diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index bb1df99096..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,232 +0,0 @@ -version: 2.1 -commands: - restore_cached_venv: - description: "Restore a cached venv" - parameters: - reqs_checksum: - type: string - default: "1234" - venv_name: - type: string - default: "default-name" - steps: - - restore_cache: - keys: - - << parameters.venv_name >>-venv-<< parameters.reqs_checksum >> - # fallback to using the latest cache if no exact match is found - - << parameters.venv_name >>-venv- - save_cached_venv: - description: "Save a venv into a cache" - parameters: - reqs_checksum: - type: string - default: "1234" - venv_path: - type: string - default: "venv" - venv_name: - type: string - default: "default-name" - steps: - - save_cache: - key: << parameters.venv_name >>-venv-<< parameters.reqs_checksum >> - paths: << parameters.venv_path >> - restore_pyspec_cached_venv: - description: "Restore the cache with pyspec keys" - steps: - - restore_cached_venv: - venv_name: v24-pyspec - reqs_checksum: cache-{{ checksum "setup.py" }} - save_pyspec_cached_venv: - description: Save a venv into a cache with pyspec keys" - steps: - - save_cached_venv: - venv_name: v24-pyspec - reqs_checksum: cache-{{ checksum "setup.py" }} - venv_path: ./venv - restore_deposit_contract_tester_cached_venv: - description: "Restore the venv from cache for the deposit contract tester" - steps: - - restore_cached_venv: - venv_name: v23-deposit-contract-tester - reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }} - save_deposit_contract_tester_cached_venv: - description: "Save the venv to cache for later use of the deposit contract tester" - steps: - - save_cached_venv: - venv_name: v23-deposit-contract-tester - reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }} - venv_path: ./solidity_deposit_contract/web3_tester/venv -jobs: - checkout_specs: - docker: - - image: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - # Restore git repo at point close to target branch/revision, to speed up checkout - - restore_cache: - keys: - - v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - v3-specs-repo-{{ .Branch }}- - - v3-specs-repo- - - checkout - - run: - name: Clean up git repo to reduce cache size - command: git gc - # Save the git checkout as a cache, to make cloning next time faster. - - save_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - paths: - - ~/specs-repo - install_pyspec_test: - docker: - - image: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - - restore_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_pyspec_cached_venv - - run: - name: Install pyspec requirements - command: make install_test - - save_pyspec_cached_venv - test: - docker: - - image: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - - restore_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_pyspec_cached_venv - - run: - name: Run py-tests - command: make citest - - 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 && make check_toc - codespell: - docker: - - image: circleci/python:3.8 - 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: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - - restore_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_pyspec_cached_venv - - run: - name: Run linter for pyspec - command: make lint - - run: - name: Run linter for test generators - command: make lint_generators - build_deposit_contract: - docker: - - image: ethereum/solc:0.6.11-alpine - steps: - - checkout - - run: - name: Install build essentials - command: | - apk update - apk add git make - - run: - name: Compile the contract - command: | - make compile_deposit_contract - git diff --color --exit-code - - persist_to_workspace: - root: . - paths: - - ./solidity_deposit_contract/deposit_contract.json - - ./build/combined.json - - ./solidity_deposit_contract/lib - test_deposit_contract: - docker: - - image: nixorg/nix:circleci - steps: - - checkout - - restore_cache: - key: nix-store-test-v2 - - attach_workspace: - at: /tmp/ - - run: - name: Test the contract - command: | - mkdir build - cp -r /tmp/build/* build - cp -r /tmp/solidity_deposit_contract/lib/* ./solidity_deposit_contract/lib - cp -r /tmp/solidity_deposit_contract/deposit_contract.json ./solidity_deposit_contract/deposit_contract.json - nix-shell --command 'make test_deposit_contract' ./solidity_deposit_contract/shell.nix - - save_cache: - key: nix-store-test-v2 - paths: - - /nix - install_deposit_contract_web3_tester: - docker: - - image: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - - restore_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_deposit_contract_tester_cached_venv - - run: - name: Install deposit contract tester requirements - command: make install_deposit_contract_web3_tester - - save_deposit_contract_tester_cached_venv - test_deposit_contract_web3_tests: - docker: - - image: circleci/python:3.8 - working_directory: ~/specs-repo - steps: - - restore_cache: - key: v3-specs-repo-{{ .Branch }}-{{ .Revision }} - - restore_deposit_contract_tester_cached_venv - - run: - name: Run deposit contract test with web3.py - command: make test_deposit_contract_web3_tests -workflows: - version: 2.1 - test_spec: - jobs: - - checkout_specs - - install_pyspec_test: - requires: - - checkout_specs - - test: - requires: - - install_pyspec_test - - table_of_contents - - codespell - - lint: - requires: - - test - # NOTE: Since phase 0 has been launched, we disabled the deposit contract tests. - # - install_deposit_contract_web3_tester: - # requires: - # - checkout_specs - # - test_deposit_contract_web3_tests: - # requires: - # - install_deposit_contract_web3_tester - build_and_test_deposit_contract: - jobs: - - build_deposit_contract - # NOTE: Since phase 0 has been launched, we disabled the deposit contract tests. - # - test_deposit_contract: - # requires: - # - build_deposit_contract diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..c7c8eed5b2 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: pip + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: + - '*' + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + actions: + patterns: + - '*' diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000000..1c3e975506 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,42 @@ +categories: + # Forks + - title: Phase0 + label: phase0 + - title: Altair + label: altair + - title: Bellatrix + label: bellatrix + - title: Capella + label: capella + - title: Deneb + label: deneb + - title: Electra + label: electra + - title: Fulu + label: fulu + + # Features + - title: EIP-6110 + label: eip6110 + - title: EIP-7441 + label: eip7441 + - title: EIP-7594 + label: eip7594 + - title: EIP-7732 + label: eip7732 + - title: EIP-7805 + label: eip7805 + + # Testing + - title: Testing + label: testing + + # Fallback + - title: Other + +sort-by: number +sort-direction: ascending +change-title-escapes: '\<*_&' +change-template: '- $TITLE (#$NUMBER)' +template: | + $CHANGES diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..1f098d0864 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,24 @@ + +name: Publish docs +on: + push: + branches: + - master +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Build docs + run: make _copy_docs + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: 3.x + - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + key: ${{ github.ref }} + path: .cache + - run: pip install mkdocs==1.4.2 mkdocs-material==9.1.5 mdx-truly-sane-lists==1.3 mkdocs-awesome-pages-plugin==2.8.0 + - run: mkdocs gh-deploy --force diff --git a/.github/workflows/generate_vectors.yml b/.github/workflows/generate_vectors.yml new file mode 100644 index 0000000000..aa51df2060 --- /dev/null +++ b/.github/workflows/generate_vectors.yml @@ -0,0 +1,71 @@ +name: Generate reference tests + +defaults: + run: + shell: zsh -e {0} + +on: + workflow_dispatch: + inputs: + repo: + description: The repository to use (e.g. user/consensus-specs) + default: ethereum/consensus-specs + type: string + required: true + ref: + description: The branch, tag or SHA to checkout and build from + default: dev + type: string + required: true + schedule: + - cron: '0 2 * * *' + +jobs: + generate-tests: + timeout-minutes: 720 # 12 hours + runs-on: [self-hosted-ghr-custom, size-xl-x64, profile-consensusSpecs] + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + repository: ${{ inputs.repo }} + path: 'consensus-specs' + ref: ${{ inputs.ref }} + - name: Setup Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.13' + cache: '' + - name: Generate tests + run: | + cd consensus-specs + set -o pipefail + make reftests verbose=true 2>&1 | tee ../consensustestgen.log + cp -r presets/ ../consensus-spec-tests/presets + cp -r configs/ ../consensus-spec-tests/configs + - name: Archive configurations + run: | + cd consensus-spec-tests + tar -czvf general.tar.gz tests/general + tar -czvf minimal.tar.gz tests/minimal + tar -czvf mainnet.tar.gz tests/mainnet + - name: Upload general.tar.gz + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: General Test Configuration + path: consensus-spec-tests/general.tar.gz + - name: Upload minimal.tar.gz + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: Minimal Test Configuration + path: consensus-spec-tests/minimal.tar.gz + - name: Upload mainnet.tar.gz + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: Mainnet Test Configuration + path: consensus-spec-tests/mainnet.tar.gz + - name: Upload consensustestgen + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: consensustestgen.log + path: consensustestgen.log diff --git a/.github/workflows/nightly-tests.yml b/.github/workflows/nightly-tests.yml new file mode 100644 index 0000000000..681abc24d5 --- /dev/null +++ b/.github/workflows/nightly-tests.yml @@ -0,0 +1,44 @@ +name: Nightly mainnet tests + +defaults: + run: + shell: zsh -e {0} + +on: + workflow_dispatch: + inputs: + ref: + description: The branch, tag or SHA to checkout and build from + default: dev + type: string + required: true + schedule: + # Every day at 00:00 UTC + - cron: '0 0 * * *' + +jobs: + tests: + timeout-minutes: 720 # 12 hours + runs-on: [self-hosted-ghr-custom, size-s-x64, profile-consensusSpecs] + strategy: + fail-fast: false + matrix: + fork: + - phase0 + - altair + - bellatrix + - capella + - deneb + - electra + - fulu + - eip7732 + - eip7805 + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.13' + - name: test-${{ matrix.fork }} + run: make test preset=mainnet fork=${{ matrix.fork }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..71c977b94e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,123 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + timeout-minutes: 1440 # 24 hours + runs-on: [self-hosted-ghr-custom, size-xl-x64, profile-consensusSpecs] + permissions: + contents: write + packages: write + pull-requests: read + + steps: + # Clone specs + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: 'consensus-specs' + + # Setup python + - name: Setup Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.13' + + # Ensure minimal tests pass + #- name: Run tests for minimal + # run: | + # cd consensus-specs + # make test preset=minimal + + # Ensure mainnet tests pass + #- name: Run tests for mainnet + # run: | + # cd consensus-specs + # make test preset=mainnet + + # Add support for large files + - name: Install Git LFS + run: | + sudo apt-get update + sudo apt-get install -y git-lfs + git lfs install + + # Clone the repo with our PAT and delete old files + - name: Clone spec tests repo + run: | + git clone https://x-access-token:${{ secrets.CONSENSUS_SPEC_TESTS_PAT }}@github.com/ethereum/consensus-spec-tests.git --depth=1 + cd consensus-spec-tests + rm -rf configs presets tests + + # Write presets/configs to the spec tests repo + - name: Copy presets/configs + run: | + cd consensus-specs + cp -r presets/ ../consensus-spec-tests/presets + cp -r configs/ ../consensus-spec-tests/configs + + # Write reference tests to the spec tests repo + - name: Generate reference tests + run: | + cd consensus-specs + make reftests verbose=true + + # Make tarballs + - name: Archive configurations + run: | + cd consensus-spec-tests + tar -czvf general.tar.gz tests/general + tar -czvf minimal.tar.gz tests/minimal + tar -czvf mainnet.tar.gz tests/mainnet + + # Commit the tests to the spec tests repo + # Cloned with PAT, so don't need to specify it here + - name: Push spec tests + run: | + cd consensus-spec-tests + git config user.name "github-actions" + git config user.email "github-actions@github.com" + git add . + if ! git diff --cached --quiet; then + git commit -m "release ${{ github.ref_name }} tests" + git push + else + echo "No changes to commit" + fi + + # Publish the specs release. We use release-drafter to + # organize PRs into the appropriate section based on PR labels + - name: Publish specs release + uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0 + with: + name: ${{ github.ref_name }} + tag: ${{ github.ref_name }} + prerelease: ${{ contains(github.ref_name, '-') }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Finally, publish the spec tests release + # Requires a personal access token (PAT) with contents:read-write + - name: Publish spec tests release + uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # v2.3.2 + with: + tag_name: ${{ github.ref_name }} + name: "Spec tests for ${{ github.ref_name }}" + body: | + Spec tests for `${{ github.ref_name }}`. + + Detailed changelog can be found in [`${{ github.ref_name }}` specs release](https://github.com/ethereum/consensus-specs/releases/tag/${{ github.ref_name }}). + prerelease: ${{ contains(github.ref_name, '-') }} + draft: false + repository: ethereum/consensus-spec-tests + files: | + consensus-spec-tests/general.tar.gz + consensus-spec-tests/minimal.tar.gz + consensus-spec-tests/mainnet.tar.gz + env: + GITHUB_TOKEN: ${{ secrets.CONSENSUS_SPEC_TESTS_PAT }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 0000000000..13cbba3ad5 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,64 @@ +name: Run tests + +defaults: + run: + shell: zsh -e {0} + +on: + push: + branches: [dev, master] + pull_request: + +jobs: + lint: + runs-on: [self-hosted-ghr-custom, size-s-x64, profile-consensusSpecs] + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.13' + cache: 'pip' + - name: Run linter for pyspec + run: | + make lint + git diff --exit-code + + whitespace: + runs-on: [self-hosted-ghr-custom, size-s-x64, profile-consensusSpecs] + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check for trailing whitespace + run: | + if git grep -n '[[:blank:]]$'; then + echo "Trailing whitespace found. Please fix it." + exit 1 + fi + + tests: + needs: [lint, whitespace] + runs-on: [self-hosted-ghr-custom, size-xl-x64, profile-consensusSpecs] + strategy: + matrix: + fork: + - phase0 + - altair + - bellatrix + - capella + - deneb + - electra + - fulu + - eip7732 + - eip7805 + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Setup python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 + with: + python-version: '3.13' + cache: 'pip' + - name: Run pyspec tests for ${{ matrix.fork }} + run: make test preset=minimal fork=${{ matrix.fork }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000000..c9835ab92f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +name: Close stale issues and PRs + +on: + schedule: + # Every day at 00:00 UTC + - cron: '0 0 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + actions: write + issues: write + pull-requests: write + steps: + - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + with: + days-before-stale: 365 + days-before-close: 30 + stale-issue-label: stale + stale-pr-label: stale + stale-issue-message: | + This issue has been inactive for one year. + Marking as stale. + It will be closed after another 30 days of inactivity. + stale-pr-message: | + This PR has been inactive for one year. + Marking as stale. + It will be closed after another 30 days of inactivity. diff --git a/.gitignore b/.gitignore index 76fe21ddde..d9e8151b3a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ venv .venvs .venv /.pytest_cache +*.swp +.eth2spec build/ output/ @@ -17,7 +19,15 @@ consensus-spec-tests/ # Dynamically built from Markdown spec tests/core/pyspec/eth2spec/phase0/ tests/core/pyspec/eth2spec/altair/ -tests/core/pyspec/eth2spec/merge/ +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/eip6800/ +tests/core/pyspec/eth2spec/eip7441/ +tests/core/pyspec/eth2spec/eip7732/ +tests/core/pyspec/eth2spec/eip7805/ # coverage reports .htmlcov @@ -32,3 +42,14 @@ tests/core/pyspec/eth2spec/test_results.xml # TOC tool outputs temporary files *.tmp + +# docs reader build +docs/specs +docs/sync +docs/ssz +docs/fork_choice +docs/README.md +site + +# docker test results +testResults diff --git a/Makefile b/Makefile index 9771410783..ecb58c1b68 100644 --- a/Makefile +++ b/Makefile @@ -1,192 +1,268 @@ +all: help + +# A list of executable specifications. +# These must pass a strict linter. +ALL_EXECUTABLE_SPEC_NAMES = \ + phase0 \ + altair \ + bellatrix \ + capella \ + deneb \ + electra \ + fulu \ + eip6800 \ + eip7441 \ + eip7732 \ + eip7805 + +# A list of fake targets. +.PHONY: \ + clean \ + coverage \ + help \ + kzg_setups \ + lint \ + pyspec \ + reftests \ + serve_docs \ + test + +############################################################################### +# Help +############################################################################### + +BOLD = $(shell tput bold) +NORM = $(shell tput sgr0) + +# Print target descriptions. +help: + @echo "make $(BOLD)clean$(NORM) -- delete all untracked files" + @echo "make $(BOLD)comptests$(NORM) -- generate compliance tests" + @echo "make $(BOLD)coverage$(NORM) -- run pyspec tests with coverage" + @echo "make $(BOLD)kzg_setups$(NORM) -- generate trusted setups" + @echo "make $(BOLD)lint$(NORM) -- run the linters" + @echo "make $(BOLD)pyspec$(NORM) -- build python specifications" + @echo "make $(BOLD)reftests$(NORM) -- generate reference tests" + @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 +MDFORMAT_VENV = $(VENV)/bin/mdformat + +# Make a virtual environment. +$(VENV): + @echo "Creating virtual environment" + @python3 -m venv $(VENV) + @$(PIP_VENV) install --quiet --upgrade uv + +############################################################################### +# Specification +############################################################################### + +TEST_LIBS_DIR = $(CURDIR)/tests/core +PYSPEC_DIR = $(TEST_LIBS_DIR)/pyspec + +# Create the pyspec for all phases. +pyspec: $(VENV) setup.py pyproject.toml + @$(PYTHON_VENV) -m uv pip install --reinstall-package=eth2spec .[docs,lint,test,generator] + @for dir in $(ALL_EXECUTABLE_SPEC_NAMES); do \ + mkdir -p "./tests/core/pyspec/eth2spec/$$dir"; \ + cp "./build/lib/eth2spec/$$dir/mainnet.py" "./tests/core/pyspec/eth2spec/$$dir/mainnet.py"; \ + cp "./build/lib/eth2spec/$$dir/minimal.py" "./tests/core/pyspec/eth2spec/$$dir/minimal.py"; \ + done + +############################################################################### +# 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)) +# Disable parallelism which running a specific test. +# Parallelism makes debugging difficult (print doesn't work). +test: MAYBE_PARALLEL := $(if $(k),,-n auto) +test: MAYBE_FORK := $(if $(fork),--fork=$(fork)) +test: PRESET := --preset=$(if $(preset),$(preset),minimal) +test: BLS := --bls-type=$(if $(bls),$(bls),fastest) +test: pyspec + @mkdir -p $(TEST_REPORT_DIR) + @$(PYTHON_VENV) -m pytest \ + $(MAYBE_PARALLEL) \ + --capture=no \ + $(MAYBE_TEST) \ + $(MAYBE_FORK) \ + $(PRESET) \ + $(BLS) \ + --junitxml=$(TEST_REPORT_DIR)/test_results.xml \ + $(CURDIR)/tests/infra \ + $(PYSPEC_DIR)/eth2spec + +############################################################################### +# Coverage +############################################################################### + +TEST_PRESET_TYPE ?= minimal +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: 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 -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 -SOLIDITY_DEPOSIT_CONTRACT_DIR = ./solidity_deposit_contract -SOLIDITY_DEPOSIT_CONTRACT_SOURCE = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/deposit_contract.sol -SOLIDITY_FILE_NAME = deposit_contract.json -DEPOSIT_CONTRACT_TESTER_DIR = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/web3_tester -CONFIGS_DIR = ./configs - -# Collect a list of generator names -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)) - -# To check generator matching: -#$(info $$GENERATOR_TARGETS is [${GENERATOR_TARGETS}]) - -MARKDOWN_FILES = $(wildcard $(SPEC_DIR)/phase0/*.md) $(wildcard $(SPEC_DIR)/altair/*.md) $(wildcard $(SSZ_DIR)/*.md) \ - $(wildcard $(SPEC_DIR)/merge/*.md) \ - $(wildcard $(SPEC_DIR)/custody/*.md) \ - $(wildcard $(SPEC_DIR)/das/*.md) \ - $(wildcard $(SPEC_DIR)/sharding/*.md) - -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} -LINTER_CONFIG_FILE = $(CURRENT_DIR)/linter.ini -GENERATOR_ERROR_LOG_FILE = $(CURRENT_DIR)/$(TEST_VECTOR_DIR)/testgen_error_log.txt - -export DAPP_SKIP_BUILD:=1 -export DAPP_SRC:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR) -export DAPP_LIB:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)/lib -export DAPP_JSON:=build/combined.json - -.PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \ - install_deposit_contract_tester test_deposit_contract install_deposit_contract_compiler \ - compile_deposit_contract test_compile_deposit_contract check_toc \ - 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 $(ETH2SPEC_MODULE_DIR)/phase0 - rm -rf $(ETH2SPEC_MODULE_DIR)/altair - rm -rf $(ETH2SPEC_MODULE_DIR)/merge - rm -rf $(COV_HTML_OUT_DIR) - rm -rf $(TEST_REPORT_DIR) - rm -rf eth2spec.egg-info dist build - rm -rf build - -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/* - - -# "make generate_tests" to run all generators -generate_tests: $(GENERATOR_TARGETS) - -# "make pyspec" to create the pyspec for all phases. -pyspec: - . venv/bin/activate; python3 setup.py pyspecdev - -# installs the packages to run pyspec tests -install_test: - python3 -m venv venv; . venv/bin/activate; python3 -m pip install -e .[lint]; python3 -m pip install -e .[test] - -# Testing against `minimal` config by default -test: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 4 --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec - -# Testing against `minimal` config by default -find_test: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -k=$(K) --disable-bls --cov=eth2spec.phase0.minimal --cov=eth2spec.altair.minimal --cov-report="html:$(COV_HTML_OUT)" --cov-branch eth2spec +SYNC_DIR = ./sync -citest: pyspec - mkdir -p tests/core/pyspec/test-reports/eth2spec; . venv/bin/activate; cd $(PY_SPEC_DIR); \ - python3 -m pytest -n 4 --bls-type=milagro --junitxml=eth2spec/test_results.xml eth2spec +# 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 -open_cov: - ((open "$(COV_INDEX_FILE)" || xdg-open "$(COV_INDEX_FILE)") &> /dev/null) & +# Start a local documentation server. +serve_docs: _copy_docs + @mkdocs build + @mkdocs serve -check_toc: $(MARKDOWN_FILES:=.toc) +############################################################################### +# Checks +############################################################################### -%.toc: - cp $* $*.tmp && \ - doctoc $* && \ - diff -q $* $*.tmp && \ - rm $*.tmp +MYPY_CONFIG = $(CURDIR)/mypy.ini +PYLINT_CONFIG = $(CURDIR)/pylint.ini -codespell: - codespell . --skip ./.git -I .codespell-whitelist +PYLINT_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), $(PYSPEC_DIR)/eth2spec/$S) +MYPY_SCOPE := $(foreach S,$(ALL_EXECUTABLE_SPEC_NAMES), -p eth2spec.$S) +MARKDOWN_FILES := $(shell find $(CURDIR) -name '*.md') -# TODO: add future merge, sharding, etc. packages to linting. +# Check for mistakes. lint: pyspec - . venv/bin/activate; cd $(PY_SPEC_DIR); \ - flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \ - && mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.altair -p eth2spec.merge - -lint_generators: pyspec - . venv/bin/activate; cd $(TEST_GENERATORS_DIR); \ - flake8 --config $(LINTER_CONFIG_FILE) - -compile_deposit_contract: - @cd $(SOLIDITY_DEPOSIT_CONTRACT_DIR) - @git submodule update --recursive --init - @solc --metadata-literal --optimize --optimize-runs 5000000 --bin --abi --combined-json=abi,bin,bin-runtime,srcmap,srcmap-runtime,ast,metadata,storage-layout --overwrite -o build $(SOLIDITY_DEPOSIT_CONTRACT_SOURCE) $(SOLIDITY_DEPOSIT_CONTRACT_DIR)/tests/deposit_contract.t.sol - @/bin/echo -n '{"abi": ' > $(SOLIDITY_FILE_NAME) - @cat build/DepositContract.abi >> $(SOLIDITY_FILE_NAME) - @/bin/echo -n ', "bytecode": "0x' >> $(SOLIDITY_FILE_NAME) - @cat build/DepositContract.bin >> $(SOLIDITY_FILE_NAME) - @/bin/echo -n '"}' >> $(SOLIDITY_FILE_NAME) - -test_deposit_contract: - dapp test -v --fuzz-runs 5 - -install_deposit_contract_web3_tester: - cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; python3 -m pip install -r requirements.txt - -test_deposit_contract_web3_tests: - cd $(DEPOSIT_CONTRACT_TESTER_DIR); . venv/bin/activate; \ - python3 -m pytest . - -# 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 -r requirements.txt; \ - python3 main.py -o $(CURRENT_DIR)/$(TEST_VECTOR_DIR); \ - 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) - -# For any generator, build it using the run_generator function. -# (creation of output dir is a dependency) -gen_%: $(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" + @$(MDFORMAT_VENV) --number --wrap=80 $(MARKDOWN_FILES) + @$(CODESPELL_VENV) . --skip "./.git,$(VENV),$(PYSPEC_DIR)/.mypy_cache" -I .codespell-whitelist + @$(PYTHON_VENV) -m ruff check --fix --quiet $(CURDIR)/tests $(CURDIR)/pysetup $(CURDIR)/setup.py + @$(PYTHON_VENV) -m ruff format --quiet $(CURDIR)/tests $(CURDIR)/pysetup $(CURDIR)/setup.py + @$(PYTHON_VENV) -m mypy --config-file $(MYPY_CONFIG) $(MYPY_SCOPE) + +############################################################################### +# Generators +############################################################################### + +COMMA:= , +TEST_VECTOR_DIR = $(CURDIR)/../consensus-spec-tests/tests + +# Generate reference tests. +# This will forcibly rebuild pyspec just in case. +# To generate reference tests for a single runner, append runner=, eg: +# make reftests runner=bls +# To generate reference tests with more details, append verbose=true, eg: +# make reftests runner=bls verbose=true +# To generate reference tests with fewer threads, append threads=N, eg: +# make reftests runner=bls threads=1 +# To generate reference tests for a specific test, append k=, eg: +# make reftests runner=operations k=invalid_committee_index +# To generate reference tests for a specific fork, append fork=, eg: +# make reftests runner=operations fork=fulu +# To generate reference tests for a specific preset, append preset=, eg: +# make reftests runner=operations preset=mainnet +# To generate reference tests for a list of tests, forks, and/or presets, append them as comma-separated lists, eg: +# make reftests runner=operations k=invalid_committee_index,invalid_too_many_committee_bits +# Or all at the same time, eg: +# make reftests runner=operations preset=mainnet fork=fulu k=invalid_committee_index +reftests: MAYBE_VERBOSE := $(if $(filter true,$(verbose)),--verbose) +reftests: MAYBE_THREADS := $(if $(threads),--threads=$(threads)) +reftests: MAYBE_RUNNERS := $(if $(runner),--runners $(subst ${COMMA}, ,$(runner))) +reftests: MAYBE_TESTS := $(if $(k),--cases $(subst ${COMMA}, ,$(k))) +reftests: MAYBE_FORKS := $(if $(fork),--forks $(subst ${COMMA}, ,$(fork))) +reftests: MAYBE_PRESETS := $(if $(preset),--presets $(subst ${COMMA}, ,$(preset))) +reftests: pyspec + @$(PYTHON_VENV) -m tests.generators.main \ + --output $(TEST_VECTOR_DIR) \ + $(MAYBE_VERBOSE) \ + $(MAYBE_THREADS) \ + $(MAYBE_RUNNERS) \ + $(MAYBE_TESTS) \ + $(MAYBE_FORKS) \ + $(MAYBE_PRESETS) + +# Generate KZG trusted setups for testing. +kzg_setups: pyspec + @for preset in minimal mainnet; do \ + $(PYTHON_VENV) $(CURDIR)/scripts/gen_kzg_trusted_setups.py \ + --secret=1337 \ + --g1-length=4096 \ + --g2-length=65 \ + --output-dir $(CURDIR)/presets/$$preset/trusted_setups; \ + done + +COMP_TEST_VECTOR_DIR = $(CURDIR)/../compliance-spec-tests/tests + +# Generate compliance tests (fork choice). +# This will forcibly rebuild pyspec just in case. +# To generate compliance tests with a particular configuration, append fc_gen_config=, +# where can be either tiny, small or standard, eg: +# make comptests fc_gen_config=standard +# One can specify threads=N, fork= or preset= as with reftests, eg: +# make comptests fc_gen_config=standard fork=deneb preset=mainnet threads=8 +comptests: FC_GEN_CONFIG := $(if $(fc_gen_config),$(fc_gen_config),tiny) +comptests: MAYBE_THREADS := $(if $(threads),--threads=$(threads),--fc-gen-multi-processing) +comptests: MAYBE_FORKS := $(if $(fork),--forks $(subst ${COMMA}, ,$(fork))) +comptests: MAYBE_PRESETS := $(if $(preset),--presets $(subst ${COMMA}, ,$(preset))) +comptests: pyspec + @$(PYTHON_VENV) -m tests.generators.compliance_runners.fork_choice.test_gen \ + --output $(COMP_TEST_VECTOR_DIR) \ + --fc-gen-config $(FC_GEN_CONFIG) \ + $(MAYBE_THREADS) \ + $(MAYBE_FORKS) \ + $(MAYBE_PRESETS) + +############################################################################### +# Cleaning +############################################################################### + +# Delete all untracked files. +clean: + @git clean -fdx diff --git a/README.md b/README.md index b5a898d370..7ea5fa1634 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,106 @@ # Ethereum Proof-of-Stake Consensus Specifications -[![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/qGpsxSA) +[![testgen](https://github.com/ethereum/consensus-specs/actions/workflows/generate_vectors.yml/badge.svg?branch=dev&event=schedule)](https://github.com/ethereum/consensus-specs/actions/workflows/generate_vectors.yml) -To learn more about proof-of-stake and sharding, see the [PoS FAQ](https://eth.wiki/en/concepts/proof-of-stake-faqs), [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm). +This repository hosts the current Ethereum +[proof-of-stake](https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/) +specifications. Discussions about design rationale and proposed changes can be +brought up and discussed as issues. Solidified, agreed-upon changes to the +specifications can be made through pull requests. -This repository hosts the current Ethereum proof-of-stake specifications. Discussions about design rationale and proposed changes can be brought up and discussed as issues. Solidified, agreed-upon changes to the spec can be made through pull requests. +## Specifications +Core specifications for Ethereum proof-of-stake clients can be found in +[specs](specs). These are divided into features. Features are researched and +developed in parallel, and then consolidated into sequential upgrades when +ready. -## Specs +### Stable Specifications -[![GitHub release](https://img.shields.io/github/v/release/ethereum/eth2.0-specs)](https://github.com/ethereum/eth2.0-specs/releases/) [![PyPI version](https://badge.fury.io/py/eth2spec.svg)](https://badge.fury.io/py/eth2spec) +| Seq. | Code Name | Fork Epoch | Links | +| ---- | ------------- | ---------- | ---------------------------------------------------------------------------- | +| 0 | **Phase0** | `0` | [Specs](specs/phase0), [Tests](tests/core/pyspec/eth2spec/test/phase0) | +| 1 | **Altair** | `74240` | [Specs](specs/altair), [Tests](tests/core/pyspec/eth2spec/test/altair) | +| 2 | **Bellatrix** | `144896` | [Specs](specs/bellatrix), [Tests](tests/core/pyspec/eth2spec/test/bellatrix) | +| 3 | **Capella** | `194048` | [Specs](specs/capella), [Tests](tests/core/pyspec/eth2spec/test/capella) | +| 4 | **Deneb** | `269568` | [Specs](specs/deneb), [Tests](tests/core/pyspec/eth2spec/test/deneb) | +| 5 | **Electra** | `364032` | [Specs](specs/electra), [Tests](tests/core/pyspec/eth2spec/test/electra) | -Core specifications for Ethereum proof-of-stake clients can be found in [specs](specs/). These are divided into features. -Features are researched and developed in parallel, and then consolidated into sequential upgrades when ready. +### In-development Specifications -The current features are: +| Seq. | Code Name | Fork Epoch | Links | +| ---- | --------- | ---------- | ------------------------------------------------------------------ | +| 6 | **Fulu** | TBD | [Specs](specs/fulu), [Tests](tests/core/pyspec/eth2spec/test/fulu) | -### Phase 0 +### Accompanying documents -* [The Beacon Chain](specs/phase0/beacon-chain.md) -* [Beacon Chain Fork Choice](specs/phase0/fork-choice.md) -* [Deposit Contract](specs/phase0/deposit-contract.md) -* [Honest Validator](specs/phase0/validator.md) -* [P2P Networking](specs/phase0/p2p-interface.md) -* [Weak Subjectivity](specs/phase0/weak-subjectivity.md) +- [SimpleSerialize (SSZ) spec](ssz/simple-serialize.md) +- [Merkle proof formats](ssz/merkle-proofs.md) +- [General test format](tests/formats/README.md) -### Altair +### External specifications -* [Beacon chain changes](specs/altair/beacon-chain.md) -* [Altair fork](specs/altair/fork.md) -* [Light client sync protocol](specs/altair/sync-protocol.md) -* [Honest Validator guide changes](specs/altair/validator.md) -* [P2P Networking](specs/altair/p2p-interface.md) +Additional specifications and standards outside of requisite client +functionality can be found in the following repositories: -### Merge +- [Beacon APIs](https://github.com/ethereum/beacon-apis) +- [Engine APIs](https://github.com/ethereum/execution-apis/tree/main/src/engine) +- [Beacon Metrics](https://github.com/ethereum/beacon-metrics) +- [Builder Specs](https://github.com/ethereum/builder-specs) -The merge is still actively in development. The exact specification has not been formally accepted as final and details are still subject to change. +### Reference tests -* Background material: - * An [ethresear.ch](https://ethresear.ch) post [describing the basic mechanism](https://ethresear.ch/t/the-eth1-eth2-transition/6265) - * [ethereum.org](https://ethereum.org) high-level description of the merge [here](https://ethereum.org/en/eth2/docking/) -* Specifications: - * [Beacon Chain changes](specs/merge/beacon-chain.md) - * [Merge fork](specs/merge/fork.md) - * [Fork Choice changes](specs/merge/fork-choice.md) - * [Validator additions](specs/merge/validator.md) - * [Client settings](specs/merge/client_settings.md) +Reference tests built from the executable Python spec are available in the +[Ethereum Proof-of-Stake Consensus Spec Tests](https://github.com/ethereum/consensus-spec-tests) +repository. Compressed tarballs are available for each release +[here](https://github.com/ethereum/consensus-spec-tests/releases). Nightly +reference tests are available +[here](https://github.com/ethereum/consensus-specs/actions/workflows/generate_vectors.yml). -### Sharding +## Contributors -Sharding follows the merge, and is divided into three parts: +### Installation and usage -* Sharding base functionality - In early engineering phase - * [Beacon Chain changes](specs/sharding/beacon-chain.md) - * [P2P Network changes](specs/sharding/p2p-interface.md) -* Custody Game - Ready, dependent on sharding - * [Beacon Chain changes](specs/custody_game/beacon-chain.md) - * [Validator custody work](specs/custody_game/validator.md) -* Data Availability Sampling - In active R&D - * Technical details [here](https://hackmd.io/@HWeNw8hNRimMm2m2GH56Cw/B1YJPGkpD). - * [Core types and functions](specs/das/das-core.md) - * [P2P Networking](specs/das/p2p-interface.md) - * [Fork Choice](specs/das/fork-choice.md) - * [Sampling process](specs/das/sampling.md) +Clone the repository with: -### Accompanying documents can be found in [specs](specs) and include: +```bash +git clone https://github.com/ethereum/consensus-specs.git +``` -* [SimpleSerialize (SSZ) spec](ssz/simple-serialize.md) -* [Merkle proof formats](ssz/merkle-proofs.md) -* [General test format](tests/formats/README.md) +Switch to the directory: -## Additional specifications for client implementers +```bash +cd consensus-specs +``` -Additional specifications and standards outside of requisite client functionality can be found in the following repos: +View the help output: -* [Beacon APIs](https://github.com/ethereum/beacon-apis) -* [Beacon Metrics](https://github.com/ethereum/beacon-metrics/) +```bash +make help +``` -## Design goals +### Design goals -The following are the broad design goals for the Ethereum proof-of-stake consensus specifications: -* to minimize complexity, even at the cost of some losses in efficiency -* to remain live through major network partitions and when very large portions of nodes go offline -* to select all components such that they are either quantum secure or can be easily swapped out for quantum secure counterparts when available -* to utilize crypto and design techniques that allow for a large participation of validators in total and per unit time -* to allow for a typical consumer laptop with `O(C)` resources to process/validate `O(1)` shards (including any system level validation such as the beacon chain) +The following are the broad design goals for the Ethereum proof-of-stake +consensus specifications: -## Useful external resources +- Minimize complexity, even at the cost of some losses in efficiency. +- Remain live through major network partitions and when very large portions of + nodes go offline. +- Select components that are quantum secure or easily swappable for + quantum-secure alternatives. +- Utilize crypto and design techniques that allow for a large participation of + validators. +- Minimize hardware requirements such that a consumer laptop can participate. -* [Design Rationale](https://notes.ethereum.org/s/rkhCgQteN#) -* [Phase 0 Onboarding Document](https://notes.ethereum.org/s/Bkn3zpwxB) -* [Combining GHOST and Casper paper](https://arxiv.org/abs/2003.03052) +### Useful resources -## For spec contributors - -Documentation on the different components used during spec writing can be found here: -* [YAML Test Generators](tests/generators/README.md) -* [Executable Python Spec, with Py-tests](tests/core/pyspec/README.md) - -## Consensus spec tests - -Conformance tests built from the executable python spec are available in the [Ethereum Proof-of-Stake Consensus Spec Tests](https://github.com/ethereum/consensus-spec-tests) repo. Compressed tarballs are available in [releases](https://github.com/ethereum/consensus-spec-tests/releases). +- [Design Rationale](https://notes.ethereum.org/s/rkhCgQteN#) +- [Phase0 Onboarding Document](https://notes.ethereum.org/s/Bkn3zpwxB) +- [Combining GHOST and Casper paper](https://arxiv.org/abs/2003.03052) +- [Specifications viewer (mkdocs)](https://ethereum.github.io/consensus-specs/) +- [Specifications viewer (jtraglia)](https://jtraglia.github.io/eth-spec-viewer/) +- [The Eth2 Book](https://eth2book.info) +- [PySpec Tests](tests/core/pyspec/README.md) +- [Reference Tests Generators](tests/generators/README.md) diff --git a/SECURITY.md b/SECURITY.md index e46fab4de1..64a7171250 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,10 +2,16 @@ ## Supported Versions -Please see [Releases](https://github.com/ethereum/consensus-specs/releases/). We recommend using the [most recently released version](https://github.com/ethereum/consensus-specs/releases/latest). +Please see [Releases](https://github.com/ethereum/consensus-specs/releases/). We +recommend using the +[most recently released version](https://github.com/ethereum/consensus-specs/releases/latest). ## Reporting a Vulnerability **Please do not file a public ticket** mentioning the vulnerability. -To find out how to disclose a vulnerability in the Ethereum Consensus Layer visit [https://eth2bounty.ethereum.org](https://eth2bounty.ethereum.org) or email eth2bounty@ethereum.org. Please read the [disclosure page](https://eth2bounty.ethereum.org) for more information about publicly disclosed security vulnerabilities. +To find out how to disclose a vulnerability in the Ethereum Consensus Layer +visit [https://bounty.ethereum.org](https://bounty.ethereum.org) or email +bounty@ethereum.org. Please read the +[disclosure page](https://bounty.ethereum.org) for more information about +publicly disclosed security vulnerabilities. diff --git a/configs/README.md b/configs/README.md index f470b932d0..63062350f0 100644 --- a/configs/README.md +++ b/configs/README.md @@ -1,39 +1,51 @@ # 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`, -and then load different configurations around that preset to participate in different networks or tests. +This directory contains a set of configurations used for testing, testnets, and +mainnet. 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: + - [`mainnet.yaml`](./mainnet.yaml): Mainnet configuration -- [`minimal.yaml`](./minimal.yaml): Minimal configuration, used in spec-testing along with the [`minimal`](../presets/minimal) preset. +- [`minimal.yaml`](./minimal.yaml): Minimal configuration, used in spec-testing + along with the [`minimal`](../presets/minimal) preset. -Not all network configurations are in scope for the specification, -see [`github.com/eth2-clients/eth2-networks`](https://github.com/eth2-clients/eth2-networks) for common networks, -and additional testnet assets. +Not all network configurations are in scope for the specification, see +[`github.com/eth-clients/eth2-networks`](https://github.com/eth-clients/eth2-networks) +for common networks, and additional testnet assets. ## Forking -Variables are not replaced but extended with forks. This is to support syncing from one state to another over a fork boundary, without hot-swapping a config. -Instead, for forks that introduce changes in a variable, the variable name is suffixed with the fork name, e.g. `INACTIVITY_PENALTY_QUOTIENT_ALTAIR`. +Variables are not replaced but extended with forks. This is to support syncing +from one state to another over a fork boundary, without hot-swapping a config. +Instead, for forks that introduce changes in a variable, the variable name is +suffixed with the fork name, e.g. `INACTIVITY_PENALTY_QUOTIENT_ALTAIR`. + +Future-fork variables can be ignored, e.g. ignore Sharding variables as a client +that only supports Phase 0 currently. -Future-fork variables can be ignored, e.g. ignore Sharding variables as a client that only supports Phase 0 currently. +Over time, the need to sync an older state may be deprecated. In this case, the +suffix on the new variable may be removed, and the old variable will keep a +special name before completely being removed. -Over time, the need to sync an older state may be deprecated. -In this case, the suffix on the new variable may be removed, and the old variable will keep a special name before completely being removed. +A previous iteration of forking made use of "timelines", but this collides with +the definitions used in the spec (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. -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. -**Key**: an `UPPER_SNAKE_CASE` (a.k.a. "macro case") formatted string, name of the variable. +**Key**: an `UPPER_SNAKE_CASE` (a.k.a. "macro case") formatted string, name of +the variable. **Value** can be either: - - an unsigned integer number, can be up to 64 bits (incl.) - - a hexadecimal string, prefixed with `0x` -This format is fully YAML compatible. -The presets and configurations may contain comments to describe the values. +- an unsigned integer number, can be up to 64 bits (incl.) +- a hexadecimal string, prefixed with `0x` + +This format is fully YAML compatible. The presets and configurations may contain +comments to describe the values. diff --git a/configs/mainnet.yaml b/configs/mainnet.yaml index dd5b394af4..aaae0a2269 100644 --- a/configs/mainnet.yaml +++ b/configs/mainnet.yaml @@ -3,18 +3,34 @@ # Extends the mainnet preset PRESET_BASE: 'mainnet' +# Free-form short name of the network that this configuration applies to - known +# canonical network names include: +# * 'mainnet' - there can be only one +# * 'sepolia' - testnet +# * 'holesky' - testnet +# * 'hoodi' - testnet +# Must match the regex: [a-z0-9\-] +CONFIG_NAME: 'mainnet' + +# Transition +# --------------------------------------------------------------- +# Estimated on Sept 15, 2022 +TERMINAL_TOTAL_DIFFICULTY: 58750000000000000000000 +# By default, don't use these params +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + # Genesis # --------------------------------------------------------------- -# `2**14` (= 16,384) +# 2**14 (= 16,384) MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 # Dec 1, 2020, 12pm UTC MIN_GENESIS_TIME: 1606824000 # Mainnet initial fork version, recommend altering for testnets GENESIS_FORK_VERSION: 0x00000000 -# 604800 seconds (7 days) +# 7 * 24 * 3,600 (= 604,800) seconds, 7 days GENESIS_DELAY: 604800 - # Forking # --------------------------------------------------------------- # Some forks are disabled for now: @@ -23,17 +39,31 @@ GENESIS_DELAY: 604800 # Altair ALTAIR_FORK_VERSION: 0x01000000 -ALTAIR_FORK_EPOCH: 18446744073709551615 -# Merge -MERGE_FORK_VERSION: 0x02000000 -MERGE_FORK_EPOCH: 18446744073709551615 -# Sharding -SHARDING_FORK_VERSION: 0x03000000 -SHARDING_FORK_EPOCH: 18446744073709551615 - -# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D. -MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296 - +ALTAIR_FORK_EPOCH: 74240 # Oct 27, 2021, 10:56:23am UTC +# Bellatrix +BELLATRIX_FORK_VERSION: 0x02000000 +BELLATRIX_FORK_EPOCH: 144896 # Sept 6, 2022, 11:34:47am UTC +# Capella +CAPELLA_FORK_VERSION: 0x03000000 +CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC +# Deneb +DENEB_FORK_VERSION: 0x04000000 +DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC +# Electra +ELECTRA_FORK_VERSION: 0x05000000 +ELECTRA_FORK_EPOCH: 364032 # May 7, 2025, 10:05:11am UTC +# Fulu +FULU_FORK_VERSION: 0x06000000 +FULU_FORK_EPOCH: 18446744073709551615 # temporary stub +# EIP7441 +EIP7441_FORK_VERSION: 0x08000000 # temporary stub +EIP7441_FORK_EPOCH: 18446744073709551615 +# EIP7732 +EIP7732_FORK_VERSION: 0x09000000 # temporary stub +EIP7732_FORK_EPOCH: 18446744073709551615 +# EIP7805 +EIP7805_FORK_VERSION: 0x0a000000 # temporary stub +EIP7805_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -41,14 +71,13 @@ MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296 SECONDS_PER_SLOT: 12 # 14 (estimate from Eth1 mainnet) SECONDS_PER_ETH1_BLOCK: 14 -# 2**8 (= 256) epochs ~27 hours +# 2**8 (= 256) epochs, ~27 hours MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 -# 2**8 (= 256) epochs ~27 hours +# 2**8 (= 256) epochs, ~27 hours SHARD_COMMITTEE_PERIOD: 256 -# 2**11 (= 2,048) Eth1 blocks ~8 hours +# 2**11 (= 2,048) Eth1 blocks, ~8 hours ETH1_FOLLOW_DISTANCE: 2048 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -57,11 +86,23 @@ INACTIVITY_SCORE_BIAS: 4 INACTIVITY_SCORE_RECOVERY_RATE: 16 # 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000 -# 2**2 (= 4) +# 2**2 (= 4) validators MIN_PER_EPOCH_CHURN_LIMIT: 4 # 2**16 (= 65,536) CHURN_LIMIT_QUOTIENT: 65536 +# [New in Deneb:EIP7514] 2**3 (= 8) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 8 +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# 2 epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 # Deposit contract # --------------------------------------------------------------- @@ -69,3 +110,88 @@ CHURN_LIMIT_QUOTIENT: 65536 DEPOSIT_CHAIN_ID: 1 DEPOSIT_NETWORK_ID: 1 DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa + +# Networking +# --------------------------------------------------------------- +# 10 * 2**20 (= 10,485,760) bytes, 10 MiB +MAX_PAYLOAD_SIZE: 10485760 +# 2**10 (= 1,024) blocks +MAX_REQUEST_BLOCKS: 1024 +# 2**8 (= 256) epochs +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33,024) epochs, ~5 months +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 33024 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +# 0 bits +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS (= 6 + 0) bits +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# 2**7 (= 128) blocks +MAX_REQUEST_BLOCKS_DENEB: 128 +# 2**12 (= 4,096) epochs, ~18 days +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# 6 subnets +BLOB_SIDECAR_SUBNET_COUNT: 6 +# 6 blobs +MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars +MAX_REQUEST_BLOB_SIDECARS: 768 + +# Electra +# 2**7 * 10**9 (= 128,000,000,000) Gwei +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 +# 2**8 * 10**9 (= 256,000,000,000) Gwei +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 +# 9 subnets +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# 9 blobs +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA (= 128 * 9) sidecars +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 +VALIDATOR_CUSTODY_REQUIREMENT: 8 +BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 + +# EIP7441 +EPOCHS_PER_SHUFFLING_PHASE: 256 +PROPOSER_SELECTION_GAP: 2 + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 +PROPOSER_SCORE_BOOST_EIP7732: 20 + +# EIP7805 +ATTESTATION_DEADLINE: 4 +PROPOSER_INCLUSION_LIST_CUT_OFF: 11 +VIEW_FREEZE_DEADLINE: 9 +# 2**4 (= 16) +MAX_REQUEST_INCLUSION_LIST: 16 +# 2**13 (= 8192) +MAX_BYTES_PER_INCLUSION_LIST: 8192 + +# Blob Scheduling +# --------------------------------------------------------------- + +BLOB_SCHEDULE: [] diff --git a/configs/minimal.yaml b/configs/minimal.yaml index b067f222fb..553349168d 100644 --- a/configs/minimal.yaml +++ b/configs/minimal.yaml @@ -3,51 +3,77 @@ # Extends the minimal preset PRESET_BASE: 'minimal' +# Free-form short name of the network that this configuration applies to - known +# canonical network names include: +# * 'minimal' - spec-testing +# Must match the regex: [a-z0-9\-] +CONFIG_NAME: 'minimal' + +# Transition +# --------------------------------------------------------------- +# 2**256-2**10 for testing minimal network +TERMINAL_TOTAL_DIFFICULTY: 115792089237316195423570985008687907853269984665640564039457584007913129638912 +# By default, don't use these params +TERMINAL_BLOCK_HASH: 0x0000000000000000000000000000000000000000000000000000000000000000 +TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH: 18446744073709551615 + # Genesis # --------------------------------------------------------------- -# [customized] +# [customized] 2**6 (= 64) MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 64 # Jan 3, 2020 MIN_GENESIS_TIME: 1578009600 -# Highest byte set to 0x01 to avoid collisions with mainnet versioning +# [customized] GENESIS_FORK_VERSION: 0x00000001 -# [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis +# [customized] 5 * 60 (= 300) seconds GENESIS_DELAY: 300 - # Forking # --------------------------------------------------------------- # Values provided for illustrative purposes. # Individual tests/testnets may set different values. -# Altair +# [customized] Altair ALTAIR_FORK_VERSION: 0x01000001 ALTAIR_FORK_EPOCH: 18446744073709551615 -# Merge -MERGE_FORK_VERSION: 0x02000001 -MERGE_FORK_EPOCH: 18446744073709551615 -# Sharding -SHARDING_FORK_VERSION: 0x03000001 -SHARDING_FORK_EPOCH: 18446744073709551615 - -# TBD, 2**32 is a placeholder. Merge transition approach is in active R&D. -MIN_ANCHOR_POW_BLOCK_DIFFICULTY: 4294967296 - +# [customized] Bellatrix +BELLATRIX_FORK_VERSION: 0x02000001 +BELLATRIX_FORK_EPOCH: 18446744073709551615 +# [customized] Capella +CAPELLA_FORK_VERSION: 0x03000001 +CAPELLA_FORK_EPOCH: 18446744073709551615 +# [customized] Deneb +DENEB_FORK_VERSION: 0x04000001 +DENEB_FORK_EPOCH: 18446744073709551615 +# [customized] Electra +ELECTRA_FORK_VERSION: 0x05000001 +ELECTRA_FORK_EPOCH: 18446744073709551615 +# [customized] Fulu +FULU_FORK_VERSION: 0x06000001 +FULU_FORK_EPOCH: 18446744073709551615 +# [customized] EIP7441 +EIP7441_FORK_VERSION: 0x08000001 +EIP7441_FORK_EPOCH: 18446744073709551615 +# [customized] EIP7732 +EIP7732_FORK_VERSION: 0x09000001 +EIP7732_FORK_EPOCH: 18446744073709551615 +# [customized] EIP7805 +EIP7805_FORK_VERSION: 0x0a000001 +EIP7805_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- -# [customized] Faster for testing purposes +# [customized] 6 seconds SECONDS_PER_SLOT: 6 # 14 (estimate from Eth1 mainnet) SECONDS_PER_ETH1_BLOCK: 14 -# 2**8 (= 256) epochs +# 2**8 (= 256) epochs, ~27 hours MIN_VALIDATOR_WITHDRAWABILITY_DELAY: 256 -# [customized] higher frequency of committee turnover and faster time to acceptable voluntary exit +# [customized] 2**6 (= 64) epochs, ~7 hours SHARD_COMMITTEE_PERIOD: 64 -# [customized] process deposits more quickly, but insecure +# [customized] 2**4 (= 16) Eth1 blocks, ~4 minutes ETH1_FOLLOW_DISTANCE: 16 - # Validator cycle # --------------------------------------------------------------- # 2**2 (= 4) @@ -56,11 +82,23 @@ INACTIVITY_SCORE_BIAS: 4 INACTIVITY_SCORE_RECOVERY_RATE: 16 # 2**4 * 10**9 (= 16,000,000,000) Gwei EJECTION_BALANCE: 16000000000 -# 2**2 (= 4) -MIN_PER_EPOCH_CHURN_LIMIT: 4 -# [customized] scale queue churn at much lower validator counts for testing +# [customized] 2**1 (= 2) validators +MIN_PER_EPOCH_CHURN_LIMIT: 2 +# [customized] 2**5 (= 32) CHURN_LIMIT_QUOTIENT: 32 +# [customized] [New in Deneb:EIP7514] 2**2 (= 4) +MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT: 4 +# Fork choice +# --------------------------------------------------------------- +# 40% +PROPOSER_SCORE_BOOST: 40 +# 20% +REORG_HEAD_WEIGHT_THRESHOLD: 20 +# 160% +REORG_PARENT_WEIGHT_THRESHOLD: 160 +# 2 epochs +REORG_MAX_EPOCHS_SINCE_FINALIZATION: 2 # Deposit contract # --------------------------------------------------------------- @@ -69,3 +107,88 @@ DEPOSIT_CHAIN_ID: 5 DEPOSIT_NETWORK_ID: 5 # Configured on a per testnet basis DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 + +# Networking +# --------------------------------------------------------------- +# `10 * 2**20` (= 10485760, 10 MiB) +MAX_PAYLOAD_SIZE: 10485760 +# `2**10` (= 1024) +MAX_REQUEST_BLOCKS: 1024 +# `2**8` (= 256) +EPOCHS_PER_SUBNET_SUBSCRIPTION: 256 +# [customized] `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 272) epochs +MIN_EPOCHS_FOR_BLOCK_REQUESTS: 272 +# 5s +TTFB_TIMEOUT: 5 +# 10s +RESP_TIMEOUT: 10 +ATTESTATION_PROPAGATION_SLOT_RANGE: 32 +# 500ms +MAXIMUM_GOSSIP_CLOCK_DISPARITY: 500 +MESSAGE_DOMAIN_INVALID_SNAPPY: 0x00000000 +MESSAGE_DOMAIN_VALID_SNAPPY: 0x01000000 +# 2 subnets per node +SUBNETS_PER_NODE: 2 +# 2**8 (= 64) +ATTESTATION_SUBNET_COUNT: 64 +# 0 bits +ATTESTATION_SUBNET_EXTRA_BITS: 0 +# ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS +ATTESTATION_SUBNET_PREFIX_BITS: 6 + +# Deneb +# 2**7 (= 128) blocks +MAX_REQUEST_BLOCKS_DENEB: 128 +# 2**12 (= 4096) epochs, ~18 days +MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 +# 6 subnets +BLOB_SIDECAR_SUBNET_COUNT: 6 +## 6 blobs +MAX_BLOBS_PER_BLOCK: 6 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK (= 128 * 6) sidecars +MAX_REQUEST_BLOB_SIDECARS: 768 + +# Electra +# [customized] 2**6 * 10**9 (= 64,000,000,000) Gwei +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 +# [customized] 2**7 * 10**9 (= 128,000,000,000) Gwei +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 128000000000 +# 9 subnets +BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9 +# 9 blobs +MAX_BLOBS_PER_BLOCK_ELECTRA: 9 +# MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA (= 128 * 9) sidecars +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 +VALIDATOR_CUSTODY_REQUIREMENT: 8 +BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 + +# EIP7441 +EPOCHS_PER_SHUFFLING_PHASE: 4 +PROPOSER_SELECTION_GAP: 1 + +# EIP7732 +MAX_REQUEST_PAYLOADS: 128 +PROPOSER_SCORE_BOOST_EIP7732: 20 + +# EIP7805 +ATTESTATION_DEADLINE: 2 +PROPOSER_INCLUSION_LIST_CUT_OFF: 5 +VIEW_FREEZE_DEADLINE: 3 +# 2**4 (= 16) +MAX_REQUEST_INCLUSION_LIST: 16 +# 2**13 (= 8192) +MAX_BYTES_PER_INCLUSION_LIST: 8192 + +# Blob Scheduling +# --------------------------------------------------------------- + +BLOB_SCHEDULE: [] diff --git a/docs/.pages b/docs/.pages new file mode 100644 index 0000000000..d9e382ede5 --- /dev/null +++ b/docs/.pages @@ -0,0 +1,5 @@ +nav: + - Home: + - README.md + - specs + - ... diff --git a/docs/docs/new-feature.md b/docs/docs/new-feature.md new file mode 100644 index 0000000000..ef016ee934 --- /dev/null +++ b/docs/docs/new-feature.md @@ -0,0 +1,139 @@ +# How to add a new feature proposal in consensus-specs + + + +- [A. Make it executable for linter checks](#a-make-it-executable-for-linter-checks) + - [1. Create a folder under `./specs/_features`](#1-create-a-folder-under-specs_features) + - [2. Choose the "previous fork" to extend: usually, use the scheduled or the latest mainnet fork version.](#2-choose-the-previous-fork-to-extend-usually-use-the-scheduled-or-the-latest-mainnet-fork-version) + - [3. Write down your proposed `beacon-chain.md` change](#3-write-down-your-proposed-beacon-chainmd-change) + - [4. Add `fork.md`](#4-add-forkmd) + - [5. Make it executable](#5-make-it-executable) +- [B: Make it executable for pytest and test generator](#b-make-it-executable-for-pytest-and-test-generator) + - [1. [Optional] Add `light-client/*` docs if you updated the content of `BeaconBlock`](#1-optional-add-light-client-docs-if-you-updated-the-content-of-beaconblock) + - [2. Add the mainnet and minimal presets and update the configs](#2-add-the-mainnet-and-minimal-presets-and-update-the-configs) + - [3. Update `context.py`](#3-update-contextpy) + - [4. Update `constants.py`](#4-update-constantspy) + - [5. Update `genesis.py`:](#5-update-genesispy) + - [6. Update CI configurations](#6-update-ci-configurations) +- [Others](#others) + - [Bonus](#bonus) + - [Need help?](#need-help) + + + +## A. Make it executable for linter checks + +### 1. Create a folder under `./specs/_features` + +For example, if it's an `EIP-9999` CL spec, you can create a +`./specs/_features/eip9999` folder. + +### 2. Choose the "previous fork" to extend: usually, use the scheduled or the latest mainnet fork version. + +For example, if the latest fork is Capella, use `./specs/capella` content as +your "previous fork". + +### 3. Write down your proposed `beacon-chain.md` change + +- Make a copy of the latest fork content and then edit it. +- Tips: + - The differences between "Constants", "Configurations", and "Presets": + - Constants: The constant that should never be changed. + - Configurations: The settings that we may change for different networks. + - Presets: The settings that we may change for testing. + - Readability and simplicity are more important than efficiency and + optimization. + - Use simple Python rather than the fancy Python dark magic. + +### 4. Add `fork.md` + +You can refer to the previous fork's `fork.md` file. + +### 5. Make it executable + +- Update Pyspec + [`constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/constants.py) + with the new feature name. +- Update helpers for + [`setup.py`](https://github.com/ethereum/consensus-specs/blob/dev/setup.py) + for building the spec: + - Update + [`pysetup/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/constants.py) + with the new feature name as Pyspec `constants.py` defined. + - Update + [`pysetup/spec_builders/__init__.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/spec_builders/__init__.py). + Implement a new `SpecBuilder` in + `pysetup/spec_builders/.py` with the new feature name. e.g., + `EIP9999SpecBuilder`. Append it to the `spec_builders` list. + - Update + [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py): + add the path of the new markdown files in `get_md_doc_paths` function if + needed. +- Update `PREVIOUS_FORK_OF` setting in both + [`test/helpers/constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/constants.py) + and + [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py). + - NOTE: since these two modules (the pyspec itself and the spec builder tool) + must be separate, the fork sequence setting has to be defined again. + +## B: Make it executable for pytest and test generator + +### 1. [Optional] Add `light-client/*` docs if you updated the content of `BeaconBlock` + +- You can refer to the previous fork's `light-client/*` file. +- Add the path of the new markdown files in + [`pysetup/md_doc_paths.py`](https://github.com/ethereum/consensus-specs/blob/dev/pysetup/md_doc_paths.py)'s + `get_md_doc_paths` function. + +### 2. Add the mainnet and minimal presets and update the configs + +- Add presets: `presets/mainnet/.yaml` and + `presets/minimal/.yaml` +- Update configs: `configs/mainnet.yaml` and `configs/minimal.yaml` + +### 3. Update [`context.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/context.py) + +- [Optional] Add `with__and_later` decorator for writing + pytest cases. e.g., `with_capella_and_later`. + +### 4. Update [`constants.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/constants.py) + +- Add `` to `ALL_PHASES` and `TESTGEN_FORKS` + +### 5. Update [`genesis.py`](https://github.com/ethereum/consensus-specs/blob/dev/tests/core/pyspec/eth2spec/test/helpers/genesis.py): + +We use `create_genesis_state` to create the default `state` in tests. + +- If the given feature changes `BeaconState` fields, you have to set the initial + values by adding: + +```python +def create_genesis_state(spec, validator_balances, activation_threshold): + ... + + if is_post_eip9999(spec): + state.new_field = value + + return state +``` + +- If the given feature changes `ExecutionPayload` fields, you have to set the + initial values by updating `get_sample_genesis_execution_payload_header` + helper. + +### 6. Update CI configurations + +- Update + [GitHub Actions config](https://github.com/ethereum/consensus-specs/blob/dev/.github/workflows/run-tests.yml) + - Update `pyspec-tests.strategy.matrix.version` list by adding new feature to + it + +## Others + +### Bonus + +- Add `validator.md` if honest validator behavior changes with the new feature. + +### Need help? + +You can tag spec elves for cleaning up your PR. 🧚 diff --git a/docs/docs/release.md b/docs/docs/release.md new file mode 100644 index 0000000000..765c69e25e --- /dev/null +++ b/docs/docs/release.md @@ -0,0 +1,102 @@ +# Release Procedure + + + +- [Introduction](#introduction) +- [Open a Release Pull Request](#open-a-release-pull-request) +- [Bump the Version](#bump-the-version) +- [Merge the Release Pull Request](#merge-the-release-pull-request) +- [Tag the Release](#tag-the-release) +- [Make an Announcement](#make-an-announcement) + + + +## Introduction + +This document describes the necessary steps to produce a consensus-specs +release. + +## Open a Release Pull Request + +> [!NOTE] +> Try to do this at least a few days prior to the release. + +First, create a PR which merges `dev` into `master`. + +> [!TIP] +> Click on the following link to draft a PR: +> +> - https://github.com/ethereum/consensus-specs/compare/dev...master?expand=1 + +Title this PR "Release \" (_e.g._, "Release v1.5.0-alpha.10"). + +In the PR's description, provide a list of items that must be done. At the +bottom, list unmerged PRs which are to be included in the release; this signals +to other maintainers and developers that they should review these PRs soon. + +```markdown +- [ ] testgen +- [ ] version bump +- [ ] #1234 +- [ ] #2345 +- [ ] #3456 +``` + +## Bump the Version + +Next, update the `VERSION.txt` file which contains the eth2spec version. + +> [!TIP] +> Click on the following link to open the GitHub editor for this file: +> +> - https://github.com/ethereum/consensus-specs/edit/dev/tests/core/pyspec/eth2spec/VERSION.txt + +Next, change the version to the appropriate value and click the "Commit +changes..." button. + +For the commit message, put "Bump version to \" (_e.g._, "Bump version +to 1.5.0-alpha.10"). + +Next, click the "Propose changes" button and proceed to make the PR. + +## Merge the Release Pull Request + +> [!IMPORTANT] +> Be sure to merge this using the "Create a merge commit" method. + +After all PRs have been merged/addressed, merge the release PR. + +## Tag the Release + +Next, tag the latest commit to master. This will trigger the +[automated release process](../../.github/workflows/release.yml). + +```bash +git clone git@github.com:ethereum/consensus-specs.git +cd consensus-specs +git tag +git push origin +``` + +Approximately 12 hours later, the releases for consensus-specs and +consensus-spec-tests will be available on GitHub. + +## Make an Announcement + +> [!IMPORTANT] +> In order to do this, you must be granted the appropriate access. + +Finally, make an announcement to the Eth R&D server on Discord. This should be +posted in the `#announcements` channel. This will notify client developers of +the new release and they will begin to incorporate the new reference tests into +their client. + +Use the following template for your announcement: + +```markdown +Consensus layer specs -- -- released! + +https://github.com/ethereum/consensus-specs/releases/tag/ + +Test vectors: https://github.com/ethereum/consensus-spec-tests/releases/tag/ +``` diff --git a/docs/light-client/.pages b/docs/light-client/.pages new file mode 100644 index 0000000000..a372a5d2e5 --- /dev/null +++ b/docs/light-client/.pages @@ -0,0 +1,5 @@ +nav: + - 'Index': index.md + - 'Altair': specs/altair/light-client/sync-protocol + - 'Capella': specs/capella/light-client/sync-protocol + - 'Deneb': specs/deneb/light-client/sync-protocol diff --git a/docs/light-client/index.md b/docs/light-client/index.md new file mode 100644 index 0000000000..32155b1852 --- /dev/null +++ b/docs/light-client/index.md @@ -0,0 +1 @@ +# Light client specifications diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000000..3849762488 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,34 @@ +/* Reference: https://zenn.dev/mebiusbox/articles/81d977a72cee01 */ + +[data-md-color-scheme=default] { + --md-default-fg-color--light: #222 !important; +} +[data-md-color-scheme=slate] { + --md-default-fg-color--light: #fefefe !important; + --md-typeset-a-color: #fc0 !important; +} + +.md-typeset pre { + color: #f8f8f2; +} +.md-typeset .highlighttable { + margin-left:-20px; + margin-right: -20px; + border-radius: 0; +} +.md-typeset .highlighttable > * { + --md-code-bg-color: #222 !important; + --md-code-fg-color: #fefefe !important; +} +.md-typeset .highlighttable .linenos .linenodiv pre span { + background-color: #222 !important; + color: #fefefe !important; +} +.md-typeset .highlighttable .md-clipboard:before, +.md-typeset .highlighttable .md-clipboard:after { + color: rgba(240,240,240,.8); +} +.md-typeset .highlighttable .md-clipboard:hover:before, +.md-typeset .highlighttable .md-clipboard:hover:after { + color: rgba(102,217,224,1); +} diff --git a/fork_choice/.pages b/fork_choice/.pages new file mode 100644 index 0000000000..a5e6ccc904 --- /dev/null +++ b/fork_choice/.pages @@ -0,0 +1,7 @@ +nav: + - ... + - Fork Choice -- Core: + - phase0: specs/phase0/fork-choice + - bellatrix: specs/bellatrix/fork-choice + - capella: specs/capella/fork-choice + - deneb: specs/deneb/fork-choice diff --git a/fork_choice/safe-block.md b/fork_choice/safe-block.md new file mode 100644 index 0000000000..aad8a60ed1 --- /dev/null +++ b/fork_choice/safe-block.md @@ -0,0 +1,45 @@ +# Fork Choice -- Safe Block + + + +- [Introduction](#introduction) +- [`get_safe_beacon_block_root`](#get_safe_beacon_block_root) +- [`get_safe_execution_block_hash`](#get_safe_execution_block_hash) + + + +## Introduction + +Under honest majority and certain network synchronicity assumptions there exists +a block that is safe from re-orgs. Normally this block is pretty close to the +head of canonical chain which makes it valuable to expose a safe block to users. + +This section describes an algorithm to find a safe block. + +## `get_safe_beacon_block_root` + +```python +def get_safe_beacon_block_root(store: Store) -> Root: + # Use most recent justified block as a stopgap + return store.justified_checkpoint.root +``` + +*Note*: Currently safe block algorithm simply returns +`store.justified_checkpoint.root` and is meant to be improved in the future. + +## `get_safe_execution_block_hash` + +```python +def get_safe_execution_block_hash(store: Store) -> Hash32: + safe_block_root = get_safe_beacon_block_root(store) + safe_block = store.blocks[safe_block_root] + + # Return Hash32() if no payload is yet justified + if compute_epoch_at_slot(safe_block.slot) >= BELLATRIX_FORK_EPOCH: + return safe_block.body.execution_payload.block_hash + else: + return Hash32() +``` + +*Note*: This helper uses beacon block container extended in +[Bellatrix](../specs/bellatrix/beacon-chain.md). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..3a9d76f8c1 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,40 @@ +site_name: Ethereum Consensus Specs +site_url: https://ethereum.github.io/consensus-specs/ +repo_name: ethereum/consensus-specs +theme: + name: material + palette: + - scheme: default + primary: black + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: black + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.tabs + - search +markdown_extensions: + - toc: + permalink: true + - pymdownx.superfences + - pymdownx.highlight: + use_pygments: true + noclasses: true + pygments_style: monokai + linenums: true + anchor_linenums: true + - mdx_truly_sane_lists: + nested_indent: 4 +plugins: + - search + - awesome-pages +extra_css: + - stylesheets/extra.css +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/ethereum/consensus-specs diff --git a/linter.ini b/mypy.ini similarity index 64% rename from linter.ini rename to mypy.ini index 6575642f17..6dd051f228 100644 --- a/linter.ini +++ b/mypy.ini @@ -1,13 +1,11 @@ -[flake8] -ignore = E252,W504,W503 -max-line-length = 120 - [mypy] disallow_incomplete_defs = True disallow_untyped_defs = True - warn_unused_ignores = True warn_unused_configs = True warn_redundant_casts = True - ignore_missing_imports = True + +# TODO(jtraglia): Remove these bandaids +no_implicit_optional = False +disable_error_code = empty-body \ No newline at end of file diff --git a/presets/README.md b/presets/README.md index 3a438cb2ca..85212aa8c6 100644 --- a/presets/README.md +++ b/presets/README.md @@ -1,25 +1,30 @@ # Presets -Presets are more extensive than runtime configurations, and generally only applicable during compile-time. -Each preset is defined as a directory, with YAML files per fork. +Presets are more extensive than runtime configurations, and generally only +applicable during compile-time. Each preset is defined as a directory, with YAML +files per fork. -Configurations can extend a preset by setting the `PRESET_BASE` variable. -An implementation may choose to only support 1 preset per build-target and should validate -the `PRESET_BASE` variable in the config matches the running build. +Configurations can extend a preset by setting the `PRESET_BASE` variable. An +implementation may choose to only support 1 preset per build-target and should +validate the `PRESET_BASE` variable in the config matches the running build. Standard presets: -- [`mainnet/`](./mainnet): Used in mainnet, mainnet-like testnets (e.g. Prater), and spec-testing -- [`minimal/`](./minimal): Used in low-resource local dev testnets, and spec-testing -Client implementers may opt to support additional presets, e.g. for extra large beacon states for benchmarking. -See [`/configs/`](../configs) for run-time configuration, e.g. to configure a new testnet. +- [`mainnet/`](./mainnet): Used in mainnet, mainnet-like testnets (e.g. Hoodi), + and spec-testing +- [`minimal/`](./minimal): Used in low-resource local dev testnets, and + spec-testing + +Client implementers may opt to support additional presets, e.g. for extra large +beacon states for benchmarking. See [`/configs/`](../configs) for run-time +configuration, e.g. to configure a new testnet. ## Forking -Like the [config forking](../configs/README.md#forking), -the preset extends with every fork, instead of overwriting previous values. -An implementation can ignore preset files as a whole for future forks, -and can thus implement stricter compile-time warnings on unrecognized or missing variables in current forks. +Like the [config forking](../configs/README.md#forking), the preset extends with +every fork, instead of overwriting previous values. An implementation can ignore +preset files as a whole for future forks, and can thus implement stricter +compile-time warnings on unrecognized or missing variables in current forks. ## Format diff --git a/presets/mainnet/altair.yaml b/presets/mainnet/altair.yaml index 9a17b78032..0d8c7a63f9 100644 --- a/presets/mainnet/altair.yaml +++ b/presets/mainnet/altair.yaml @@ -1,6 +1,6 @@ # Mainnet preset - Altair -# Updated penalty values +# Rewards and penalties # --------------------------------------------------------------- # 3 * 2**24 (= 50,331,648) INACTIVITY_PENALTY_QUOTIENT_ALTAIR: 50331648 @@ -9,16 +9,16 @@ MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: 64 # 2 PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 - # Sync committee # --------------------------------------------------------------- -# 2**9 (= 512) +# 2**9 (= 512) participants SYNC_COMMITTEE_SIZE: 512 -# 2**8 (= 256) +# 2**8 (= 256) epochs EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 256 - # Sync protocol # --------------------------------------------------------------- -# 1 +# 2**0 (= 1) participants MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 32 * 256) epochs +UPDATE_TIMEOUT: 8192 diff --git a/presets/mainnet/bellatrix.yaml b/presets/mainnet/bellatrix.yaml new file mode 100644 index 0000000000..268a0e75f4 --- /dev/null +++ b/presets/mainnet/bellatrix.yaml @@ -0,0 +1,21 @@ +# Mainnet preset - Bellatrix + +# Rewards and penalties +# --------------------------------------------------------------- +# 2**24 (= 16,777,216) +INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 +# 2**5 (= 32) +MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 +# 3 +PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 + +# Execution +# --------------------------------------------------------------- +# 2**30 (= 1,073,741,824) bytes +MAX_BYTES_PER_TRANSACTION: 1073741824 +# 2**20 (= 1,048,576) transactions +MAX_TRANSACTIONS_PER_PAYLOAD: 1048576 +# 2**8 (= 256) bytes +BYTES_PER_LOGS_BLOOM: 256 +# 2**5 (= 32) bytes +MAX_EXTRA_DATA_BYTES: 32 diff --git a/presets/mainnet/capella.yaml b/presets/mainnet/capella.yaml new file mode 100644 index 0000000000..3cd83759d9 --- /dev/null +++ b/presets/mainnet/capella.yaml @@ -0,0 +1,16 @@ +# Mainnet preset - Capella + +# Max operations per block +# --------------------------------------------------------------- +# 2**4 (= 16) credential changes +MAX_BLS_TO_EXECUTION_CHANGES: 16 + +# Execution +# --------------------------------------------------------------- +# 2**4 (= 16) withdrawals +MAX_WITHDRAWALS_PER_PAYLOAD: 16 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**14 (= 16384) validators +MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16384 diff --git a/presets/mainnet/custody_game.yaml b/presets/mainnet/custody_game.yaml deleted file mode 100644 index 2930f9c783..0000000000 --- a/presets/mainnet/custody_game.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# Mainnet preset - Custody Game - -# Time parameters -# --------------------------------------------------------------- -# 2**1 (= 2) epochs, 12.8 minutes -RANDAO_PENALTY_EPOCHS: 2 -# 2**15 (= 32,768) epochs, ~146 days -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 32768 -# 2**14 (= 16,384) epochs ~73 days -EPOCHS_PER_CUSTODY_PERIOD: 16384 -# 2**11 (= 2,048) epochs, ~9 days -CUSTODY_PERIOD_TO_RANDAO_PADDING: 2048 -# 2**15 (= 32,768) epochs, ~146 days -MAX_CHUNK_CHALLENGE_DELAY: 32768 - - -# Max operations -# --------------------------------------------------------------- -# 2**8 (= 256) -MAX_CUSTODY_KEY_REVEALS: 256 -# 2**0 (= 1) -MAX_EARLY_DERIVED_SECRET_REVEALS: 1 -# 2**2 (= 2) -MAX_CUSTODY_CHUNK_CHALLENGES: 4 -# 2** 4 (= 16) -MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 16 -# 2**0 (= 1) -MAX_CUSTODY_SLASHINGS: 1 - - -# Reward and penalty quotients -# --------------------------------------------------------------- -EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 -# 2**8 (= 256) -MINOR_REWARD_QUOTIENT: 256 diff --git a/presets/mainnet/deneb.yaml b/presets/mainnet/deneb.yaml new file mode 100644 index 0000000000..e5547612ea --- /dev/null +++ b/presets/mainnet/deneb.yaml @@ -0,0 +1,16 @@ +# Mainnet preset - Deneb + +# Execution +# --------------------------------------------------------------- +# 2**12 (= 4,096) blob commitments +MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096 + +# Networking +# --------------------------------------------------------------- +# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 4 + 1 + 12 = 17) +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 17 + +# Blob +# --------------------------------------------------------------- +# 2**12 (= 4,096) field elements +FIELD_ELEMENTS_PER_BLOB: 4096 diff --git a/presets/mainnet/eip6800.yaml b/presets/mainnet/eip6800.yaml new file mode 100644 index 0000000000..55e649c758 --- /dev/null +++ b/presets/mainnet/eip6800.yaml @@ -0,0 +1,12 @@ +# Mainnet preset - EIP6800 + +# Misc +# --------------------------------------------------------------- +# 2**16 (= 65,536) stems +MAX_STEMS: 65536 +# 33 commitments +MAX_COMMITMENTS_PER_STEM: 33 +# 2**8 (= 256) +VERKLE_WIDTH: 256 +# 2**3 (= 8) +IPA_PROOF_DEPTH: 8 diff --git a/presets/mainnet/eip7441.yaml b/presets/mainnet/eip7441.yaml new file mode 100644 index 0000000000..13a3d8ea2a --- /dev/null +++ b/presets/mainnet/eip7441.yaml @@ -0,0 +1,16 @@ +# Mainnet preset - EIP7441 + +# Misc +# --------------------------------------------------------------- +# 2**2 (= 4) +CURDLEPROOFS_N_BLINDERS: 4 +# 2**14 (= 16,384) +CANDIDATE_TRACKERS_COUNT: 16384 +# 2**13 (= 8,192) +PROPOSER_TRACKERS_COUNT: 8192 +# 2**7 - CURDLEPROOFS_N_BLINDERS (= 124) +VALIDATORS_PER_SHUFFLE: 124 +# 2**15 (= 32,768) +MAX_SHUFFLE_PROOF_SIZE: 32768 +# 2**10 (= 1,024) +MAX_OPENING_PROOF_SIZE: 1024 diff --git a/presets/mainnet/eip7732.yaml b/presets/mainnet/eip7732.yaml new file mode 100644 index 0000000000..434eb01901 --- /dev/null +++ b/presets/mainnet/eip7732.yaml @@ -0,0 +1,10 @@ +# Mainnet preset - EIP7732 + +# Execution +# --------------------------------------------------------------- +# 2**9 (= 512) +PTC_SIZE: 512 +# 2**2 (= 4) +MAX_PAYLOAD_ATTESTATIONS: 4 +# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 8 + 1 + 12 = 21) +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 21 diff --git a/presets/mainnet/eip7805.yaml b/presets/mainnet/eip7805.yaml new file mode 100644 index 0000000000..b61c2d1fb9 --- /dev/null +++ b/presets/mainnet/eip7805.yaml @@ -0,0 +1,6 @@ +# Mainnet preset - EIP7805 + +# Inclusion list committee +# --------------------------------------------------------------- +# 2**4 (= 16) +INCLUSION_LIST_COMMITTEE_SIZE: 16 diff --git a/presets/mainnet/electra.yaml b/presets/mainnet/electra.yaml new file mode 100644 index 0000000000..55308d5b1c --- /dev/null +++ b/presets/mainnet/electra.yaml @@ -0,0 +1,50 @@ +# Mainnet preset - Electra + +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# Rewards and penalties +# --------------------------------------------------------------- +# 2**12 (= 4,096) +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +# 2**12 (= 4,096) +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# State list lengths +# --------------------------------------------------------------- +# 2**27 (= 134,217,728) pending deposits +PENDING_DEPOSITS_LIMIT: 134217728 +# 2**27 (= 134,217,728) pending partial withdrawals +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 +# 2**18 (= 262,144) pending consolidations +PENDING_CONSOLIDATIONS_LIMIT: 262144 + +# Max operations per block +# --------------------------------------------------------------- +# 2**0 (= 1) attester slashings +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# 2**3 (= 8) attestations +MAX_ATTESTATIONS_ELECTRA: 8 + +# Execution +# --------------------------------------------------------------- +# 2**13 (= 8,192) deposit requests +MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 +# 2**4 (= 16) withdrawal requests +MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 +# 2**1 (= 2) consolidation requests +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**3 (= 8) pending withdrawals +MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 8 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 (= 16) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/presets/mainnet/fulu.yaml b/presets/mainnet/fulu.yaml new file mode 100644 index 0000000000..82ab37ce25 --- /dev/null +++ b/presets/mainnet/fulu.yaml @@ -0,0 +1,13 @@ +# Mainnet preset - Fulu + +# Networking +# --------------------------------------------------------------- +# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments') (= 4) +KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4 + +# Blob +# --------------------------------------------------------------- +# 2**6 (= 64) +FIELD_ELEMENTS_PER_CELL: 64 +# 2**1 * FIELD_ELEMENTS_PER_BLOB (= 8,192) +FIELD_ELEMENTS_PER_EXT_BLOB: 8192 diff --git a/presets/mainnet/merge.yaml b/presets/mainnet/merge.yaml deleted file mode 100644 index 97f07c7f03..0000000000 --- a/presets/mainnet/merge.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Mainnet preset - The Merge - -# No presets here. diff --git a/presets/mainnet/phase0.yaml b/presets/mainnet/phase0.yaml index 89bb97d6a8..bb55e0cc66 100644 --- a/presets/mainnet/phase0.yaml +++ b/presets/mainnet/phase0.yaml @@ -2,11 +2,11 @@ # Misc # --------------------------------------------------------------- -# 2**6 (= 64) +# 2**6 (= 64) committees MAX_COMMITTEES_PER_SLOT: 64 -# 2**7 (= 128) +# 2**7 (= 128) committees TARGET_COMMITTEE_SIZE: 128 -# 2**11 (= 2,048) +# 2**11 (= 2,048) validators MAX_VALIDATORS_PER_COMMITTEE: 2048 # See issue 563 SHUFFLE_ROUND_COUNT: 90 @@ -17,13 +17,6 @@ HYSTERESIS_DOWNWARD_MULTIPLIER: 1 # 5 (plus 1.25) HYSTERESIS_UPWARD_MULTIPLIER: 5 - -# Fork Choice -# --------------------------------------------------------------- -# 2**3 (= 8) -SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8 - - # Gwei values # --------------------------------------------------------------- # 2**0 * 10**9 (= 1,000,000,000) Gwei @@ -33,38 +26,35 @@ MAX_EFFECTIVE_BALANCE: 32000000000 # 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000 - # Time parameters # --------------------------------------------------------------- -# 2**0 (= 1) slots 12 seconds +# 2**0 (= 1) slots, 12 seconds MIN_ATTESTATION_INCLUSION_DELAY: 1 -# 2**5 (= 32) slots 6.4 minutes +# 2**5 (= 32) slots, 6.4 minutes SLOTS_PER_EPOCH: 32 -# 2**0 (= 1) epochs 6.4 minutes +# 2**0 (= 1) epochs, 6.4 minutes MIN_SEED_LOOKAHEAD: 1 -# 2**2 (= 4) epochs 25.6 minutes +# 2**2 (= 4) epochs, 25.6 minutes MAX_SEED_LOOKAHEAD: 4 -# 2**6 (= 64) epochs ~6.8 hours +# 2**6 (= 64) epochs, ~6.8 hours EPOCHS_PER_ETH1_VOTING_PERIOD: 64 -# 2**13 (= 8,192) slots ~27 hours +# 2**13 (= 8,192) slots, ~27 hours SLOTS_PER_HISTORICAL_ROOT: 8192 -# 2**2 (= 4) epochs 25.6 minutes +# 2**2 (= 4) epochs, 25.6 minutes MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 - # State list lengths # --------------------------------------------------------------- -# 2**16 (= 65,536) epochs ~0.8 years +# 2**16 (= 65,536) epochs, ~0.8 years EPOCHS_PER_HISTORICAL_VECTOR: 65536 -# 2**13 (= 8,192) epochs ~36 days +# 2**13 (= 8,192) epochs, ~36 days EPOCHS_PER_SLASHINGS_VECTOR: 8192 -# 2**24 (= 16,777,216) historical roots, ~26,131 years +# 2**24 (= 16,777,216) historical roots, ~52,262 years HISTORICAL_ROOTS_LIMIT: 16777216 # 2**40 (= 1,099,511,627,776) validator spots VALIDATOR_REGISTRY_LIMIT: 1099511627776 - -# Reward and penalty quotients +# Rewards and penalties # --------------------------------------------------------------- # 2**6 (= 64) BASE_REWARD_FACTOR: 64 @@ -74,21 +64,20 @@ WHISTLEBLOWER_REWARD_QUOTIENT: 512 PROPOSER_REWARD_QUOTIENT: 8 # 2**26 (= 67,108,864) INACTIVITY_PENALTY_QUOTIENT: 67108864 -# 2**7 (= 128) (lower safety margin at Phase 0 genesis) +# 2**7 (= 128) (lower safety margin at Phase0 genesis) MIN_SLASHING_PENALTY_QUOTIENT: 128 -# 1 (lower safety margin at Phase 0 genesis) +# 1 (lower safety margin at Phase0 genesis) PROPORTIONAL_SLASHING_MULTIPLIER: 1 - # Max operations per block # --------------------------------------------------------------- -# 2**4 (= 16) +# 2**4 (= 16) proposer slashings MAX_PROPOSER_SLASHINGS: 16 -# 2**1 (= 2) +# 2**1 (= 2) attester slashings MAX_ATTESTER_SLASHINGS: 2 -# 2**7 (= 128) +# 2**7 (= 128) attestations MAX_ATTESTATIONS: 128 -# 2**4 (= 16) +# 2**4 (= 16) deposits MAX_DEPOSITS: 16 -# 2**4 (= 16) +# 2**4 (= 16) voluntary exits MAX_VOLUNTARY_EXITS: 16 diff --git a/presets/mainnet/sharding.yaml b/presets/mainnet/sharding.yaml deleted file mode 100644 index 120a716c9a..0000000000 --- a/presets/mainnet/sharding.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# Mainnet preset - Sharding - -# Misc -# --------------------------------------------------------------- -# 2**10 (= 1,024) -MAX_SHARDS: 1024 -# 2**6 (= 64) -INITIAL_ACTIVE_SHARDS: 64 -# 2**3 (= 8) -SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT: 8 -# 2**4 (= 16) -MAX_SHARD_PROPOSER_SLASHINGS: 16 -# -MAX_SHARD_HEADERS_PER_SHARD: 4 -# 2**8 (= 256) -SHARD_STATE_MEMORY_SLOTS: 256 -# 2**40 (= 1,099,511,627,776) -BLOB_BUILDER_REGISTRY_LIMIT: 1099511627776 - -# Shard blob samples -# --------------------------------------------------------------- -# 2**11 (= 2,048) -MAX_SAMPLES_PER_BLOCK: 2048 -# 2**10 (= 1,1024) -TARGET_SAMPLES_PER_BLOCK: 1024 - -# Gwei values -# --------------------------------------------------------------- -# 2**33 (= 8,589,934,592) Gwei -MAX_SAMPLE_PRICE: 8589934592 -# 2**3 (= 8) Gwei -MIN_SAMPLE_PRICE: 8 diff --git a/presets/mainnet/trusted_setups/curdleproofs_crs.json b/presets/mainnet/trusted_setups/curdleproofs_crs.json new file mode 100644 index 0000000000..25c7ae0e2f --- /dev/null +++ b/presets/mainnet/trusted_setups/curdleproofs_crs.json @@ -0,0 +1,140 @@ +{ + "vec_G": [ + "0xa44aae199242e24a4d00b8e5c96e318793eeeb2e154423ca6dcac20043387323dea3216a69bc13e9a3507ff842da544d", + "0x8dff68d38281daa552c587d073e498d1ed311986967192cba052827b07f194a936809ea3de921511db45d15234754993", + "0xad0ff210542fc069d065b53b2cd4228a0f097facebe089a83b989fd3344c53e440ded5da26bc6115299d9d464e1a9f28", + "0xb638e703f852d2ac49595141f7688c52a95dcc0b00f82a8548b14d823ebffe8657557ed7dab6bee44b17d39d742f69aa", + "0x9377c7771e07a1a9b9368796ce1a1b93d560d7726afde02627b424ee1dcddb3761ed49f2e1ae5279dca050935bd4a6dd", + "0x8d1be282936392c0c61c94745cfb29da0f3334272c9b37fe43c8b869640eb1ea88c8aaf5ff797bd90daf3d6ebeb4efb3", + "0xb3b55847d3bcf98b587c4441b0703939b5984bac91b00aabb5f3a45b38445405b61127bc6ee9f6b4b9e88c7a29c3aaa3", + "0xafb61afb9f92c37ec21220c02edf14876d2a08eab8ad3c2bc1f3bfe5036abfd23a4a7216616fa1953e14340bf0acab37", + "0xa94133ee96e00465fe5423e0ea52404e0f624ee8cc9d69b4cf94e7d73635dfa2087cd2d2596ac4a75504aac5ef6a02d4", + "0xa4b93e670e7ee926ffb4ea4e07b87346e5d33c76520912f8a7937cdc3290a4c054586e175c39826b7fafbe777d14e4f4", + "0xa57540f7c906d9e70ef90580967562f8c06398ac2d77612045dce0ea6fbc2fedcfdbeb3f6ad3bb717e1295d9539ede63", + "0x8504de35cb15935580dab4ae521aede9a6764731a687d23ed213859545065fae9ba915f33f05c5417d91801d2e70098c", + "0x976674e04ccfe0530db2a3723a36760949857f56b77df698d6b8c88a1152ca4ee2f0dad4fac413a34e0aaef9684fb547", + "0xa7aee20d139d52043203c05ce128a7b17d0e37cfd92370aecc2a777339f03558bbe9cb4bae5c42557be50d6be381537c", + "0xaea7520f557b33f6dbbf244f3c7e5784ce727ff18396dc7c200818333b0e946c1bd8c2e464646ca7b43a857e09b23bc5", + "0xb15a797f9a56e5511f73a02cc6a94ca3e6b4a50e8166151cba087d1bc051486b1771054ab1a76cea9045c961b989dad3", + "0x90458a1852cc7581b8dbf82e7ce16407542b30ca8d7b13ba86707a57e25996545cf2dc2ce03af35c4465129f1441dc2c", + "0x8975b5a131cdcebb1a7716e766dd829acaf1bb49e245e765b968f67de81be679937485c84193eb466033fedff89f117f", + "0x86a8d7b004b32f4a00b520b35701cd64df833827e37ff5ccff01f1f9ed72cd6a95cc6de5313085aa0f16d537413cf3f8", + "0x881dbeff4ac65d1be0bb462480ecfe299003042c4190a5c68201981e08868e3f056f1e35a5117488db6633d567c9930e", + "0xa70b2ea517b3c51cd02f6b6677d926ac648fa605330be36c7a475b4dfacbdad035c02d1124350fb67a6e9eef9db858b8", + "0xaab1475f7a085a35253adf0a5c5c968f8a44e119e92f69ceff1fb46a62e161ac2c65358310a64128c8a87573646712f7", + "0x94cafc40ecbd04ec8976ae399b820c3a0717dee37b2a8338724042cb7465a18ea29d76068b82bff1bc2147776a7f44c1", + "0xb936bf0248d8df624185c9176d2edc9c6cbf7a4624c942f50011ae83ca2212ea2453c07cf8db96294bb010b18cfabc48", + "0xaf0a2894d762325816d6a35485612eaa173b4fc31ff112b6e20dbab722692f58230b17c038212098fed7998eb2fa23a4", + "0xa6caa65c5483318cb4e47fa186353c565517a03018a6eb04baf5aaa8844379b09ab998e568cfc2af976e44cd1cb15395", + "0x924c94856f92e5212215636fe3ccc5de9e952293be6fe2369250d6aec62d481977b7d2815d72ccca28d0be18918c8d63", + "0x91c764d72eb782e2957a39eca43107069d28dd2e33f39484c55201f685714269672c191ee42d27237bb9055612eca176", + "0x8af8de9f36eac06547468021c040e020b284f517b8a4ef99f0909962c2fed60a0c7955f9763258dc4839dbaafe8f9365", + "0x9864fc53cbf30454f8ce1d9094d70f4c974e841c7d69815d73eb1b5efa0b063b511cac62ded138e75a7a0440f6b332d4", + "0x83cbf72e944cc0bd81fa18eda658c9c668f3f17c06b1936d7646aef0d7f5d35004709dbb04a811cade784bb5a89f96ad", + "0x93c9e4b3a4f477723410f30134fe16184df889ef021aaafbd8c562929b90031fb22b1b4e806287703f12287fbb0e99af", + "0x99fb0257c487a9801d7af55179f8eba20d90e673f57791948a35caf7dbdc08ee6069d2d1b9751c7d1b831918bdceb7db", + "0xadc24c2c32ce6a7ae62fac5fcd87f5658071a01e86d18bd8366c50a6414caec5fcd067168b9359b2cdb07b70f7f21f11", + "0xaaf509c0687bab09c61f36202e81c21f8ad01276dee9c8035457fd1bf550afc2eacdaa93a9a7b53c60120ac07594261e", + "0xb30b3bfc59f53f15adaca94304eaf9df1c85ceb8f2f3831fc7472e9aab4ed62454655930ab7c924d744ae49919db7b9e", + "0x887e2559ea7fe8012bff545cf77b51f149f18ea6dfba90e60aa4bca704aec4f79e628b73fcb9f55d38071cbca322853d", + "0xb7fed350264b85c1c167f8763e6b4ef23bd65a1d611daa5e3ee8de5df399603f206f7b81cc061019bedc694243cc23b6", + "0xa83210d7c9c7858b047a866f672e6cdec26d73fc4815f28636cca898ff349970163819869500769208e74bc5a484845a", + "0xb08abbcda10f77e83b1f267c29ab192d4324d890834b2b44847a412a48cdb65e91a97c9a2fbc768034bceec55324a15f", + "0xad67e686bd0159f8ed762916211b5b0884a40df62518b8035edb5b7091dec05ec0a28ed147f3d6b2ee6aaf52d94bff31", + "0x8f324349647ccbaefb906d6790c314487b66a472ed1f8e02579b0658f2967185fe16227ad97802652941d23b5d2f67d1", + "0x96f41b8f53b08fe484f9df8880ed95a5d239ac541c9bb4ebbf7351c36ab191a3be33982c6bbdd099610cd0d96406aece", + "0xb1b79d46dd8a0dac9e9f555ce082cdf30f968263557dcccdeb54294f953f83f333c3af785c91e117de3ce70f79edcc66", + "0x81cf46a6962ba9d4a4f5bf2e63828b3f11bc9f35b2d126f80d9c547f53cec1539f303f22b91750660af46a17fcdf92a7", + "0xb7228f3497afba6c316d65eab6f3307bd88c01c49a881e649a67c89b88d5743ff74a8a7cb59e1b6e0f0ce32541c78dac", + "0x8fb76e5fc58d3c7e20805e8ae8a9d2b9359470c1a8c90017744abcee7e86f09b53838d34b56b6c95ed8f3bd4a4d06022", + "0x8ddfa7be366374d6fb55c6ab88c1a3b0b61edd87ef1069b492b38124e68a901da691702bef9ea3ad66019b59148d9285", + "0xa137a4405d6ea2b9b6a124b7bd073bc57a5b62f6b7dc70f6ee1da1d6103da22e19368cc6c804853998901fb9a5508723", + "0x86fc4a0481122463dea3fed7ba1671b41200edad47d1b16f90a0055e10ea46f1db64efe7c052aaded4e9ebcc00e811ee", + "0xa21a5cf22c6e5d8c95a0cf4b0a28be314534bee6bf1b342551edfff8a594664f75a95531d176f54bc8a1b3780dd56a00", + "0x9324572f9dbcbf9732eeb796089e902361e1d638fb83d4ad3bbd4b46bc169b23ce5e79ac066961ea6c096b5e219351eb", + "0xb048c3ac9604adbf3aad2ecf66485cb1fe90c0d767f0fc6f050a8d1fc3ea5620a88e46e32e30154f2fdf0990dffb350d", + "0x8a38fddb1a0a9de438aecf09cd0b2860a77491adfc2f47c485bd6e550d8f37e3accf0acd631743d855c830c20ffc4eae", + "0xab0ba1ec519d872ef5f768c940135f26bd8586ae530c48e45e2a25229e8a740ba17c93b3dd6761ba6c81a1929878866a", + "0x830b63ccc9713075ac618c64b870d8375d5bed64fd3701ec0caed47afe5ab3f567b3a1a981c302540ed0010c8aa48148", + "0xacb93bff4d4640d5c25291fc93d159360828481c6339baac50aa861def7088afa5909b735d1d4a12e3e2a23e303b6399", + "0xb398803308ffcd86e7b6df0ba952d95e7f417b73afed81e23eff09a4bd0a7ed1ab651beb206834d2c883ac4417f49032", + "0x9756aa1c5173a38e831f5cadae27fb0ee8ed850e2a846718f0f5419cc90beb9518dc31e4e8fefe4a9a40e54917fe120b", + "0xaeb4cbd4c463752a695e9c2d66188d015dd6220754130579c9bfa2d3b7c3c6c3fc7ec49fcf0009aba9bd5074dcb3f95e", + "0xa1e3c0889f0657ddda6816c1e4e1e43e457a5a388f60cea410c048023ac227db4e3e6d2a7f0222f499a89137605210e3", + "0xad96ad5fc3e43e68bc238e1267ccd81636e9e0ab035890185c4294705534a7bd25bb1c15a60786f35a829473d49781ea", + "0xa36db550a04a4676ac760a32e3734f5f17f8b7b912d9c560e9c148a706a2492d8b5a146b4188c66e3f4d5272777ddd58", + "0xaf47ec208a81bd7003cfccc1a1db8d2065f1c984f42abb430a903c9a643d1cc9fb981d55a01380bf7b74721275aaaa62", + "0xa979361a25434641c217ef285c4c81974bc2fe3a856781beab30a883b95d1b93de1fc21872723737cc93e028c5d3d147", + "0xb67ff15cc11b431c47fd1c136ea18c34224741c147eb584c6a3d253af826babe76dac4f7f7b847e7cd674730c3cf4956", + "0xa1638a24170fda842334a68c3a3939ac24b1de7b124d184244405b26419ccf7a5ceb090a4f1755bc07a5fa6637165255", + "0xb1ed9cf1516dca2a38b00694847809d8a172968b61a26d0615c5b2ab80363acda6a9af632fed703299d964a3736a7103", + "0x99319462b880885aa5db0070f151e205bf8288bf993d434fc99081bffdc1528265d5e252e2666d0947fdeafa48625513", + "0x8f5707ce471989512e497385171f9a5f462b0e987ffd8a696c602248155e9639b9597bbdd8b6cbd6685975136b52a40c", + "0x87465b2c5dd27e13a0892c30e7e2ff6819489db9b53487265a23fe764b6b4eca3b2338de672e6ea4ab3f8736c9feef56", + "0x89ddb3632add71b62e324fa6265600e809b29e4904d68c5fefd59a36f66cbd3741e03245aa4f015521d946e777d0c195", + "0xa270e76ffa82fad0a4408aa2e45235dbbd18304eb470e51411ae4ddd16b142666bfe37d9510eea9e69ed04e799788e0c", + "0x8983d57179a62eb563d3f7453672a5940b958a27df321bde6589056c1ea542c419e4116765a457c9b529b1014c3b3f68", + "0xab405480f4d5001e4c43b52f095896a3c8e394bff02c14f57facbe539c04210b4b589903bd94d0ca58b78e8c82745a22", + "0x82377e25d1f00987908d21ee2620a6653af77c72e038bb394c72d0b1d9b9a4930c6a2bb06ca091b8c4c19e62830268d6", + "0xab94d4848d372c00e205c64a6c7386a4078cb1860989c99e0313776d0518b056f6608ea3b4d12f50e0a8678dbfa0c73c", + "0x977ff883fc1217d4ef5220c74e06c3ce002cb691f191a1e31f46082fa2400236a5879d5dd4bd1d2421b991bb394c5e17", + "0x95bac7596af12ba4c11226ecd0ed0828c98eb60c8f142477872b401e2d7af5f3b04204508cf40a88f29d2235125a1b65", + "0x813e6c95f967f1371d0df1144bf73993947a6cd98e31f127db9239d69a8e97c1a41394890a2a2be85240c9b36ec74906", + "0xb44194edd26a519267d4ca212540bbe114976f28be9082c77a308c1731159c8b0fabb25b590dc445053585df0e555797", + "0xb7bf875591b4c4859154bbb9081fcb82b28fe87121fb866b598a5baad601478acbac0cb13d0cd14402368cee767b4231", + "0xa7bce1268dd1ba7d2e3e24e9d3fd44d0f7664e658dc27e9bee4aff75d76ea920bc34f25d14fe96a02c96cbb6692b544c", + "0x973194c2280380f42070200c9c729b3f651336379da482c5229ad321f29423bc6d1ccc1a5ced594806ce73b3ce437d12", + "0x978b88b3a66934790fba6bd2fec74410e18fab319b6c8a828dc32c3c8ffc23014e26f6c42835b58440bad6201ba790a2", + "0x8445283a55cd40ac99a710e4ebeca19b4042f89a9dbc0cb22cf62b6312edc7a4d4366efb169e1c0de8bacb8a1f2ff2ca", + "0x85bfaa05173114a0f3a2276671846db99a8f858b279a868e236cd9d974f69171501198cfcdec3dca093e5439a88199be", + "0xa3aab6d03e5c0cdd38096d7c97935913dbd24953e49eee341603ed434a067e1ac2270e6b74b45737ae1e79e9c248f15c", + "0xaf36fb1566ffeb6f0673640853b6c576330bb32751454b83835f0f26f50cd5d5ebb6658f6b1e9eeb9dcdb879745c9c7d", + "0xb216eb3d9d28c1ba93a57e82cc03469a9f40156266fcc96134a66da8a61aff3b78b783221fda5b23526fed2f91345418", + "0xb74637cfe60f5e7c116ab4be75bcfdfb08ba29ecc7b2363f547a2236bc170346388dd9fbaa1670ce1e45d4c96069717b", + "0x823a3cc16cfae5317b293fe905b8af7d7d2733c24f96cc66522aff2a376b5340dbcca8429f4082edb562da157c051c80", + "0xadf3b83761df2ca910900775e5d65e29bfd274cbb0cdd9614115aceaaa019b0e38a3e3b11777fff99d2b3b8c22de490c", + "0x8ef121f237356ed3dce22ec6e6b8a8085b71db20974483242d1280c18c51ba4f4438200cb4137e25f447e1a713f8894b", + "0xaec4690276f929c9cd2fedef923e1d2324a6b5f273f5c938b9e971b93d0762f181013e2cef334bf3ba70f1795fafcf23", + "0x91099cdfbe5ec822474b397366cba936c997bbe17169334bf94730c689b1e27943793f96e4825e0d96df577af77ad06f", + "0x94ac37115fd458fb690177ac88e6fc7f11bafb231fdc20e2995fddab695494a4bc86b1fcf53f7259843749f55ae40b92", + "0x832d99b9e3f910e8e19bee53dcf1ae0fcd7713e642cfebbdd891c59325bc50894a812ff53edbfbb38aca8cc5d97aea06", + "0x96373b775b1eafe66113b1bddad0e4ae9ba26f2c32393a29a2fa3660979eac480748d05deda7a68cf44c64fa38c7a03d", + "0xa0f960d2e4c4a6b75ded6207b686d3e943b675f5eaed6820d676889bd0625554753db4de8bc8d0c3cad475ee411e39b5", + "0x97d86db51837301ebb10e4867a8d71ed6f82b152e6b9d4256d15e0807d7e461dbfceeeabfc2ab9d5bb5789f3d9c30779", + "0x892bb178f0f2bdd2f6a027ba426396e610cd0803f6a1365ef6caf5508ddc5349f30f363e15cf19b2e700374b6d871830", + "0xa1271b15e75da127dbb44e2765c879ec037479edcfe52a3b7b607c114509e03a057a6d685223c3f4a0fd9e734469378a", + "0x8863d29a686a040514661be853c4cbdc28cbe7fe8c401aad01644f0a892ee4c4005148e40c2fdce642e690be9d8eef2f", + "0xb567760e8dbf7a61ba5a77d4b07c4a879b580863894f3c4fd9d652cf1ca53b9a0aebd6d8f559c5665fdf5cab5b9242c9", + "0x99bb4f6d41b33039c9443ba90203ca47eb6e79b126ec3e92e61495833d59c8464002cedc74bc33795d5a5e5d4772610d", + "0x94cf97bf6f28e38b2e6f4cbc58a6fbe1f031ecd8a9cc66b62835698ea88e9fe6419a80b57ffa19bf77dc048e39c11f41", + "0x8dc24197a96bbed35f779bd64cf9104975b68f310b82c2f03a587b522102cfecf061383108d7628e8b46359c06f41726", + "0x86ed177c05f473eb8bad7f79238d911c11cc3c7378e24dd70aa83659888f4915f9e13e3563617d369e8217e1ba77c01f", + "0x82b7176c8a6609cc286bb8f3f8d72a707aae630cb05510cba5a5cba711acd472d60beb2a413e45aef8265026d79fe576", + "0x875085a20d7390d92385ff1e4c112195198f7763cebde5d13ffac243f0a96be7a2a57ab9ec105f99187bd13e00cbf2f9", + "0xb14d2a2395677a2beb3b90bda389c67a7a4a8361ce353c8710a13aa59c08d2aea8d0197beb0db31b0e92fbde16bb9606", + "0xb7f222ee1e25115ece1b098b1c0261b326dfc454380d07e99bf498bbd8aafb209da4b5ff64c6a481cdcafc5d205de296", + "0x8bc66bbfb988913fd3b1c56d36ae3eb06b6219c8c278bdc8422d07e01e48e44239eca14255a43e1038f80322b2969156", + "0x906d257ada831ab1db57a7511d10d33c43f84947a2cbb8e9401010c9de542edaaa39d2ce329c33fe1a99c0bd03779acf", + "0x80373467a36d5e99aafde2875dc9caf6b1242bb4a285c6879f11d30ec4eaedea54327237eb02cf221d660ead62875948", + "0x9081a5170a70333cd9d6bd638772c2012e9c095800d3cdaf77a7ca98a1413c109686b42b9fef681250eb93b715702d1d", + "0x899427b7eca7c24e0760a6928f688ce91f7bc725b70c456c1ad7995effaac3edae2b41067e39cf8e2310a7201a4af55b", + "0x8d5ea173aa180ed6940d9577898271a21faaddfaf5afbc46c66ac29039ab35946952217545f5e7b816873e97df6e294e", + "0xa8af63310ce64f772410f18f29d60f9f1c5c49a858ed1971089673c1e0c8d85c8235617ea8bd919e542b238a63b1be07", + "0xad591bb5842e0d6132c573ab747d391a698332637452bdd262b0a6ea2ca29b346c7405348593228769459f5e1f156a07", + "0xb38395b34871fbc0c3a4d5e50c7e62a08ee58d2e19051ce269d2a56615f1f679e7eefe47e99ebe1e53a9bae9013c9de7", + "0x87affdb63f3d5bd9f4e95da4dac365ba3f853be767b5c09c4fbc24162744566ab20544a61657374e4122f36a2cfcc8c2", + "0x80cd960856a511cf957bf5bd4a4c3c6bc65c0fb5e785dc51560aa34ce56ddec6838f73e6bf257cfd273e05c7d719c098" + ], + "vec_H": [ + "0x8a135f527bcc420b9a4dae226c237012346c2356abbf20b60c26eb513ff0745153ff20dd94286d96fe679b1a22cbff5d", + "0xa5c64c216db68e10b32ee5c8fd29b1a3dce6238273ec141ca7d8d8dcbdf7b992c4ddf576633cd4f828244527e05e3461", + "0xab0a28fa68ad7d91c40b49e277e25ebdef5b689dbeae3be297161e44df940c02d2594e5d76b6be1547780d8ffc3cf9de", + "0x8532adc9d2fac12f65261fd17a57f231f7246feb60babc9c7beaeb628c0e1ad207e17252d736a7965542c3d7ebeb7fc2" + ], + "H": "0xaeb2d25680cbf2be736d999a01d73472e2779229a8ee2a8701b5cea2a93898fdf2150d467247f23a7761f650d38bdf6f", + "G_t": "0xa4e53147e355879fdb62f185ab7b8569925f356503a2ea67d4a13380f2a1bb82be57112893584834f1965cc8a4061d2f", + "G_u": "0xa693bce513d30e072ef71b7dfd03966cba8b11b0af9dbc0585b92514175772a81d083d7ff48e0adf3e3bee88823db240", + "G_sum": "0xa0181ccd048b494d5b35463e180408dc9c3325573f8639bf6bcd9447accfc093336158a0859fe3b3021ad141936da977", + "H_sum": "0xa6dbebe99ca5ddf836d4d1fe64479de04d8370dea2c36c3409b83706d58ec58150eba667d1d60471299b494162fcb6c1" +} + diff --git a/presets/mainnet/trusted_setups/trusted_setup_4096.json b/presets/mainnet/trusted_setups/trusted_setup_4096.json new file mode 100644 index 0000000000..6793490e2e --- /dev/null +++ b/presets/mainnet/trusted_setups/trusted_setup_4096.json @@ -0,0 +1,8265 @@ +{ + "g1_monomial": [ + "0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "0xad3eb50121139aa34db1d545093ac9374ab7bca2c0f3bf28e27c8dcd8fc7cb42d25926fc0c97b336e9f0fb35e5a04c81", + "0x8029c8ce0d2dce761a7f29c2df2290850c85bdfaec2955626d7acc8864aeb01fe16c9e156863dc63b6c22553910e27c1", + "0xb1386c995d3101d10639e49b9e5d39b9a280dcf0f135c2e6c6928bb3ab8309a9da7178f33925768c324f11c3762cfdd5", + "0x9596d929610e6d2ed3502b1bb0f1ea010f6b6605c95d4859f5e53e09fa68dc71dfd5874905447b5ec6cd156a76d6b6e8", + "0x851e3c3d4b5b7cdbba25d72abf9812cf3d7c5a9dbdec42b6635e2add706cbeea18f985afe5247459f6c908620322f434", + "0xb10f4cf8ec6e02491bbe6d9084d88c16306fdaf399fef3cd1453f58a4f7633f80dc60b100f9236c3103eaf727468374f", + "0xade11ec630127e04d17e70db0237d55f2ff2a2094881a483797e8cddb98b622245e1f608e5dcd1172b9870e733b4a32f", + "0xaf58c8a2f58f904ce20db81005331bf2d251e227e7d1bef575d691bdca842e6233eb2e26c2e116a61a78594772b38d25", + "0xb3c1313c31ec82da5a7a09e9cf6656ca598c243345fe8d4828e520ade91787ffb8b9867db789b34ad67cef47b26ff86d", + "0xa8ed8a235355948e0b04be080b7b3e145293accefb4704d1da9050796b2f6870516c1ebf77ae6a65359edcfd016c0f36", + "0x80e792d5ba24b8058f6d7291a2ec5cb68aab1e16e96d793128e86815631baf42c56b6205c19e25ce9727bd1fd6f9defb", + "0x816288c5d726b094e3fdf95cb8882f442c4d9d1101b92c7938a7dfd49bc50636d73ea1b05f75eb731c908c8fd8dee717", + "0xae009128d128ba2e1519bfa7a0c01ed494a7d461c3aba60f8a301701fed61fe4e31d6c79ce189542ae51df91e73ce1b3", + "0x96a866d60a9007d05825c332476a83e869e15b11d7257172a67690ea9bd3efea44bf9c8d42191454eb04fcf110b16396", + "0x8b250a2a06419adb9b611e89f7f8f2990aa301949b533ad3bf17c4a61ab5f5be0b1d5e2b571864d13f1bb75805c7795d", + "0x8450f49facf2e620fa45ee90e1801178842d927a2a25fc6ed7ba99a4eec7ae40eebfee41028eaa84f107f4a777694976", + "0x91049080cf659c0985a22d1366e59191bb89663f922e8168b9b7d85c8a73d74a6d9dceefd855d3d858b493670c750581", + "0xa1e167aeb2008087f3195926f1985c0a459d6ec57237255b1473a96de4e2c1cf766127c862c7dc853a6909e67cb06cf7", + "0xb667c0d4e26e20698b07567358625d5f003839c92de8088e12dbd74a6f6a3156b4ea8d252c9ad62af5f6c4fec1cf6cc7", + "0x8e4b5e304c0b1b161ae3e4b68b5e3ac66c42acd7c1ee2458044f6527c508a93995e50894d72d57c1350f91afe72775ff", + "0x8c642640aa7915421cdc21fd639f88a42052b1cfa358ff7702e60793a92b7b5926dae15a0c8f8f59cd3013f01c159ba3", + "0xa356f35e713cfc283056bf539de54a21731e61efb4c47319f20de4a4b723d76a33b65f4a67d298b9ec5c2a1579418657", + "0x93ce204146ce95f484dc79c27919a16c9e3fc14a9111c6c63d44491158d5838117d20851cc3227a5e8ba6ccf79e77f39", + "0xb585664cbb9a84b52f89114e1cf0cf1171bea78a136dc1404ac88a11210b2debc3b7a55e702da93ff629095c134a295e", + "0xb6dfd444ec7fdceb14c6328f26ca12c3f9fc4327d8d8c68948e92e7e61262b82d833a65a9e3af6353ffa832b6da25705", + "0xb4d4b8eb9ecfffe3f0d48fb4149c7b31aec1da7041ec03bd0750c52a2a7cbc3a7cfbf09d5bfdc56e3860826a62d0bb91", + "0xa4e248e3d61db52da9683fef188579c470d65e2df9064726847b1599fc774049ffdc6ef2ae578d5ed7874f1298ecdf69", + "0xa68a0fffc2e37d3183feb01b42234c0f4e510f9dc29d09c571e6da00fecad9da224cd0f31550070148667e226c4ca413", + "0x86adda2ffecb77236c18005051f31f9657a0d50fef2a1175dfda32e74d5d53df825c10f289eb0ad39df0c64fc9bc7729", + "0x998266d5c9c3764ed97d66fa9ed176af043999652bae19f0657c8328629d30af453230e3681c5a38e2f01e389ed8d825", + "0xa05261554d3c620af0c914cf27ab98f5d3593c33ab313c198e0c40d6c72022eb5943778cd4f73e9fe8383392a7004976", + "0xad243fb3631bf90fedb9d679fd71fc0cf06bda028591ded2bd4c634ea7b3c2bd22eca2ab318fcdaa6c2cda1e63e1c57b", + "0x89b9859a04f903c95e97fb2951f01cc6418a2505eee0b5bc7266b4d33e01b69b9fe7dc56fa9ebb5856095be0925a422d", + "0xa68d118343a5bbfbbab95ff9bfe53aeb7fdbaf16db983e6f4456366df2aa01fbdb6ee9901cb102fc7d2bd099be2f1f3e", + "0xb49301f25d5a9dd2ec60ddb0b4b477291958487efea9e54dc0e4ef388f03b8bbadd13259d191f7a0b7513876767d8282", + "0x8b93df7fb4513f67749905fd43db78f7026589b704ebb9ea3255d0ad6415437799f40f02e07efccda1e6fd5e8cd0a721", + "0xad88769ace96455da37c3c9019a9f523c694643be3f6b37b1e9dcc5053d1fe8e463abebdb1b3ef2f2fb801528a01c47c", + "0x80f0eb5dcbfaaf421bf59a8b9bd5245c4823c94510093e23e0b0534647fb5525a25ea3aeea0a927a1ee20c057f2c9234", + "0xb10ad82ea6a5aeabe345d00eb17910d6942b6862f7f3773c7d321194e67c9cced0b3310425662606634dcd7f8b976c04", + "0x82f6fd91f87822f6cc977808eeac77889f4a32fb0d618e784b2331263d0ffa820b3f70b069d32e0319c9e033ab75d3b4", + "0x9436d3dc6b5e25b1f695f8c6c1c553dab312ccace4dac3afddc141d3506467cd50cb04a49ea96ea7f5a8a7b0fc65ef37", + "0x8e0a9491651d52be8ebf4315fbbb410272f9a74b965d33b79ff1b9e1be3be59e43d9566773560e43280549c348e48f01", + "0x8809137e5d3a22400d6e645a9bd84e21c492371736c7e62c51cef50fee3aa7f2405724367a83fd051ff702d971167f67", + "0xb536a24f31a346de7f9863fc351fa602158404d2f94747eebe43abf1f21bf8f95a64146c02a4bec27b503f546789a388", + "0xb5cdf5a04fc12a0e0ef7545830061dff7fd8abea46e48fbe6235109e6c36ee6bffcb9529e2f3d0d701cf58bbfb6a4197", + "0xab15377525753467d042b7931f66f862cbbb77464212c9aa72d4e5c04375ef55f619b3a446091c1ba1a3b5d9f05e538f", + "0x905a75b943ad017ff78ea6ddd1d28a45c7273ee1c2e5e3353685813793ead3370c09cabd903fcab9d8b1c6961372d486", + "0x8147df4324faddc02fb0896367a7647b719b6499a361aecfdd3a34296fa6768ad31c34f9e873fd1e683386c44651883e", + "0xac91d08570dd91f89d2e01dca67cdc83b640e20f073ea9f0734759c92182bb66c5d645f15ebd91ed705b66486ed2088d", + "0xac6295ef2513bbea7ef4cdcf37d280300c34e63c4b9704663d55891a61bf5c91b04cc1d202a3a0a7c4520c30edc277c7", + "0xb604be776a012095c0d4ebc77797dd8dec62a54c0559fb2185d7bac6b50d4e5fd471ac2d7f4523206d5d8178eabd9a87", + "0x80ead68def272ce3f57951145e71ed6dc26da98e5825ef439af577c0c5de766d4e39207f205d5d21db903d89f37bbb02", + "0x9950b4a830388c897158c7fe3921e2fe24beedc7c84e2024e8b92b9775f8f99593b54a86b8870ec5087734295ba06032", + "0xb89ba714adabf94e658a7d14ac8fc197376a416841c2a80e1a6dde4f438d5f747d1fb90b39e8ea435c59d6ecda13dea1", + "0xb0c78e7cc60bd05be46d48fbb0421a678c7f14b8d93730deb66fbe1647613b2c62b5075126d917047820c57fc3509cb9", + "0xa860c4acc5444e9ae987e8c93cb9a5f17d954d63c060cc616f724e26bc73d2c54cd36e0492d1fde173847278e55942ba", + "0x8fb8269c9d5c15428e8d45da1251e4c4a4b600d47da0caea29fef246854d8fb6acae86a8e6440d0c429d8dd9c2dfee0c", + "0x96c5d8eb6fd5c525b348ee4335d200139e437e4be83690af0f35b7f336a7cda8c6d2958647988b84da9f2dd7bbb7710b", + "0xa7f62141c4346cc14e9823dc38ac7d587b0427022afc1498d12ee2c43f6ac3a82167057e670dd524b74137f8c3ceb56d", + "0x956aac50d06b46a3e94397f163f593f5010d366aa2d816c2205c7d0f47f90cf0f36c169e964f9bcf698d49182d47d91f", + "0xb812899bcdc0e70d79ca729cb01104bf60e1357b9085a10f64f3ba9865d57e9abd0a505a502d4de07afb46f4d266be2f", + "0xabce02c7e1372e25d40944dc9ece2904a8f59c8854c5f2875fe63ace8ce37d97881f4f9ab4f7bad070ec8e0daee58d3f", + "0x8fb13c515b2d6abb4e14ed753fad5cc36c3631dfe21a23d0f603aad719423dd5423157eefcbd9a9c6074e155b79eb38d", + "0xa9ef67304dc297ab5af778cf8afa849eeac27db4b6978963e97b95ef7a8d3264d0d07775f728c298a2b6daed2ecf5053", + "0xa9b975520adb066e2ff2a4cde53284c23bc84261a22dc43b1634d99eff8e7892e46bb6e6da7319c9e72788aa9ea7a1ea", + "0xa6eaea4ab4206294474d9b956d9d3188d558a5633de2bd05df0d3bac03dbcbe4ed85406349c1d2e660b77c6da1f5bf8c", + "0xaf4a19f77290dddee762e1e0d4bc9945aacea3f75756ae46cd3e58a8f74d1b5db73e4834687946b0f39191e32f2fed0c", + "0xaafa6523f58f1a4cabc924c86d842816d606afeea21fa4b2b8b9573425810fdcc41c98888318e868f9c05e2be12178a3", + "0x8ef38fba0a3fa4ebe985239c8b759c22aaef0c57e6f39050a651c869487803b0d1e389c3d958fb5a7f37740f050ac69e", + "0xb07dfc9f85913c608ca7596a2e361f05e4853fad00e796fd492d247de6414892ce160f627669b1ba933b6ad726415d4e", + "0x94da679ad1d78b2bff5283c938f17b2a7d6e9cbcdf59d340e6dfb652951c7a9e852ac0590f99cfee9631b9410f6f00ea", + "0x98a907c9c021a5b034d3720197c160a82c4b7146cb73d48efeed99b9d0c6b831812cf80ac7e19e85a676a8cd3ead72de", + "0xadb746595466a12929019d0048cea33236b05c1229d2eba73b259a18a786f2bc3f05fc0598d8ce253cecb80bdf679aaf", + "0xa2fbac016996d68f9027a157b0a3f6a336144a798d6113adfcda3a5d05b62c31f108f112aa915906aef22b7f83b9228b", + "0x81841dea1904406d1b6fa49b4b3f7f6cb40b7646cf44d36c9fa07e3dee29f8e47324b40d8356ddf653109673c3374e9b", + "0xa3edbb8aac5e60c775775cbdb19067341b2e2530de48738e84c2c07151241ee31f0d8333bf20c2bc9dcb7b2e638a6b5e", + "0xb8aa6890e22964828787ce86460d3a32f12a655bb5c28de500f2fcf6b61e3334640ec6ba96029a4912af0d18df4b4139", + "0x8ca43169f04243ad0fdb0152de17c60d9e31ee0ab520970fccd98590e05508821a183b4b367967e60d53c2c826ec5dbd", + "0xb179fffd9df8c00486c5a8b9327d599f5a11745ef564f06e126849b06fe2f99273c81f65bc941efb0debaadfecbfec1c", + "0xacf068f1c2b1926279cc82750ce21b0d6b0bfd0406f0d8bbfa959bd83935932957c7f6b8de318315bf0b75f6ee41a0f2", + "0xb97831da260919c856e9f71a41687f5979bc16f8a53b1037285b4a2f9ce93af5cfe70bf0ad484744827fb55c847b58eb", + "0xaff50b0bd907383b0c241727af364fe084d021221bfb1b09fb6c1a7752eeba45d662493d590f1f182764b90b25f17906", + "0xaeeef044c14e3ad41e1235c9e816e1eb49087fd3abe877b89b3bade74459186126e160bb569bcd77779e701b19b5f71a", + "0x8483deb2b7001ca7c438fcdca8ca6aba96c9cbc4becfd9b16a6062705eae270011bcaedcae69bb54630d8c78129e57c7", + "0xaeee8d24be4ac0d9784c029e239fb5e64316ce29b88f47394cfaaa8bb966a72061bff72f99d02dc51c9705854686e77f", + "0x90ae09525a16bb2422169e15d6831c87968a14ebc0d1d27e11a759839c73c655b9d33ee5b12f275d6f440688146fbd2f", + "0xa3a41fc7fefef101422465e506bea7f3ff23c26fe35f5732b86f5f2471fb93b37ebc339f84c6be1e8d22abc812c2e212", + "0x86f4b5293e8aea4af1f1fb05dcf99714cb3aff1cfc849b1bb73524061c921c9da9ad92579a852e1889da29d952f02fe5", + "0x8932ef39d4050a1e9dc0fd8afeaf159472d71c5c27f458c69d2730836606ea56e19c8c4febf2535f930d3260e9bc7637", + "0x86307b9f3696bb21c20e4558e30310389e7367803c353d437e9b696039a0ff054d9a4953b75237ab1d1dd6f71118c189", + "0x96e57730e683ef5b550c91de18b19ac73879f3e26234297db68d28747ed0953beb0f3913cfb720c602720bf9330685d8", + "0xb04a19ee70123782e47b238abde55baf60ac0c66292a998af0d14afc8bbeb1134e557b94cd17a020084631c09a0d3c02", + "0x829abc8718be8139569fcb2c398962f38f4201114d30e2b2fb23566f8a27a5c380f5605cec543415202a12ed859e33f6", + "0xa0744fa488c8fa92a722c5fc4ef5a47dfe824eccd87d26c8bab9c174cbb151d44b1b29082c48652f03d3177e5ec86001", + "0x81d4035ae9fd28bdcd78b135cb54955d3b685a527319df6ee7e904b8e6d796f5f5a5f5035ee1de750c4cb6050e452b9e", + "0xb205e8c2ec24d7104fa0106c09ad34b5a912c1adef553fb718838dd627355993c2ec01055c11d00b2c75b68e9516d44b", + "0xb12d09da7968fa7394e449624fc7174d1d76c069ccb03e140d4d87a2d3f6d1f7b9cfc930f0c80becc673406ebe63f08e", + "0xb23752c158695da85048fdf38b395681cc0e8998630af8a9ed41efbda08c9964c2dc8ae6e53377264be4467d702c0de4", + "0xb0d84582fd73628d96b8c1ec96197697c41a963542451a2ade0890af0d33c7161d0f18e1a1ce2c168ca2dc1e9119d55e", + "0x8b877e618b469aa187632e410b125d2999d5738fd66d482000706b51fd904a0c7e7daa8c9b729fa33817bbc4154cba2a", + "0xb1cfc8a7551b601723b937d497d01dec3ee7614c2bf13d430b1058d5ebc1406045009ff02c2ac15bf8cf16f860193d1e", + "0xb6d9da84f97b21e13175bbb0b5cc8e79e88b470c87a3e115726c1bd98e0288526c58f3faaa8aa170ace0cd6a60852525", + "0xad2e773c2d527671ca5fab7085dde4da31cd35f45d4315dd95d8893ff5fb900494dca08eccfc1a2fc7bf7c7fd2fcab97", + "0x8d5a79b34aeb761d4a0c73f09f02e9548e6d382c33ee6887a759ab05762b490b8a549ef2933c7e3a46415c154c0221c0", + "0xb6f2cbe81bd0a7298403be392f8456bed30aed7ef30216959357698f789affd2942ae5fbaf3f48ecebeb7c273b20cb57", + "0xb5b6c45d99cea7ce6a1dc134aff4a8f630f299b42bd59592a7592345f8cd35bcbee944e61b0723de732fcad6e4425b63", + "0x8077d64dfcb2418974e956ea6dbf8a4c05b25d2a025333ad7e2a379f1976dc036771403383a51bfa3476c9c619ef8bef", + "0xad2e0a9d479c77a5fb73b3613a177fdaad50dcb50fed50e756ba18164c153af30b07fb2565e80ff7469f1b0338b7b5de", + "0x81017d1d80a6b6df4e99d0d7f85a8180b5523e8fa2ea2672fddff604933f8a113cab27fce098dcb454d7d1f7ed266e04", + "0x852355479d68e76c7febf6dfe2ef8e80d575c0d3bd52c983803592021cfa898c571c0b884412c21e66f0dbfe03167b53", + "0x98e1bf8ad48421467c93b9f72b47dded7c41b4fcd36ea55ca43ab24b0d0b876f5a731f422579b7167c7138fad2121266", + "0x803369314abd5422019ed4b0ef652b4dbe97ef5a87b0ea373eec9628b64a12120b2c3d4eb53db405131ff786d14c7ac6", + "0xadf2613fc34f73e1160975c140e925ed84d254e03cc3bc7fc1d19957b499c9ba9d9e4c1639981b594a7095c0a52c6757", + "0xa2f6a68efdff6e4173c00692abcfdfcdaf6f8b62369afad3dafaae4f2f38c4860780b4624d185e20e4f4498b75b5fe94", + "0x8b1658aa0e119fb8401d486ed08d60240d26a8623ef9788e3b45ad09ae31259395b021bd16be395139cbb7149714e764", + "0xa7dd8bf21121285e00672ee8bb84e0cb39b2496fb53a26e35dfbca7f2b04e9a9ff9db15f53fe63fcbeafeb2deeaf2ca4", + "0xb6d8d709e44bc18f3b41d69608edce60c02bcba48d3b7e2fd420842657f0665a7343246dea149a25e8f3416284abae66", + "0xaaf744ca5e9bcb63e3e2939b7a1e96e4a93c88c76bec0cf4294dd7db95cdd3f6a7d92196e352d08680e2328bc4592899", + "0x84434b015a7c398d35f1ec71fce455d62ba4ed4f62da042ec31bb2b4db47073314354cd50bc322297a1cfe35138bf490", + "0x8d70b3a3cd9d5dfefdacfa418c0b775a112a47ce538d33a560a519660009c3f141fd6221c18539129e9c0acdaceeeb80", + "0xb8c6903412a800ec78a4c15f31c24385a267b0c0ece32fd31bbbb557fd70c3b2d60d8fc0f90fbd70f43baa1928ea30ba", + "0x8e391dd445ea06cabb433f057853f8159511b2f9bef41aed9ccd14e0a6fcd912bbaebd38fd5fb736cfde0fa34b7a4874", + "0xa40cd988f70613df32babbd1bbc2f1b29ff1ab0147b01161555a81d56c9621657999bcdb1df38485f687afc51d5d0f23", + "0xb6a008b4426b3d7b28ae04eee4698fc8ef6a35d89008ef5394da39ce582ce1a45dcfae9a33b90f6fa4237f3667803873", + "0x8987280debfb175c3b44a2f152ea82548e4f680966f1fcbee9bf7d714e31bf8080c33f52705ef3aeee70544b22516aba", + "0xa78a51a2c11eea7680a5a0ae417a2981f8c69c396e06da621eadd7510a3664ade49d065617bec67b3de779548a4f4509", + "0xa4d9163f0a1bc048385e94d5e0bcafeee1b18f28eb23505623b9e8ef16f3df76408254dfbe790e45f2884198060d388d", + "0x83dcae2568a0c518793c0f6e38b42f9ceb50673d100b556a17ec8bd9faeec84afe50b8d72422c6b2356959667bb8e2de", + "0x874731941be4474b4576226e5906b5dee89fc9b56a9870dcc7289c1a7d494d345ba6aba31f7546a16f9963283c05f744", + "0x82c1cfab1f501189ac20147fc4631075dbf1abf9125b7d42fcb4f31cf73f3d6461b1bd08fdf6e45cc54bc08a7d5d51d1", + "0xb978228286f5d4a10ce027b6bea3021affcaa805340ca4b5192c69e8c56db59f48e4a14a284ec015f53baf97389f62b2", + "0xaf125f4fdccd1c1b64fdffecb5ec7cf8c7392bbe476e1b89a5b5329c5ba4a526e58c11e72ab9de8a38d60af648d75adc", + "0x8411a41ec14295acab0d36389013535a80dfff6e024bffeb32fb3070762f61256419e8c51b2ad6de9dbe4f1e8e286912", + "0x8ea67a91112a41f9c65515cd496f4b0cdefa1400fc06568eef000c9eae6dc250fb7622eb3f2deca10b37287cd96fa463", + "0x8da99b6c55c31dee6a49aabb54da249d348a31d4416201a10c45a3b04b11e99d4ae9813632f0ee36c523b5cca62f6f49", + "0x8b44656341e039e2bd83a19c3bb9a88f6209482e274f8cd4f8557b728e5948dd80b5745f621b96f4562928689314e8c2", + "0xa02d424a615ba0dce8ed91f477e79852215a3a39d025059826fa278e7eebef19824b2a2844f5b3865a0f471b609a23f5", + "0xa1f115cebc3fff3bcf233da27cef19eae791660f155d088003460f75567a550bef0722885010ddc384acdeac635939dc", + "0xb61a55ce9d143c17876776e064b58a10baf0ba13553c785c1e47f57b5f94c0cda8bc89d43d73386e57816c15b61a8ec8", + "0xb4073f47041e20a8e548c7fb00e07ba3b9056c34eb4ab63bb0e7b48f8e338e8b56a17611a1b5f4c03b352450b86f1d69", + "0xa7b1a07b213205b682fc5b6acb7e76fdf97b280c26621d8f3b76b7c1deb3511957da33a4e358c8e8f3d98b2a8855d67e", + "0xb797e67c2670fbd9844e8a68c585f404b035dc14bd4ec75c3f95f932c777f9db5d5f5df7629164af488fc1213035cc5f", + "0x99618200797b945f595794d6468e5c618649554ad9ba896330f1cc844090eb956ae9fc23132912f9047085c5f0c3bf7b", + "0x81194aa1319abf534cb3927af9adfb178a99d0e3e8c99ab1105f1d3b4fed40ec2971caf1d6647acb0c8d681eca53097b", + "0x80673f18e4978dbc226a6cd4b128a1259d9a7f833879c6e2fbe24d69fef2c3c23a51a4f3e8d88fa4533434bbb0723661", + "0x8125bf6c7dbb2fb63aaa3f53283559f172c788223674adbeb6d5bd17cfe888e6b87a79aec774917f20ce911c1f85f8e7", + "0x884bcdb1878b14fc38adc9fb8b4dd0b3afde404fbeb664f26ddfebc81736018551f23e75ce4cfe4865f610bcd454fbd7", + "0xaec65c8d4be8316e98aa54888af01bc6703a0c5d04b69756ff39a0a947b66817ec59d76afe9f61a25749b5e890f03e02", + "0xaa457aaa1b014a4c5a8992847a187a23321bb43452c98745987d038e3b04046102ae859b7a8e980eea978a39d76a88ef", + "0xa9832ee63b08e19123f719bfe2fe742125f32463efa966c7709a98ebfc65277670e9ea1fa2d2d78b96bdc7523b0c4c3e", + "0xa87b6b1b7858f96d55064274f29fbde56067064962cf3c3e2ba3110b22ea633bc037a74d23543ce3307a46208855d74f", + "0x897cbe4ab68a753020fec732dfcc052c7ed9905342b5a6fe0aa25c631f9ad9b659e0ee75d46f0df6507b6720675ee28c", + "0x97c3b5f0d54c1fc45e79445c3ff30458959e406a069f5bbf7979d684195b4fa0406b87c1c008f4075bc9e602ed863152", + "0x921e65d582ea9322ddfad1c855331c3cac81f53c700b96db5305a643c084eb6793094e07944bfd41dc02c3b3cf671530", + "0x8f23ef1aca02a260a3b65d25b110f28d3bafca44727448c8f2d03c5e77eda620c1721b06681bd816ee6027664d76352a", + "0x946a89b132ec0795aea9ff9dde7b77e7feafffe6e4a2f093042a7e6c71cd6ab87ce0ca914a1b5fabad4e1f96a795f163", + "0xa01e2de9db33df6511172123ad6f7c64074237471df646b32dd9aff8c15278e2723108e4facaedca97e9f49503f8c792", + "0x99dcdcde45b2ea3f15279936feede5f7d3b63ca4972f335b0559c2fa6f9faabd8127aa892a36deb114357ca906553ed8", + "0xa3f8af37bfcf66b04d1896a4bd5d343f4733d4c3305369ac7e75a08f20f2004c10c642d2c7577f4e5c4d1f2cd851ac3b", + "0xb7294d15a3d674a56099f97a1adc9e82c15e90832eaf1722df110fc2abc8634c51515e5ad8522015498a3753b1fa8c49", + "0xb4f27f5062ba7a04ea0048b3025b5e3d5b5d319a9e80310c808a5fb4e8e77b38c10a0f3172cb805cadbcc8bc66d36ec7", + "0xaefe5decee0ae2dc372cc6cf4217daf97c4c908d145f100f0daf1ccdfdf641c78432c2e473e7e4b77dcdf2d4c2bb05f0", + "0xacc84af7648a535ffd218c0cc95c8f7b092418c548815f1bafc286b1fe14f6ccb51b2044db3bff864d0bb70e88604084", + "0x84d8e3dac0df6a22beb03742e1d4af684f139f07e2ea0f7fb27fc2d7d4f1e89b5e89f71af32ff115ed5e6092133535f0", + "0x8ada001e1a03a823c4c056f636e77adc0f9dc08689d28de0d99e0feecab5db13abf37b41ec268dbdb42c75419a046c68", + "0x87dac6c798d1744dff81d8bc3e0e04f3c9bf260e811685ddb9a9a8d6eda73927439b344f9a818d2103fad633de5a4a17", + "0xad9929a7d8a7d5d5954e48281a87e5c84f67e19110d73296b9989a09c76767a57a8115629239ffb4d99dfdf9c52ef6d9", + "0x81ac7cbeef8ec35a5c3b61cc887080c29e6cd3e08af37e45830d17400dbacfb374dd07bf370b979828c3875b2027d5c6", + "0x97f92c9182953b7e10f7a1bbb6b5b5c40b8275eb5a6eec1e29874c4712814749aa8c409651380216e1ff01d7b8511041", + "0xa09794d0bbe7db013045d3fd857c1544fe6231d21afa3495fa300371f6301a3a0f4b8ea175b281503dd06078ff371ae4", + "0x839bb58d320aa08116dd387a57a2b9bd9efc89c4cdfd82d0e47a00cabe644631d09be5436bd485df3b61b75ddf81a3ef", + "0xb1cdaa344f783757e8b9c1f84421da3c5be4c69f019a8fd4c1aa5bf1a63e8970c99e35c22cf3b48a0e6738bc6ba7ce8d", + "0x92af68e3216c78998208fb24b5ba0e645d0d3f5e28222b805668d7e9cdd6c033d3b22fd6df4c2d745d7f910d133cd226", + "0x87640a4ea4e605e2204e5232b29a6c1c31152d83547eef14122cb76a0da52b8653801af48455a3ed713b9dcfee7b1ef1", + "0x8147e5bf0c8f4731155ca0517ef3fae5a32b4d5d2d98ed0007b23893d8dbb7f8a1199c50c1750c2fa7c9cebe594b1bb0", + "0xa76b4473c63c3ab6103c729afd2482822e4150f3155af39983b0ff0766c71cb622455ce6304e23853661eaa322219d18", + "0xb3e2f05ca551bc3adec0067e4034aaffd72e0b64ac18ae25452c996927976c6727966e26d213b032521889be2170800d", + "0xa8414cd14cb3be658e9e0004ce511ef7063439b1cbc3166a11de030613fde4b59caad4e91d426927863c55382afbf476", + "0xb2f0f8ab99f4d0ea785ac84fdbc00b20217b1df59b30b51d9d209d489d53b69dd5d82cdacc16fd1dd15c3a4001595f50", + "0x8b2025d5fd658c9bbed619f3e3f6ac8efe7aeff8aa9401bd66a7ceb0062c44b353608ca073f95be99204f0a913bb77eb", + "0x94a46bc5a87291b42024b2137e623c70115b9c6b196604106bfbfa20f3f56ac7779763f56b580190d3cb2f1c648cada1", + "0xaca9355545118d0769cacf69c4b23d6d68d229cd8f68f1bc0c847c05569c5af6bbbd8c4dceb637b4a6b3b5c83841bf5e", + "0xb0731992cab87c7116406b283a84707a34838bfa3284b0f6082dfabeaf41c5ac2b0ddc1b420547a1b0955aee92de2dc0", + "0xb671f77588c0f69f6830a5b28e7d07ed161b81fa9791bb3a24aae6638e3aa5e186df74978a82549c370c18ebee04d4f0", + "0xb5621ed841780f3e6681d880a76cf519cdd20d35197b112eeaa686764d57b5dfa78ffe1a294b6bc76b6e3949cd2a2369", + "0xafeba2524659d00caecf089645611553187a6ed7102050f6dd20f5a19bed08ac7065912d88371ee06242897d58d652a4", + "0xb78bfb83d44ced14a20135804aba3f00128c3ce1f302e95567ce4097b0d973414153fb305b9f156882a5a0554bf25973", + "0x98510aede95d26b1adf214053eae051ffaf24894e2fa37961a91d0ff5392dd09388196648d95b73e90bd88f2587cc4bf", + "0xb35c682d49c295946b9f120fbc47b95abd9ee86d294abb003a92139fb825b509209562575015856a270eb3eea86397a7", + "0xb9641bf685571dd9c478dd2033a1f1b11cd3a662b26502c78595863b8e536a189674a9a85f7a253453ebfd1b99fbd841", + "0xb2ad37036a59b1c9b8457972665720a6868422ed8157b6810a9c0783006103be34ab732d7aeb8629653edd18fd0f1717", + "0xaf0920cff05179a3896ea6ea322c39adf91ada5bc40fe3f6fb1b1b4e121e907c904bbaa8ca00468b3749f3da144d71f3", + "0x8e269672818ef1e2f9e0c8aa65c84442fcd9151d74bb8e870cee8c0e3fe24526e1a5388b430cef47b67f79b4e4056bcc", + "0xaa29a16fe00ea3d143b1032b1dd26b8ce638f37f95c085c7e777e8e2784bd724bd5c38b1583c61a6ec7c451dd78fd3fb", + "0x87452b7435911cc5f513b0c81b15aa04972ecbe3d7bbd0a5d676c96a8a311301c0e07fac925c53a350b46fbd3d4d0fc1", + "0x869a81c351096f47748e41566ae7b77a454b1cdfaa41d34a5742f80df38fbf5cbb08924b6fdff58e3b18f05c62bbbbb1", + "0x8b7bc1b0486300981147a40a449ada9a41afc06d735cce8bf0fab3ee94ba2e2ea57b1397e3cd31bc295352beb8334ef7", + "0x93e93fc41adb2df279d95654921b4c2edf0d293dab58d0afefb221f777349ef88d0985b3447e3b935954a81f1580a92c", + "0x970fa7cdca8324faf3e62348bb50d78f580b4f43f2e1c11bd8382d48d0074a3c55c6407203a0c9cb1c5f2163ba421ef4", + "0x924983929e608d27e4a36d4ed919297869e3c64de51aca794d32d6e90aea546bf898d98ceca28a0b2187734821b78504", + "0x8d395332529c703d943d68415d443332b5c1342ca9d9a59bfa8bd4ab63e93358c4b0dde6ce1f2e8ea9dc8f52ad7ebd95", + "0x80200dda853e588256599e7f905add5d5ee7c74272780317694fbae39318ae9be05d5bcd7b20cf460069743f3d4ef240", + "0xa287d51d6359c9ef7c7ac1b20e479ce7d0146dba5606397bd04b7a622cec642508d5b45d51b31de71f9763595b6ac88e", + "0xa320396c075175d6599225cf2e1de8c7cab549f6316c07feb0f6eaa21f06b2dd29ab14fbdf2af4543b4890ec0fd08a4d", + "0xb1e9fe230418d20368691058adcbbe30011bab3000422f0371015ff8bd09c60fb5fa85d18550d35b1c900977ca48f58b", + "0x9718fc26a51783b971744933f20490e9b5cd9162f86b84788c4c5217f5409e37b5a39d628b18e5b35a757acf67596321", + "0xa0cf81fdb161f4f1b419c5e4caa36d4bdca2325f0cd25b119a30178016f171bd6fb88403e4e3aec026c4089f180d540e", + "0x8ab1e36bd04625ee794ef04c4dcb8e004d61aceb2b62438377f49ad95dcf025ba25eb799280004941e555bf7172af6fe", + "0x9257b9e3d14d37fc7efae49b0c68d36eaac546035f4a2654d566b3ce1b2c4564cbb03dc8ec66efceb768559a8a507a18", + "0x945d1123b839637ab5154a1972c3c83a0ff34a3b1a3465de6ef0416b1950f649869a3ef88d7f1036648ee385265ce2df", + "0x81449639d708860fc0229c94f754f7262e8a3c7f67960ff12dfd15df95f57a9ffcee2013e81978b7703dd42bd5d0816f", + "0xa865481deaae5a690fd53892791e5fa729db283b75a525a11cdfee1ce17e8e7f0b449d25f20b3c1b43da128dbdf98a8b", + "0x98766812a65fcd25b853546e3bba618a3edc9fd61510e4f8ab60c038a7fa50d197abeec8776109df0f2119be9445ad00", + "0xb1b8dd5379d903dc41d74e999b1ab693607a0d2905692f4fb96adf08f738e5d31f9d00df28ccb8b5856145ca552c3e3c", + "0x99d20be7b511bec78a8ed03c207aa4aa9097ba39d85e18f1b8d52f65431ab7e9a773c7b9ac3e8d8b25458bc91bd00703", + "0xb1b7c3563fe8cb33c7d3e0b89d00bdd13e86452ff507c2e69db7b3af06f247f139155396e9b0278753310dc63940a10b", + "0xb3dc9c08451b1de7c9969b1e47574bffff50490f4a16c51e12390195d9e9c72f794790caf7b0a835d64e01fec995d3ac", + "0xaaaa4761a00022ede0809d7063d3532b7bfae90ff16f45e17a340ad4ebaa2fbac40728ccc5fbe36a67ab0e707566c5dc", + "0x8319a1903314eab01f5442d2aee6ae9c3f6edfda0d9a88b416d0f874d7d1d05d08bb482102f8ca70a4fa34836d0840c1", + "0x932949a6e9edfec344932a74d4f81eec3667ece1e8b8ca840ce07ffd4b5d6d8f01657c764d64ac1b9190f876b136490e", + "0x904db1568128487e312fe629dd8bb920cecafd3bb9cad8b63e269ae0129f2f5c80cd82f0d81e7feca9835c3945a72d28", + "0xa17280693d30dcd43c85de8f6b02d5f30cb9097274ad680cede1ef105c903615b4c40f3c6aaca478642de324972514e0", + "0x8d5f76e093aee71d0cdeb017fdfcb13bd068039746de90690ce150a0bfdbe7ddc4d539df0f82c2d2890a40b191900594", + "0x96fa1f2196a3883cdd73c66d28403cbbb58f6a939a3697ee0d308d8a076393cbb4be86255af986869230ee410c01bcfa", + "0xa8b74438dc5cabd70a91bf25601af915c4418d074327a9b01e0190c27d3922c89bb9b41e0b366e82e313edda8f21983d", + "0xac9fdc1a9b2e3ff379eb2370979372e13c4177bf4574f1490fadf05a7073e6d61e703e2d8eed9ce984aba317d411e219", + "0xa45a6c9b958169f2f8df70143e6ac3e2f6f969a4eed6fd9f1c620711bc2454739bb69f0094079464790c5429c0d8aedd", + "0x8901cbdd1009864386577842c1e3d37835fddf834064d9613b4559ea9aef3084204e1f863c4306f874141f4374f449ff", + "0xb6c582161691e3635536686825be9c4d7399d668a7675738417e0363e064dfd28acdbd8dbc9e34c1dab8a1990f1f0eba", + "0x89e89ddaf3cacc78428f3168549c161283ca8337345750667c98212717b21e7d994eae4e45bbddacc832a18df1d79276", + "0x84be275627eed8e1a73c7af8a20cee1ef5cc568cfeea7ec323d7f91b44e9653e9aeed47c1896a8240b99dde545f0e1fa", + "0xa779a54ab4f40228f6e2539595fb8d509b70aab7c19e1928c1be69ec1dc19285c3898cf15e5f8b8bc725e13af177fe17", + "0x92e2a49d2b9b36349d442283b17d46f8f9bf5932c34223015ce62d2f285e7363b2c12232be4a838b5b6cf08e694c094c", + "0x8b4e28c6f3f36caa2cfb82ba88066c830f8017bd35608b077143dff236f3181230166f5a5c02fa0e5272297331726aed", + "0x85fd77d46162ffac4b8adb25baff0eb0512a53a3d01638b3a376ea34702279ce21c8e7d8884308c03e00c9bcc1a9fd29", + "0xaad5e46916ff1be29009b595d1d8fa160cc7aa01c7fbf3a68f445c87615790dcab1fcdbdceda533d182b6541f09f2f73", + "0x948df7654726250dae393325addd3c0a20431c81f00470962190335ea4b6d9f7463d6f308cda46b92084c1f24390b1da", + "0x8f577474dea132676504376c5542b730b6604fe3d965eaa194659fd11c52233bd0b11ab62e198c0f442327ff1c00e501", + "0xae2f1001546db3e0c19700adad997cd9f765fe7a51a502cbcd9a2a07a3a5db79c8f603e05cf96d80b688cb6c9b6cd3ae", + "0x953b68e5d9561088dd20406ea7fb6894cba33868a38ace38fc30b5813140cb15dd6dd2171befae5b4df2e4a9658889d8", + "0x86c52901655ff11419b084a04da8fc3596eae59d81d3461601c0baff59ba59e3d1dd0b7ce719e741a3e97c013e898579", + "0xb9a72dd5eff73f9912a28b55de073568efb3eb0241a10b77a2bfd4f30c2aa4fbfe0c89eb345c9f07fb725660873cb515", + "0x8e7353f5f2932e4ffd95811caf46c9bd1a53643c27eb41a4ebd211f230955cd71a8b27e17cfe8aa708d8514c0de67a66", + "0xa096b8e66312a92fb10839ebe60189a8d1bd34dff55f7dfae85e4d2f53a1a4a88211c19fc84494f066358ddce82be131", + "0x931c5cd82719d76596832b007969b5f75d65cffabb41b9dac7910300db677c1309abe77eeb9837a68c760bb72013b73a", + "0x8ba10f5118d778085122065b55dd1918fddb650cce7854d15a8f0da747da44d7b12d44fc29ad7dc38f174be803db74c6", + "0x8c971deec679372a328587d91fd24ab91043e936ca709c333453d7afd43ee256d08c71cb89f0ab0e89ae119831df6d86", + "0xa2ac28a58034fbd8fd518f409221bad0efec52670880f202e09c0530e2aabc2171ed95e99891790596ffad163d86c110", + "0xb3354e3dfa8068aba4f3741152b9204baa4e342c1cc77e6dd1419cbaf8da1d118be605846b8609e997d6a62a11f3423a", + "0xa12ab65a213c9d95c24865fddc2dffe0cf9fc527dd6bcdacc1bd7271e79929a4ab3427a231f4f49d0530474e6cbc88f9", + "0x90afd65b7e6973f8aafbe74da0f42441840d3c93bd69bc1bec8fa56824e7ca97ad1b427c8a85da7d588469bd4ccc50c3", + "0xa09175940c59489bac3d3da3a4091270d9118948cbbdd57f2bcc63fbf45b8010651c801d3e58dccf42733ce1d6b446a3", + "0xa843bbf286e3cecc1fe370ff1bcf5f1001bc2e95b34246625ff50d48ee62343e82fba2d25b8a4bd5f7b5ffe90920efa2", + "0xa3c4d1003219157fdbee2707ce07afa6c2a64ae8e450182c307ed7f070024071f30b12c4b0032960ff913c74e73a9976", + "0xb24af3f68d66f825d06fc3ff94fcccebe28b1a0d4ba29c48d3a3c953b9bf7ae6707f193fef25e2dcbd2b74e483c774f0", + "0xb0f657f7723184ef7d7e4381143f1ac8020d8c6c6f2dcbebb0eaf9870d61a81f2d452596503311e46d1b38f625d4756b", + "0xb90091004fc8f6205c51bec68547ac82dba0f5525631e7632cf6efe54eecd9020729fbee6105d1b8012402d3b79c54aa", + "0x8e3fa187713c60eb0a416d6900a894cdf81e6b6b69dae0bb64f6287f3c3f030cfa85c665f7aace1eab4937f380b8f728", + "0x879bf0784ccf6725c9cd1ea8c49fde31c91c605de1ea664a33c2ce24c277ee45d20b66309f98d989acb2ff3b77e13101", + "0xaf3f3a3ddc4e11abd627d5aef8adffa91c25df5f0c68b4d2b5d51e7d9af3395ba4f6f7ae2325a6672847e1ecc6cad628", + "0x973e667289e796d3a40f072e6fea575a9b371a9997cf8961677f8dd934619ddc47c1a3efe91bae9ef95acb11a8fe6d09", + "0xafa81c5606de82f46b93f4bb6db3fc0670f4e0d1091388b138a66b3827322d95a56168c951c30831d59eeadc227500bd", + "0xb83eff77db5b4c18574662942eb36f6261c59f655f8a9c3d3731412d0f257c8e80aacc995c4b2303058a1ba32522a434", + "0x912e5ac9234b9445be8260393ff08e4859a7a385e800b74d1534eeb971f58f74cfb518dfdb89f8705d89fbf721439129", + "0xab27c8ece4a51d23e22c2e22efa43487c941139b37ea1182e96efb54ca4809d8245eae0ebe8ba94f0ed4457896fe11b1", + "0xa6630585d104a745bc79dba266d9292bbdad346449c8ee8140a5e6e8a6194411df9cdbf3d3ef83468a536d4f052e9335", + "0x8b8c128244da48e7fec641a882d0005a2d05c7138d86a293e6a0a97c76bf632b44767d0ce44663c975e7f9f9679e25e3", + "0x87dbcaca67351a4e7d2297d7cdba4796d12f58857e7ee4abd0645563577ff33544a44cd84e50b3a3b420d6998de9b57c", + "0xb859ba43df259d7f8e7fac70bfd7aae546d57a5dc90e107b174a95bf7fd3cf00f740c4434848e69b2a7e6061f66c1ef1", + "0x99d6e20978fefc40c6d310187eb2ad3a39296f189ee122ed64d74f81033c3069d44f7a9d3988a1df635b609603a17272", + "0x99a5ddf3420cc0c92b21f71a805245608d4995ead447d8f73a670d26d33e26920d5f07bfe1f6230bd5f15978055b4253", + "0xb936ac0944d3c5e4b494f48f158000abb37b80b5c763f77fe856398c664b0f1ddbcc0a9a2a672db9278f08b4bafbe2ec", + "0xb4af85fbf4040e35a686dd016adec037c99b47cc2e4dfccaf7870ee9e8c97bff30f3035992def2a9d4af323c0b3af8ae", + "0xa5ee32b8bd5f8fa9000da4da0bf00565659a43285393d37080b555d0166bde64d87317b2eab2d48a0e7b287caa989be2", + "0x894d4ad58ecb1c9ebc4f5a97407082e56cb7358d7a881ba7da72321c5027498454f2c7fa2bd5f67a4b11d38c7f14344a", + "0x965be9eeaa0d450dacc1b1cc2fbf0d5d4b0dd188f2c89aaa9260e7307a2a1eb22db6092fccb662269e9a1abfc547cabb", + "0x805893c424aec206260c1c2d2509d2cb9e67ee528bd5179a8417a667aa216a3f318ed118b50d28da18e36c01f0805e3f", + "0x972d7040d4963b35260ef0cc37cd01746f1a2a87cedc0dc7b0ee7e838c9e4573784ea743f563b5267eb3905d4fa961ba", + "0x8c7156991d4c2e561888feaecf501f721b4174e7d14109e9deeac5a9d748301c07e11fb2b04b09799f0d34ff42cb77d1", + "0x894722ac35af3d507e81d737d21e16c5ba04686f8f004aa75934aae5e17acd3e065b96e229eb011c2f34096f4c62048b", + "0x81237937c247c88e8e31e2c72412189fe59c1daf65c5513489d86cf29ee922c0bb08e5f7890f09f4ada7e5262083d266", + "0x8cf62cda2fe0d9a6b42aa2a1c483f4ad26378c7cc2c2d1510a76df7560b07dba8528b33aaacb15f7f20b9d4c7c9f61f6", + "0xaaf0921fb3e1920eee5d0acb59dcc268b42f4b435d60d25d30357edd7dd758d035919691bd15311d85489dfa2e5ee696", + "0x92cec07be2247ef42002ebcaf65ec855611b8e893a5675796f2225f55412201b0bf9f4761924d0c8377b9f131e09e39f", + "0x8e514a62ac1e91773d99588415426c97ad63e917c10d762fe06ace5277a5c3bf3730e4b9e5d116f8493b9ab8687b70e3", + "0x83932df2d923a5052468a3ea87f7b55c6a80ede3594046ee4fe233046570921822bc16555b92ba6aeabaef9b1dc0805a", + "0xa2b5bfb249de3472113fd3f35bfabf3c21d5609da62a27ea6aab5f309c9068d94bc58ba03efb4ec11be06306d59e60e8", + "0x8106cf3ebe6f0507be8c6e8d137987315fe3689ecb75bb27980f36ba5efac504baccea0e7603549b6d126beccc278804", + "0xa73ee70b6fe8c082443972102c453fc0e386852476cf22224fc0bfe554735c12f96037fbf10922795f4502c4f052b5f4", + "0x932b27e175440169958504f3ed6400e7d6dcd5e716c19dcd0f15c56c04503ed133d5a993e111c016f141e32d68b29886", + "0x96f7ce4595318e0b4a6b368f788ff82226aac676aed4ace343867f751de414453a9aaaabef6e6224ce5aedc3d5cf77c4", + "0xa950c1e3bc9a14484997013d44d876374b939af437ae7c821c131fb886063ee9fe7214a25a0c7084f0b07b99412eff75", + "0xa9dba3886ed6855303106a1bdd26010f294218684e1c178afcfea3f37a2f04fd01724a31d82de3449046617e3507a115", + "0x87a2f776b32a6b550cf3ceeaf78db02819be74968d228b1d14e0d74a1cdf994bb500b7abef6619455e98d728701fac5c", + "0x8cd887b07e335edc0b27e6a660cebb64d210741395be431d79d570139687b056557159407459799a8197b6079644f666", + "0xb81a61fce00588909c13a90c1caa150f15788786af443ff60ce654b57147601f7e70b95659e01f470334a220b547611b", + "0x8aebc51141544c5f3d3b99422250424b9800031a8fdfbf22c430907a3a446fecaa2392105d66d64b1c8e847240da4a6a", + "0x90db7dc12baa02f3f86d3edadf9434e2b9318d4f6f0eca08276b765dbb38d8eb0d08be2fe70adf2bf16ceda5db08d3ca", + "0xaa1839894152d548cc6ad963de20fb6fcc843bc9af2a2bf967c63626b8ad19e900894d6106265f38f3afccca317c22f0", + "0x848e27b741496988a582515c0c8847b2bfc6a001259396cdeea1e1b1d2828ca3a626693a1bf4adf3a3d7f8b1fa3d75fe", + "0xa0aa11754d4ee136ac3ca609b17bcae77758763b2016544ca7921dddedd8aafcc7ad5f2b337c8bf53084eb8e43ea41fb", + "0xb8713b7aa1c112178195fdcc9b7024f46e6bc04c4e76c41abe620aa265287809200d98eaed6c9703fa97e81d6964f0ec", + "0x8605b5b33309e9ea6823542b85383c496794b8481c577497aaf99ba90496e794dce405be615bf92c7b6361460e6b82e3", + "0x826fa34faa7f83e063a7bf172addfc07badabada59cfc6604fdf481d29085251c0a67a1355b2cbd374e2975934b84cb6", + "0xb45d131082dc16fa53af010d43eefb79200dc23d2f3ee26af95ac6a5cebc49c84a9ed293e534ed16ff3ef9a4a25456ec", + "0x91bd6ce3c5396a7a0de489e49f0cdf6dce1cd2d0be7a410326423c3185bd1125ce1e610768be7f15f4e44b62f8834fc3", + "0x903ffbe3d33fbf106c01c727dc3a385201a67ded70d4df623934882f69a3a96c909b027a124f3d70cb072b0046a149e8", + "0xb405359db9d9ef4821a181b440ef2918c240595141d861d19a85867a5afa74d2972d22c988775eab441e734700bae4a3", + "0x8abb756d027233c83751910a832b0ef4d28d100077f1c5d656720c94906f91d85dd0ea94b1cc0ed95b692efee14c786e", + "0xa78ee77ab476a41a3454160ba7ca4085d8b1f7057c63e76db8b07cf20afdeddd2250cd00771a6329133bb4ad48ccc20a", + "0xa41810271d8c37197aa9b3dfcefe3498e42f5978d3f3d59defff4676d6402d8575b40683834f184f143b6cfbfc859b3a", + "0x90c24a0750242660bcc6d487358a3cc015730538a0a8beb00ad5ac2ef33cb8ca8a62121e50bec8f3d2f43900f8e3134a", + "0x8b96c39695d864ef5796941754978a1fd612b369f6b77fe5ae6587beac936ee28190af8f0a3822b63060af35e49a5c8b", + "0xacde2548883d0e63c0fc257bb9dadd919aba60a985b69ebcfa1bca78acca42fc1322ec30bcc8e7c188818f858d04ad33", + "0x895c86ae9ff8d95f2707d4838a3bc8ddb05b2611f0476f014b9c150d0e8332bc73285037a747426f09ac8179ba4e19fc", + "0x821761fe406e18bd86fa9ca9db99d382cd3b5c70c456f471fa3706d57763d147706304c75d54f51ce8f3115aa26e59d9", + "0xa803a80e3e8f47dc3c59ea23eafdec017458eac648b360cd42cbd075e0dde6f6f450b48c7646fb1e178c04f82ae51a12", + "0x91f40e1b6f588bd592829ce937996452c40be0fd6c43793c607866701ac6a8c7227e0891d45c6e7b1599382b0a3fbdbb", + "0x9408246d996a634a58689337f2526dfb3ba9ffef1d3ff91c32aa8cbbed900861ef25d6477308b67d76491edfcc70d65e", + "0xa492325a427f3df1c9c690c5b553daa8ac41f62f5ae55f425539222bacf959e2f67afabbba1732e120d3e7a6dcdf7049", + "0x8fd0c3e15477cae228613a171b6e9ec29ddc63ef74854d99b638adeffe39f89f34346a42851e8445e855a9f2bbef0f57", + "0xb735ed01fafa051004dbaad5e8c9e2faca8f6049ef9b590f256ea4d75b04594af12764ad4e6031735eae36f83179db93", + "0xa7d35f43fca06c86b3425dcb68a87186834ba9740664fd657915771beca4cdc0fa2fc9b4c2e9d9bdad8ec33543ddfa59", + "0xa1156e71e2db1b17df5da28747c88e091bd687bfee59d89096437ab4dc9a543fe5c5272d5023d72adbaab397a6fc94d1", + "0xab06a58bd81b33a411bade8d8c5232d38fadc2e38507159edea6e2e104b8ebd65ca02b05335118f691d44197b847a4dd", + "0x848b67a10f1e6ff8f5c228f226ef2ffeb67fb8f50925fc94cbb588d61896d9dc79726959e649898fd3354fe3ff7b7ee3", + "0xaa933397361f32b388edcf832f0db172a38e756b34d5f7a4a050fa7325058006c22cede26ee27917e8f1b0f301792bd7", + "0x89e49e7f02cfaae4a4b9c4180c9f6559d76e3a45774955859d4147970b1470dac37bdc9aedca1c32a20b045049161590", + "0xadc1825d5ab94fc719f25d8c9773f4d518134ed88eb13ac33cb910b2be3523ef9ef88d9e4aea2418b806e20108317bf6", + "0x96c4b444c8a023da644f3a343ebeeed19a8392d2ce175992461451c318a54273b76c3574d8f2dceda2947ddd34d1a674", + "0x8aa7e97e87c8c5b29bbd51a6d30396a6be1fb82b716ef83800f2c36d5b85467ade7e0f59d2db82c310fa92a9265f0b03", + "0x9146c32d99f02c3a6f764dcd9b4807f1585f528ac69dc4f84e4380f6fda4f9d5057c375671d51e7aca2b2b4140e83da0", + "0xa10760a533d9bc57536bcaf65f080302086aa50225437efd64e176841544711828c23a15c49c0dd1f357d3f10722ab72", + "0xacb0811777e17f7ae7aaba5f6fce81b759c067a4908730916195a2505c7450d0e6e2194c2ef0f241090597d58e70de47", + "0xb24f161e9bcdbad56665e2490b5e4c7768390d4668cd69a04ed74739062dbe832636dd33cda89e9b0afa8c77e93fc641", + "0x96b4d01106b831868a88ef016500ef2fa42d0ce87a37ca8ca4194a92a22c113edfe04eb2ca037329f3c1acc635148f55", + "0xaebbb95fb4f7adcc8e7a217aeb73f9e037cbb873d08c1cd9d68c6c6834511adf1af8b44567fee84327599bdcb734dedb", + "0xa9bd8b17300532fb94d028659bcafbe7bbdf32f8945baf5db4cfaa1bac09e57c94cad0ba046b4514044b8fe81ea8596d", + "0xa5557cbda599857c512533e7cadcf27bf8444daa0602aa7499cafc1cf1cf21f9d16429915db7485f0e9a1b5046cf01c5", + "0x8810307c40bc661c478a9747ebf2a30e5a5ead942d1ac0418db36ba5db0709c476f7d19685cabe6959e33ec1f3bff914", + "0x8829b741f41f2c32e10b252d9338deb486dba2f23996a44cf1dd888ad967a589d51329be34d764139f372a1043f6c2e5", + "0xa6b4728d18857c5fa082fa67bfb3b1d801e76b251b1e211a19c87cea5fe7ce757f943c85071f7a03a718388cd5690e95", + "0x86da7f397e2533cd487f962ae58e87bea2cd50af70ef2df9ea0f29f70b5843cde664d30ec207ab84fc817f3851277e02", + "0x8085776ef4ac6d42ab85b9d9135ecc6380720efd274f966544eeedf4684028197de76ecab919fa5414302597e1962bca", + "0xb05a065c733033d223ba13d16baa7a97bd8c8b8b1f0e59a9bdd36ee17e9922d48eb39bd180c168b122088a77f0bf321a", + "0xa89343fe44a93023dcc7ef71bd3bcb6786f68e1885ad260edc56a52445d34757f476395ba7ad35437f89bc573c7618dc", + "0xa114a9cd6105b524f3969c69faa2e09afe21753a93361a296f9e0e3b4e3e63726ddf2e6bfd3ddc046043e50bd44e539e", + "0x8a5611fec539cf681c05636bb580f29acc06f628bb012649ffa41ea6c1521194a5643d5dd843f09b6eb2c3bdb4d41acd", + "0xade247c4011ec73ec90b72f35afa59a999e64ba5a7e664a4b30874fea53ba6a14a76a41b58a5f891a20d019e5f091bdb", + "0x905b5d96df388160ade1ffe210d0c6d1979081bc3de3b8d93ac0d677cc2fc2dc1ef6dcd49d3947055514292a3fa2932e", + "0xa9520796ca9fccd11b7524d866507f731f0f88976f0de04286e68d7cf6dbd192d0d269f0cd60fd3d34011a9fe9e144c2", + "0x989a1edf4d7dae811eb57a865c8e64297837ffeeaae6ee6ac3af0f1044f023f1ca552bf00f1642491f0f0f20e820632e", + "0x879c8e63713f4935ed6e020559e140ea3073ced79d3096c152c430141272117b4fd9a9fc3eef012e81262df02ea14bd7", + "0x95074738ac1540c0312274333acd1ecad9c5509fee883c4d9295fa8d8200f6e637c363de395f9fa612f05c0dc58fae88", + "0xa770e4fc595269eb806b113ab3187ea75c8f96b57bf9fcfaf535f3eedc1d4d7e6285a20990575de0ff09f62d06ed0692", + "0x81283e5dfb6423439ff513eca1cc316941d196df8da2d1069d2d0b63f5289e630af2fd4119bc0144c002d33313372dab", + "0xabd1b108e743887b78f698f2aba9d5492f87a22868d1351d705d93a1084fd45be67170c68a6e18b07f400d9a01cda8c2", + "0x8509c3f67b92908cea8144f4e2a71631a66a61ac3547601c788907e52e380e5fe8ae4110aed95d13c67d3bcdd5b55a61", + "0x8fa5a790ec5cce6d4114128c295390120869aac5490a82feebd3c37a167120df2e7fdfaf2a4050a7dfebf48fb093212f", + "0x944753e1ea7d8bc727d46a7702077dc01dc0c6574e8263a16579b57ee155ca5901f71bb347a01a9a922b329d3ff75135", + "0xb46bc1fd4590b7a6275e20036d247c5909fc549c78e95b64ae7ed96e3b05bb044840f19f7650ebfe7008ba09fa83c3c9", + "0xb1e47e4d88e59a06c465348c6cc4181d40f45b91e5e883966d370c26622c328415c6144aa2f61ddb88ec752482c550ca", + "0x8bd4f8e293e3f1815c7e67167618fb3b0ea76424bc0985908957cfcede36109378e41b4d89555b8c2541b4c447e00461", + "0xa70589a867b2bfb63d0106083d58475d506637148549ed35c83f14e5c8de996e1b1f3447ecc80cf5cd134ef4db9d2fb6", + "0x8048b80ba6131d07370162724127b0f7cb17fa7f71855e55e5a75bd0a9e4fd71b0d0ea2d16ec98858e458528df8d06b5", + "0x97326cb94bae7530f4ec3235770c5a7ba042759e789d91c31fedbd979e3c0e6a2c69e2af3c1979c6fe0094274dbd53ce", + "0xa18e9c1d3eabd62af4e31a4b8e08494f4167fd4598c95d0123f39c46c53f9e93f76615900246e81a286c782ac37c569f", + "0x80309c59d4522b15aba617cd3c6238663e8b1c7ad84456346082c8f281140fc0edf9caa19de411c7e7fb809ca4fa3f4d", + "0x8e450c0990e2f65923f252311623038899eeff7b5c2da85b3a224e0ef7132588b291b782d53c477ecb70f34501466178", + "0x87843f96f41484e254e754c681a65681b9ae5c96c292140368743df9e60f7e2ada58ca2bb95fa39abe064b2ebf21eeba", + "0x858e8d5bf2a1cf26d8af5036b28b831d450a446026f58a1734b696c18f1f41482796b91cab0e5b443dd2f0b9cffa52b4", + "0x99627dd6bad8c05c5904cd23aa667d664da846496dbbb8452705c4ec01e1480e9c7295504a5a8529e4a0c842306b038d", + "0xb64b33256c18b2c886a837a0c0730fdfe73befb0e2796207c4dc592c5a33cd51f8c2ef47c584dd5773abf9ce9c1b0082", + "0x944f6da2a1546f0bfc4d98c3e73c79e935e33d208b6be26b0b5f8df6d0e3b74a5bda649853b99281bd3a3ec799a7dd04", + "0xa266d165435784d4e884640155e35b2a911b3f89e1e715986de419b166a36a341ba724877d80583fa3da566f6a828971", + "0xadff2698409d0756e78c534032ee926560c13d578cb178d5073172d049ebbce32a92692f7e2033ec781b9b0d894ddce0", + "0xa91933f110756c699c28bf9e24fd405bf432002a28c4349e0ca995528e56a5a2d101b8d78afa90a178ff1a9bf2ba515c", + "0x8e77839c0eb4da2d01e4053912cd823eddffbdc6b9c42199fba707ca6ab49fc324288b57be959fbfb11d59085d49324a", + "0xaa124517c76692036c737e987f27c2660514e12a953e63ff4bcb269dd18fc44dae95e282de8444bed09639ef6577af88", + "0xb285deae99688f1bd80f338772472fa2b35e68887c7eb52c4ef30fc733812444c5cd110050275ad999d5a9b57f782911", + "0x8877b0fa85b44ef31f50bdb70b879fa6df5eb1940e2b304fd0c8f08abb65f3118fa3d97ff93919038c1e452fb1160334", + "0x8a89f3b50dcbca655024542ca7d93df17deff5c7d01c7da2bdb69e76b3e0b4490d85c800fb3debb4b0b4d20c9527f7ad", + "0xb7e5dbe36e985354ac2f4ab7730fea01b850af00767a6c4d8ee72e884d0fe539bb81f2e34638fcf5d07b7c8d605f4c06", + "0xa85a1d78f6d4f9d5d83ec0f2a426708342d4e4a5d15625554e8452f6a843d9aa4db0c7e68caebdaf767c5b3a6a6b2124", + "0xa518078a9dac63c5bf511b21ed8e50d1ccede27ebfe9d240937be813f5ee56aef93dc3bf7c08606be1e6172f13f352ce", + "0x91144eedebda4d1ad801654ef4ecd46683489b177ba1de7259f7dd8242c8c1700e15938e06c5d29aa69f4660564209a0", + "0xa16c4657bc29d1d3271f507847b5a4f6401cee4ad35583ad6b7a68e6c2b9b462d77b5dd359fd88ea91ce93bb99130173", + "0x85b855778f4b506880a2833b8468871c700440a87112fa6a83fd3ddb7e294b3a232d045dc37dfc7100b36f910d93c2ae", + "0x8d86bb149d31bfbf1fabcae1b8183d19087fd601c3826a72a95d2f9cedb8bb0203d1136a754aa2dd61f84b7f515acfa9", + "0xacfe7264eee24e14e9f95251cbcfdd7e7f7112955a1972058444df3c2d2a1070627baefada3574ebd39600f7f2ea7595", + "0x906bd14ecca20ac4ae44bff77cc94eb5a4ecc61eba130de9838e066e8766ed3b58705f32c650e1e222b3100691b3806b", + "0x8f2cbc7b8593c4be941dd01b80dc406fe9dfdf813ef87df911763f644f6309d659ea9e3830ff9155e21b195fc3c01c57", + "0xa68eb15ed78fae0060c6d20852db78f31bebb59d4ddc3c5bdd9a38dbe4efa99141b311473033ff8f8ea23af219bc8125", + "0xa95cb76c9d23fc478c7e8a73161f2ff409c1e28a2624c7d5e026e3cee9e488f22225a0c5907264545a73e83260e3a4ec", + "0xb76f90e55fa37c9e2732fd6eba890dd9f1958c1a3e990bd0ce26055e22fe422d6f0bcc57a8a9890585717f0479180905", + "0xb80cc95f365fabd9602ec370ca67aa4fb1219a46e44adf039d63c432e786835bb6b80756b38f80d0864ecb80e4acb453", + "0xb753c86c82d98a5b04e89de8d005f513f5ea5ea5cf281a561d881ed9ad9d9a4be5febb6438e0dba3d377a7509d839df0", + "0xa664733f3b902fac4d1a65ea0d479bb2b54a4f0e2140ed258570da2e5907746e2ac173ace9120d8de4a5e29657ae6e05", + "0x9479722da1a53446e2559bb0e70c4e5bf3f86c0ce478eede6f686db23be97fcd496f00a9e174ceb89ab27f80621f9b80", + "0xb707fd21b75a8d244d8d578f3302d1b32bb2d09f2bd5247dff638d8b8b678c87d4feab83fe275c5553720a059d403836", + "0x93214c16831c6e1d6e5a1266f09f435bbed5030c3c4c96794b38d4a70871782002e558d960778e4465b1ff296ffedad8", + "0x8648f84e18eb63dad624e5fa0e7a28af2ee6d47c28f191be0918c412bf24b5460c04bf2b7a127c472914a0741843f78b", + "0xb67f61e75d6b773a6b58b847d87084b94f3cdac3daa7bef75c2238903a84250355a986b158ff96ba276ca13a6035fdd6", + "0xae9b094b7b5359ee4239d0858d3755a51aba19fce8ad82b0936cca48017523319c3309409ea6e9883a41bece2077e4d8", + "0x8d1d8e1fba8cebd7a0e1effea785a35e16b1a10842f43e2b161d75add11eccf8f942d2ae91c20eef6c1a0c813731ea9a", + "0xb82bd387458e3603782d5e2dec32ae03890a3fc156d7138d953f98eff4200de27c224f626e3648e80cd3dfc684c4790f", + "0xa6dd02a89ad1c84e25e91176c26355e21a01b126c1df4d22546159dab9d502dbc69bc0d793a017c1456516e4aa5fa53f", + "0xa9ab74a5c5459b8500beb0ad13e9cfe2656e966dc9b4f3f98bec7588023b4ddebf74e4fc722d30423f639f4ee1b2587f", + "0xb03e5f33ab7ecec12cbc547038d3fa4f7ea0437e571891c39660c38d148212d191be29e04eb2dc001b674219b7a15a9c", + "0x925df4fc6e898ca55090ad1a8f756cc5014167a042affda5b24896eeb6aac408545134920586a8e1a2b997de9758b78a", + "0x98c8580fb56ed329fad9665bdf5b1676934ddfb701a339cc52c2c051e006f8202e1b2b0f5de01127c2cacf3b84deb384", + "0xafc3765d374c60fac209abd976fe2c6f03ce5cc5c392f664bb8fac01be6d5a6e6251ac5fb54cfcd73e3b2db6af587cbb", + "0x8e7e98fb5a0b5b50d1a64a411f216c6738baaca97e06d1eba1c561e5c52809b9dab1da9f378b5f7d56a01af077e4f8cf", + "0xb724bf90309651afb2c5babaa62dc6eac2b8a565701520fe0508cee937f4f7b6f483fc164b15d4be4e29414ce5d3c7d4", + "0x9665160e7bf73c94f956ecb8ba8c46fe43ae55c354ce36da40ccc7594beae21d48d9c34d1af15228c42d062a84353a0c", + "0x8600ab3aa86b408ee6e477c55572573ed8cfb23689bbdadf9fccb00161b921ec66427d9988763a7009b823fa79f8a187", + "0xb0d8d19fd1022e7bc628d456b9bd1a2584dce504eb0bf0802bdb1abd7a069abbeeccdb97ce688f3f84a229342dbc1c33", + "0x8f447d5e5a65bb4b717d6939cbd06485b1d9870fe43d12f2da93ca3bb636133a96e49f46d2658b6c59f0436d4eede857", + "0xb94e327d408d8553a54e263f6daa5f150f9067364ded7406dcb5c32db3c2dffd81d466ee65378db78d1c90bc20b08ab3", + "0xb58c02781b74ef6f57f9d0714a96161d6bfa04aa758473fb4d67cc02094cd0c0f29d0527c37679a62b98771420cf638b", + "0x8cfa0a687ea51561713e928271c43324b938aa11bb90f7ffaa0e4a779b3e98899f2af59364ce67b73a46a88748c76efa", + "0x95d6d39c814c5362df69116558d81ce6f1c65fb400fc62de037f670d85f23f392c1451d43341c59bc342bc31842c8582", + "0xaf888b384c52d9e04e4db6c4e507c2037eb5857e9bcc33acf84fc3a02d93cbde8cce32141fce9f5fec715b5f24d56356", + "0xa7822bbc3c236fd58bd978f0fc15fe0b60933a0c953db6436a233441219418090ae0c07c490a6548e319029771cdaba7", + "0x8c53729f750922e5eb461774be8851a3f40fe42eed170881cc8024d590bf0a161d861f5c967144d15cdcdc3dc6b5cf88", + "0xa052a25a4aeab0d5bb79bc92a6ae14b5ad07d1baca73f4f6684ccecfc7ea69bc21eadeb9510452fdba116c0502dd698f", + "0x923946b83d37f60555dbac99f141f5a232728c6eb819a37e568c8c6e4d9e97a4229fb75d1de7e9d81f3356f69e6d36f1", + "0x8cab82cf7e415b64a63bd272fe514d8b1fa03ba29852ec8ef04e9c73d02a2b0d12092a8937756fdec02d27c8080fb125", + "0xb1123314852495e8d2789260e7b3c6f3e38cb068a47bdf54ed05f963258d8bcabaa36ccbea095ba008e07a2678ec85a7", + "0xa685b779514961e2652155af805996ceb15fb45c7af89c5896f161cac18e07b78c9776047c95b196362c9ad5430bcb22", + "0xb734dd88f6cc6329c1cb0316c08ade03369a11dc33191086c6a177cf24540c7ceee8199b7afa86c344d78d513f828e81", + "0xb0bf492fb136ecdb602c37636ed4deef44560ab752c0af5080a79c9f76a1f954eba60a0bf6ba8bd7b8cac21848c29741", + "0xa5c74682323e85ac20f912ab9c1d6e1b9246c4c829dca40c8a7d58ec07ea0ad3524be30623f351269552f49b65a1245c", + "0x837403b9cf830fb33ecc11a7c8433e07745973c36acdeb3fc9ea8f7d8d690d462e1250b7410f79f2f4180fe8f3962a4f", + "0xb03d64b944d49c83608f2c5b9c14070c025f7568c4c33d4eeb1da31d07f0bc5897e498b35b50d557ee129f0c3c68e254", + "0x827272aab8bf757e2483156e00fbebe1093a58070dd3af9855bbf946c7abfb9c8a850a6a8acda8c620902f391f968b8f", + "0x84c4eb863a865282d321302d06b362f8bd11c2bb0090f90ebffedd3eb3e7af704cff00d39a6d48cbea4262942e95200b", + "0xb044eb91653dc55dce75c8d636308a5a0dae1298de4382d318e934140a21ca90e8a210e06fdf93aadbbeab1c2ef3904a", + "0xa8c08955a4378522e09a351ecb21b54025a90f2936b974068e80862803e7da2b5380c4b83b4b4aad0409df8d6c8cc0cb", + "0xa763a5fb32bd6cb7d7c6199041f429782deacac22b6a8467077fab68824dd69343ebca63a11004c637b9cb3129dbf493", + "0x8c44c8afa9a623f05c2e2aba12e381abdb6753bb494da81f238452f24c758c0a0d517982f3999d2537b7279d381625ed", + "0x8613f47fda577cd3bda7c99b80cf4b2dd40699edfd3df78acb5e456dd41fd0773bc8da6c5e8cbf726a519b9fb7646ccc", + "0xb21a30d49d7e1c52068482b837a4475568d0923d38e813cea429c1000b5f79b8905b08f6db237e2eccf7ef3e29848162", + "0xb9bdf4915f3fbb8d84cdfd0deedf2c9dc5b14f52bf299ef5dca2f816988e66322df078da2c54b934b69728fd3bef40b5", + "0x993b45f389f55eba8e5ba1042d9a87242c383a066cbf19bc871b090abe04de9ff6c1438cb091875d21b8c10fac51db58", + "0xa85a95d14633d52d499727f3939979a498c154fd7ebb444b08f637b32c1caf5cca5e933a2f5d94f26851ae162707b77d", + "0xb9874c7c4be1c88a9646e0c2f467cd76bc21765b5ab85d551305f5ec0b4419e39d90703d4ac1bb01feb3b160517e97b7", + "0xad6771177fc78812904c90594712956357de1533a07fec3082ba707f19c5866596d624efc3e11773b3100547d8f6c202", + "0xa79f31921134f7197f79c43a4b5d5b86736a8d3ad5af1bdf4ad8789c2bfe1c905199c5e9f21e9f446247224f82b334f8", + "0xa7f1b6c45321222a350a86543162c6e4e3d2a7c2dce41aeb94c42c02418f0892dbd70c31700245d78c4d125163b2cd5e", + "0x92abafe3ec9dbe55c193fb69042500067eb8f776e9bf0f1cb5ab8eb12e3d34986d1204136856fb115c12784c3b8dea6e", + "0x89bc761238a4d989006ca5af5303c910c584fe7e6f22aa9f65f0718a1bc171e452c43695e9f5a591725e870770c0eceb", + "0xaa0e44c2b006a27d35e8087779411ba2f9f1966a0f5646ff6871bcf63a8b1a4a7638751b94c9b9798ccd491c940bc53f", + "0x8736fe82862b8106e7fdab7b5a964d87ec291a74b8eb1cb5a6c046a648c1b686064ef3d52297043b8940bfe870c712f8", + "0x956a3def1942f05144d8e9c3a82fd2d3610064b53b9eefde3d5594a8f705bf8f6849eb2c22181796beffeba43cc74ee4", + "0xaf27416d00cf97d5a1f4a1b6b51c010884cceca294f1151c3b684a3f83c3c8a3c30771df1166d833cbddf6c873c400c3", + "0xaac3b8dca2336fc4ffc63c362df461289e4bbd3418c621bde6c581d3ecedf66e2b3e523d4db39e3d8ba014577bf85efd", + "0x94c3a8167f62074e5b28c2bffe4b6ce645439a9a0c5da3ca1b3ee956590a465d6f84a8a4dbbe9070ffbd6bbc734e4d62", + "0x95e23ba6986d25ed4451215da05bd72c5491528271726d79a94c8cb16aef1c85b190d6c5b8a3a1191c7cafbab1dccf0c", + "0x953e3dadb5ad68f7de31ac09692948655d174fe16d88b96930ef35b331da7f1dbc4c17863cd07b4ec3135b5205891a27", + "0x915d018f18b5d63cb3301c2bb5c6e85e75a88ba80663c964d06575b6bacbbe59139d030b218ce0998271d5b28c00b26d", + "0x8c871ba3dd138a908b2f7effeea0e71df096b23e0dd47cab10b9762b250abfd1221da94a8ee884e05bdf02271fb85a04", + "0x96bad5c6ebc3080ecbe337409ae398bbeada651221c42a43ea3b7c08c21841ddbcfde544c9b8d4772de6f2ce92c0b963", + "0xb5dbcd0b1c44c62108841558ec0a48df4b327a741e208c38b1c052321eda6e6ad01af71d49dfcdd445ab6fa6f0c34e6d", + "0x97dba59219b69e8aef2659d1f10bbea98d74aefff1f6451de3f41be39acbac0122b8ff58b02e90554469e88911ec3547", + "0xb7e5682ec306478be4858296f5d03364a61f3260636a4242f984d351a02e8723378496beb30c4ca22def9c9ca193ea70", + "0x9656a7a3df4d11df3d8bc35930dff70a5e78a488ca57bba20bb06814fc390fc6c7cb3f39b22134992aad196cced577de", + "0x8b269695aa63eb56d0324ba984279dc4c88e565321f1d61d553622bd4f1910d5eff68393d3a830eb924472bd478c2aa3", + "0x9177bcd04b28c87bc0440268b4c8995c6790cad6039594971b2c177f0e197055231e776927d3fa30d98fb897a2ba401f", + "0xae0e943973482001c4f214b9da82e1c27e38aa254d0555e016095c537c835d3702bc2de5c67b234ab151e02b3b7a43a6", + "0x82fc719a7d38bf4787fe1888019ad89fbf29beb951d2fece8686d2beb9119d0c8c6d13bc598748c72c70d73d488140ca", + "0xb716dc66f87eb16b95df8066877353962d91bf98cf7346a7f27056c2a4956fb65e55cb512af278783887ab269e91cd76", + "0x81d58cd8bc6657362d724b966321cd29a1b5cdc4601a49fa06e07e1ad13b05e9f387ca4f053ed42396c508cd065c5219", + "0xb32ad0280df6651c27bb6ddbdc61d5eb8246722140a2e29c02b8b52127de57a970e1ded5c2a67f9491ae9667349f4c46", + "0xb68a2eb64cc43f423be8985b1a068e3814b0d6217837fb8fbfd9c786db9cca91885c86899c50a1242040b53bf304ced9", + "0x85887515d4e371eabb81194cbc070e0c422179e01dbda050b359bd5870449c7950e6b3947b7a4a0eb68199341cc89fc3", + "0xac5fff3c27dfbab78eb8aad37ac31cc747a82401ebf3644a4f4f5aa98d37b8bf3b3f4bd8a3428b32a127c25c9e19d239", + "0x86fceaa6fbf8913553a9e1e907fcb1f1986d5e401a7eafd353beefd1899d571454fea96ff5b2a21254d9fb693ec94951", + "0xb6778bb296d3f0de2531b67d36fdbfa21475be0ca48b9dfcc38f396c41b557823735ed0b583e525a2bae1fe06e04058c", + "0x898088babeb5b9866537d6489f7514524c118704abd66b54210dc40a1c1ddb0a1edf7fe0b6e0db53b836f1828ecf939e", + "0xb27854364b97274765f0fb8d1f80d3660d469785d1b68da05e2bd1e4b8cbbe04304804d4c8aabb44cf030eba6c496510", + "0x8c55bbf3603dc11cb78b6395ccbc01e08afcef13611f7c52956b7a65ccf9c70551bff3ae274367200be9fc2d5cb26506", + "0x947726f73cd6281cd448d94f21d3b91b96de7ad3ff039f9153befbb5f172db9f53cacb4f88c80a3db26e6a0f7a846eb0", + "0xa7b733a05e97528812d71cecb4f638a90d51acf6b8fcbc054787d6deb7e2595b7b8d1cbe1aa09d78375b5e684a2019bc", + "0x8d5ca6d161341461544c533314fe0a6655cde032c2d96f0e4ea7e41098b8b39fa075d38e2d8c74e2d0308f250d6cf353", + "0xb960e9f081393e2260b41f988935285586a26657a3d00b0692ea85420373b9f279b2f1bb2da2caae72dd2e314045f1bd", + "0x852a49c7388c10821b387c6d51617add97ba72485f52be95d347bac44c638c92e9c6a44ba0d32afc4d59178a497d944a", + "0x8412162a65147e1334ad5af512982b2b48eef565682b3f3e0bbe93fbc5e1103db9375a0c486bdb1b2c57e4cb3a8e7851", + "0x8f52c3eb5d4f1e1e82cfd2b291d4910195427603b796f6c311deb35ef14a01a57a9e6cad39619ad108f3e86f384f9e1c", + "0x88d221088f2bf0103c53e44d0d96cd7881ec2b0a965db9121a47481771a8b796edd5ac23c4f9c208a171dab301f7d3bb", + "0xb49c3235e8b3617ed08a1891b9e2bcb33dbdacceb94ca96330555b7e00904fe6a749ced9312b8634f88bcb4e76f91cb1", + "0xa85834215e32f284d6dfb0cbfd97f6cffc7b9d354e8f8126d54598bb42d7f858a2b914cf84fa664069632db2ff89a332", + "0xaa3d48eb483c6120c27d9b3e3d0178c1c942632ff54b69f5b3cfbc6ad4ff5b2b9ce6eb771fd1eea8edf4a74c97027265", + "0xa446cfded353cdd9487783b45846402b973cdeddf87e2bf10cf4661610fff35743cc25e8d3b5771dcedfb46b018a5d18", + "0x80998377b3b393ef3073f1a655ad9d1e34980750e9a5cfb95f53a221b053ddb4d6985747217e9c920735b0c851d7551f", + "0xa35ac469790fac6b8b07b486f36d0c02421a5f74ea2f0a20ffc5da8b622ac45dfccabfb737efa6e1689b4bd908234536", + "0x8fb1f6d8e9c463b16ac1d0f36e04544320d5a482dd6ffaec90ea0f02b4611aaca984828bf67f84dcc3506b69af0a00a1", + "0xb6e818d61aea62c5ed39c0a22ccbb327178feebdabda0c9927aa1549d2c5bb0637785c4aed2a6d9a7b4989fa8634c64a", + "0xb4e7208d16018bf67caafe996d436113eac619732e3f529a6efb7e6f094d8ebea55b7be0e122be075770f5957b6ea6f0", + "0xb691d38b552befac61f6d367287c38d01fec73b7f2efdb6713ca30314a37fb7c177eb111fe6bee657f2681014e07630a", + "0x9817587e418e6e7e8e97ae27067f17b55d25dfb14e98f63f530620c855d9a348c9fa571c8508e2741f902f8b9fdc0c5c", + "0xb6a6e5ca779ba140bf1d84cd5394ede8262f7479637ec0087a4b152243a1774ba916d8115ce759a3bebd1b409de5f2fc", + "0xb53d1c84ad766ff794bf497db3228efd2cc8ed5fc1958d89c1126efdff361610ecb45ea8e329b39035ab00a66c1259c7", + "0xadc31333c507c8e0f4aa2934fcdca57fd9c786722a50dbd5404e129541f7ac182cc7373bf14e1e4e06e6cf94b31b90eb", + "0xa82b7fde4642d982d95cec669efee140ad797a2442c7f6620580527d163accbf021b893446cbb8038ea82fe25b15d029", + "0x91f7acf8a8903979afa281646fdecb54aa4d2ed905748e156e92f0910de268fa29d67107d40863935d677d1de8039be2", + "0x86fea71c6d43a7d93216a92fc24dfce8521fd4534a9558b33762d002081247867a6eff54cad7116023277fb4049403ad", + "0x8ae5369a7f9f4c91f3be44b98089efd9c97c08f5bb4cd8b3150c115ecd86288fa0865a046a489c782973a111eb93966e", + "0xb6fb9e829aa2c81c2d9eac72bb2fd7f3a08e0cd763532c2ce3287444d33cf48b3621f205e9603ec58525934b61a795a9", + "0x83e35ca808d84e41fc92115e9f6e283e928c3a614e6dfc48fe78c33b6411262e7bfa731eadb1e1937bc03cff60032e1d", + "0x832fca5196c95098ad47b7d24ba2f9d042e1c73ad2273edd1c2ce36386796ccc26e8567847697f3fcc2a0536a2a2087a", + "0x8fdb7038bc8f462ab2b76bf7053362f9c030019f1b6105cf42219a4e620ecc961e3eacb16a8e581a562a97f1418b0128", + "0x8d3a5a404b51b1ad8ce3b23970e0d5cc57b573922341008e3a952a1dd24a135e19e55b79d86a70cfd82e1c0e9630f874", + "0xba00c025c1c21c57c03cdfc0bfd094b35422281ff0a64b68b240617aa58c6b18800af5f2047d3ff9068bbe987d6c7980", + "0xb468f0dd51964b3806b0aa04f3fe28a035e8f5567fc7d27555be33d02701a838b8dbfe1348b6422c4eac46d2c75c40c7", + "0x8a73a18c97da9958903c38584b08d0e7e26993a5d9b068a5e0e1ee0d8a873942745cf795f94f7a3d3ba88790a9fbb2f6", + "0x953a0a40c2c8102723736854d13b228698c14a02d85c8d2e61db1a768019ac305faf0d5db62ac976430ce087a5b20f1e", + "0x8998219da6b34f657cb8a621c890a52cb98c2bc0f26f26e2af666eebeadadc5e8bdf4f830a91d04aca8ce186190152c8", + "0x8941e08c3155ad432236ed05460420a05dd0aaab30477493ffb364b14c00ea5b9183d30d3442b6321d2d20c36e4f5c7e", + "0x93f293ff7fb56cf5b03aee6f3ad2ad78444398ed5b3be56d7bf5b56b5aa5a2b980d13895dd57a5726d1b067c20cc55e2", + "0x84a16f313e3f75e31824f58d19ab24c6611fb4c75140a7cadc3c166f68819547c1d0ff7f7d13f5d8ae30dff1d80e2aa4", + "0xb6e3e830b15039d3e28b08f5465bb089eade11ee3bd80afe39e010df7db1fcf0c56d698717677a41ddbc91eeaf6544d3", + "0x95e928e6dfff51351281568ae72da7d1edeb6e9fe01f30af0499e7505ba35a22b5bb919d41bb809a432dce83f3977663", + "0xaabeeb60ca46f9b0232ff82ea7766dcab8cc5aaf9d23539f30174f9486640bc9312868ca493b59b314519fc399973e47", + "0xb393a11e957d0bbb3ecf617b075b5906a3450b348e62916c04791b366f0a7397cccd6648440ac544bc30526e1f95aad8", + "0xabb5bfc3964a6d246da60bd809d0ea6daf4f8222efdc12ceb6730194e85f413ee7eb03bae300abf7ea900dbbc3d08971", + "0x96c1bd1d1d216a4bfbcf000c123f296c0d31e1684e9e3884c14df23bf528c8d599f82bb98fcea491716b617216a8e0be", + "0x92d1e570a56f1741fd9f3d9f488cc336421c6256c14a08d340a63720be49b0029e3780e3e193a2e22bf66cc652fa22a3", + "0x8769c08551e3a730e46f8e5d0db9cf38e565a001dfb50db3c30fa7fa0e98b19438edc23c6e03c8c144581b720d7b33a4", + "0xb850bd67fdf5d77d9288680b2f6b3bc0f210580447fb6c404eb01139a43fccb7ed20051999ae2323ea5a58de9676bfb4", + "0x80285da7a0aaf72c4528a137182d89a4db22a446e6c4a488cf3411937f4e83f7b00ec7549b0b4417682e283f91225dfe", + "0x80520368a80b97d80feb09dbc6908096c40ff7120f415702c1614d7112b0b57f6729581c71f4a3ce794ac959a46494ff", + "0x9817b4c27a490b1cd5a6337e7bc7e8005fa075dd980c6bf075ddfa46cd51cc307ad1d9f24e613b762a20fc6c877eab41", + "0xad66bda1a3034ec5e420b78107896ecf36126ce3ef9705163db259072dfa438c6107717a33572272062b9f60cb89557c", + "0x876114ef078c2915288e29c9abe6b0ad6a756b5ee2930ba1b8a17257f3f0557602d1225e8aa41ce8606af71ada2a971b", + "0xaa3d6cde4c3b9d3d5d0c77a33e67f182a3e1cf89b0921423b2024236171955b34afc52b1f25b1dad9da9b001371771d7", + "0x984d3e3a72412d290e3459339757af7520d1739c7af0cbcf659c71999328db44f407d92e8a69fea11625612c49eac927", + "0xae890d0faf5bd3280dcad20a5f90e23a206661be8842375fea2ab22aadc500849ffbc52fe743b376d46bb926cedae6a6", + "0xb1f231f3f4d710c3fe80099faeb56dac67c1baf53b8fe67a9920fe4f90e52cb9a4bf19211249a6456613b28efe337f18", + "0x8caa54b418ba609d16520af3dff2e96d5f2eeb162c065a1763beb926547b2cfb3ae41d738db2c5681a9bc8bc9e6b9a1a", + "0x932157ff56c5ac29cf6cf44f450c882b3acfbb9f43d12d118da3d6256bde4e6eb3183aea304ab6967f37baa718ffec99", + "0x9360bed8fc5b6aac36aa69473040689bfc30411d20ffb7275ef39b9ff5789f9055d149383ce9f0f7709a1f9d683adbfe", + "0x98b5b33209068335da72782179d0c7aeeabe94b5560a19d72088fe8323e56db7ce65debe37a97536b6b8a0ca3b840b61", + "0x89a385c11be40064160b030a1bb28c3921fc8078522618a238c7ea0f86f34717ed9af9b4e2e20f5128e5f7fc66ad841e", + "0xb615703cbc64b4192990cc7e4903b74aed6a0076ce113b59ef7719197ffa46fb29eb78ca56b49873487432d0625c0faa", + "0x90f0d77abae9d3ad73a218e5ccec505ad108ea098451461567ae8ef9661606ca8e78df53b5d628b20b7037bd24622330", + "0x92e0e7cc4dfadc5fa0ee6da0c8de0493030db6e54ba0317f52f232a6708b732068b6077bd13a17eb7eb40b88368085b5", + "0xa24dad20094985bfccc6df1343506ed3bf9dcbdf4b2085a87627a5d71f7568db067304e465f8f380c5c88e8a27291a01", + "0x8629a45a10619354c84bdc2f6c42f540eab5a46f53f2ae11970433d7a2aef007897590bf31dfba1c921614c6d6fe1687", + "0x84ac64040d4206f82b08c771f375da4b7d752e41d2aa0da20ce845f6bc1b880a855d3ee966bca19b8ec327b4b43e7f0e", + "0x9608e6050c25996c052509f43f24a85cdf184135f46eaac520a9a6e78e0d44a6cee50ebc054048c708aefde8cd6651c2", + "0xa32032b0e0d7cc35e480c328f315327f9385adb102a708c9ba637878deb74582ae26bb6d6e5f8c9e3a839b0e0154b82a", + "0xb7e3c78d63acc6564a49e9f00b0a820b56d4f37a2374af1f7f1d016268011df9e7af0670ed2b0eee961f15aa948328dd", + "0x8b88bfdd353acc91ad0d308a43e5fb40da22c228f2fe093c6d6904d70f69c6203f56636ed898b05df51d33f1095ef609", + "0xb1d7a430c51fc857af55047683fc18c453b013527196c5e1bf776819a3dffca802217e9249ae03f084e2ea03ad67fcc2", + "0x80558e28a819ddb5e72e97c54be0f57c173ccf78038d360d190b7f1350a19577b8e3f43fa2f7bf113a228cd3b965b2e4", + "0xb4b2ec44e746c00dfc5661ba2514930934fc805cdc29adc531c02d28ce3cc754414b0485d4ee593232cd1175f357ad66", + "0xb57cee5d32835f76572330f61ccd25a203f0e4a7e5053d32965db283aad92f287645533e8e615137208383ec51b1fd99", + "0x930256086b419a8a6581c52590d0dbd9f8a3564c79424198fca3866b786df2f6098a18c50dc4abd20853a7184b1ce15d", + "0x8e75fd01181cffcd618a983492390f486e8c889972a46c1f34a4e1b38f384e8e4efc7e3c18533aa2057da9f9623e2238", + "0xb375d927dd988429f9e2764e5943916131092c394fce13b311baa10f34b023dd3571da02553176091a0738cc23771b9a", + "0xb9e28e4c0d0477518034d000e32464852e6951c8db6f64ccdb1d2566f5094716213fbf2fc0e29ac88d0e79f725e3c926", + "0x963981e99392afbd2b8318d5a6b2b0cc69c7f2f2f13f4b38dddbfedb2b0eaf0584aecfcbda20a4c60789c15d77970a58", + "0xa7804e1977aa77c263c7c001afa6cf568032dea940e350d6a58ce4614f1a91c13ae1c78bfea740c229dce2444556976a", + "0x8787204177da3cde6d35cd3497fa8774d244f9faa9f4bd91b636a613a32ce2ea0326378cf9c4cf475e73ef751b355c4b", + "0x895aeef46a07152a04ec812f1aa1fd431389fa0ef6c6e96a5b833e70ea14073bc9984757a8ee456dbec9788e74e6f0ca", + "0x8d17f0e5826783440d1f0ec868003510a4d9952bfe4a638e44a36d94482ac18ba70ef7ff773bdf7a3b62d714dcf0fcba", + "0x810d5e36b31310b2e054a666d3b3f7ed16dfcb1765532d87ca2a3920316f0187303c27dd113db145d47e8961062a6c03", + "0xb4e2fb48ae04cf8580bb6a28095076c9b95e5f13122b917328f334d4ac8a8648ce442919e28319a40148987350ab5303", + "0xb85549a313544fa1eb3ceb78473b7d3d717fc85b808de7b79db7dbd0af838ebb020622a7503f1cbacab688dddb648f84", + "0x80665adee057088eae827a5fe904ec3ad77d8843cdce0322d535e0659b4abc74a4d7ddd8a94c27f2def5c34ac2c038ee", + "0xad72fc19c2ce99b5b717e35528fe7d3ac8add340b02ebeb4889d9a94c32f312a0b45ea84d21c54f84cc40ee4958b72e1", + "0x99d530c843dff89a47a5ee8c87303ab18f8a82b0d5b808fca050354b35da5c5a5594d55921c6362d6cc917d75bdc18dc", + "0x99c7286c293e1be21c5b2a669dfdfcd5aa587105d2886fc5a8eaf8984da4e907f7d7b8c2362d64a4f1621b077a2a08a0", + "0xb4a39e1a9ed5d80c9563c3ca3fadf76f5478c63a98f4346a61b930c9c733e002f3ff02bc16abfdb53d776184cc3f87ba", + "0x9378ea71b941979404c92d01fb70b33fa68d085bf15d60eb1c9fc2b5fcdee6379f5583389a3660a756a50019a2f19a69", + "0xb68e17344a2bc45b8e2e19466b86dc139afefbf9bad2e2e28276a725099ebac7f5763f3cb52002261e3abe45ef51eb1a", + "0x819e64dc412b2d194d693b9b3157c1070a226af35c629837df145ea12ad52fa8eabd65b025a63c1fb0726207a58cdde8", + "0xa5e8ff8748419466ff6df5d389125f3d46aedacf44eaf12cbfe2f68d218c7d5ab6de4a8279d13aecc25f3b1d98230894", + "0x91560d54a9715cfda9cf7133ae51c432d0bf7fcbaeb468004994e6838bfc5ddcfa30e4e780667d0c4c0376780b083017", + "0xae8adb3309cc89d79a55ff74f129bb311fe4f5351a8b87600a87e0c3ba60825f71fccf67eadcf7e4b243c619417540fd", + "0x8d92cc1a6baa7bfa96fbce9940e7187b3d142f1888bdcb09bb5c8abf63355e9fb942ac4b4819d9be0e0e822d3e8e2e08", + "0xa6e8b79fdd90c34735bb8fbef02165ccbe55ea726dc203b15e7a015bf311c9cac56efd84d221cc55eaa710ee749dbdfe", + "0xa409b151de37bddf39ce5f8aa3def60ee91d6f03ddd533fce9bf7bdbeac618cc982c4f1ffbf6e302b8353d8f28f8c479", + "0xb9693975ef82171b3b9fc318ca296e4fe6110b26cbdfd653418f7754563fa7b6e22d64f8025ee4243483fa321572bfe4", + "0xa039ebe0d9ee4a03ade08e2104ffd7169975b224061924cca2aae71464d250851e9f5f6f6cb288b5bf15df9e252712a6", + "0xb27834db422395bd330e53736a001341ce02c9b148c277dabac67dc422741bfa983c28d47c27e8214cd861f2bad8c6f6", + "0xa2bafaf4e2daf629fd27d7d5ac09fb5efc930ff2ae610f37519808683aa583fe1c6f37207daf73de1d8a164f79a0c981", + "0xb856cee1cfcf5e50db9af4ab0aed3db2f43c936eaea369b5bba65582f61f383c285efbda97b1c068c5d230cbe94f7722", + "0xa61ab205554c0550fa267e46a3d454cd1b0a631646b3df140623ff1bfffaa118e9abe6b62814968cc2a506e9c03ea9a0", + "0x8c78edcd106377b9cbdfa2abd5278724aed0d9e4ae5869b5d2b568fdabb7804c953bae96294fcc70ef3cd52ba2cbe4ed", + "0x8570869a9bbf6cc84966545a36586a60be4d694839f367b73dfc40b5f623fc4e246b39b9a3090694aa2e17e652d07fd1", + "0xa905b82c4da8d866a894da72315a95dc98faa3c7b3d809aef18f3b2be4801e736a1b79a406179e8cac8f74d27e71ac52", + "0xa8eb8679ff1a64908515f6720ff69434cb33d63aeb22d565fde506618908b1d37585e3bd4d044fd0838b55787af06b42", + "0xaf4d86b2fbd1684a657dffe4210321a71e6ae560c144d44668d1f324dc9630e98348c3d444622a689327c1a59cc169dd", + "0x80359c6eab16954559ab0e6a1fee9a0526c45d3cae1a371159a2e3aa9b893afdc3a785c9559a5fd9cd8cd774234bf819", + "0x8d4e5ff81eb5d17bbe8ae6416538ca51a9427ce142b311f5cbb14febbbbb9c1ffc6489fd625b9266264c366c12a9d997", + "0x92e181c66489c5fa063ba2a1a354b6fd3439b8b4365a8c90e42e169bfaa1fb5766bf3e0fe804399d18bc8fbcafb5c3b1", + "0xa9ddf229360a095393885083716cb69c819b2d7cfb100e459c2e6beb999ff04446d1e4a0534832ae3b178cbe29f4f1d3", + "0x8e085ef7d919302a1cc797857b75cff194bdbc1c5216434fa808c3dea0cf666f39d9b00f6d12b409693d7a9bd50a912c", + "0x916dc4dc89e5e6acf69e4485a09fc66968f9b292eac61a146df1b750aa3da2425a0743d492179f90a543a0d4cd72c980", + "0xb9cbf17e32c43d7863150d4811b974882da338cf0ed1313765b431b89457021dd1e421eeaa52840ef00551bb630962dc", + "0xa6fb875786daec1a91484481787093d8d691dd07e15c9c0c6ae0404bf9dc26083ed15d03c6d3fe03e29f28e20da21269", + "0xa870fcb54b9a029e8086de9b08da8782c64ad2cc2e7fdf955b913d294038bb8136193256b85267e75a4ca205808a76b4", + "0x99883f057e09b88bf0e316f9814c091837fd5c26eeb16fec108c9fed4b7a2bd1c783dac0e4242b5a906621ab606c1e50", + "0x85d89069ca3190577dab39bbec43c16bf6dbca439ad3eebd8f5e9f507d84c3c43e77fd6323224582566a3aa2c8018951", + "0x9363ba219e0003f6e8a9d8937b9e1449e4b2c5cd57194563b758bea39deab88778e8f8e4f7816970a617fb077e1e1d42", + "0x820622f25553c035326145c1d2d537dc9cfd064c2f5bdf6d4ec97814de5fe9a0fbd443345fa2ea0a9d40d81d3936aa56", + "0x87e31110aaf447e70c3316459250e4f7f8c24420c97828f9eb33b22107542c5535bdb48b0e58682dd842edea2886ff08", + "0x95bf80cac6f42029d843d1246588acb40a74802f9e94b2bf69b1833936767e701ef7b0e099e22ab9f20f8c0c4a794b6c", + "0xa46ecf612b2763d099b27fb814bd8fdbaee51d6b9ac277ad6f28350b843ce91d701371adfaaf4509400dc11628089b58", + "0x8604decf299fb17e073969708be5befeb1090ab688ad9f3f97a0847a40ea9a11bbcfc7a91e8dc27bc67a155123f3bd02", + "0x8eb765c8dc509061825f3688cb2d78b6fef90cf44db33783d256f09be284bc7282205279725b78882688a514247c4976", + "0xb5c30b2244fa109d66b3a5270b178960fdec47d31e63db0b374b80d2b626409eb76d2e8d1ebf47ef96c166743032fc5e", + "0xaab01e76290a7e936989530221646160bf8f64e61e79282e980c8c5dcaaa805ff096efd01d075a2c75917a3f4bf15041", + "0xb9d79671debd0b83d0c7c7c3e64c0fb1274300564b262771f839b49218501e7f38ef80cae1f7e5a3c34acdc74c89dab6", + "0x92c0eaceadf036b3b9dfd2712013aba3dd7c30b7760f501f52141618265baa31840fe77850a7014dc528f71f8cf39ce6", + "0xb3cdd098059980455dd5b1c04182df1bd12fa844a866f02a9f8a86aab95b59945baa9af99f687410bffc5b07153cb23c", + "0xb361b73a62f71256b7f6ea8e0f6615e14fc5a06ee98b928ab3c9dd3eef9d9d30070e9855c82b7facb639cacb3401e01f", + "0xb9c85fc0f25a3271cf28b1ca900078eaaa66cbab0a3e677606e898ac32781a2dfce4d9cbd07404599e2c3c02fa161c9d", + "0xac5b4fdac2a0b2e6430d9fc72bde4249d72183b197fc7347bb1546ae6f544426686bbe0caec3ee973b6836da5e831c44", + "0xb675aebf24b92e398e166f171a6df442b3f5919b6bee192f31675a5e8eeb77d34c6590a6f0c0857417e0f78cfb085db8", + "0xa9bef942044d8d62e6a40169f7dc7b49e40cd0d77f8678dd7c7bae6f46c46786f9b1e319a3fa408f22a54fd2a4d70804", + "0xa20d19cd917d5102ae9ca0cf532127d2b953aa3303310e8a8c4b3da025dded993a47e3a28e6b02acfadb6d65dc2d41a3", + "0xa47fdb04059b83b2afb86a47b2368bbd7247c337a36d3333b6e5ef2cc9476a92c4907e4c58a845c9ef9b497621e0b714", + "0x94a9e9ffc14b411e11a4ffa59878d59460263589003dc7b6915247c549f67feede279bf3645fdd92379022fb21e3caeb", + "0xb92e1177dd9ecdaf1370c71b14954219cf0851f309bc216d5907a4e2e84e0df3457018224150c142cc6bf86644bb4b73", + "0x8bc57fadd68a265b7df9b42227a9c0968db7b1bb50dc12f7d755505779f1ff2c408672b3091e903366acc9ce15d19fb6", + "0xb6b5efbe1ac4e1bd2e8447c45000d09397b772ca5496acc447b881022608a41c4f60388814607a01890190105bee7be3", + "0x95f7c85fd614df968f8ccf8d086579c9e1cec4644ecf06da26e3511cb39635a7326b3cec47bd51cf5646f1c660425e9c", + "0xb81765fb319bcdc74b4d608383ccb4af7dd84413b23af637be12e2827a75f7e4bcd14441cf979ed9038ae366fbb6f022", + "0xa120ea76cda8c6c50c97035078f6648afe6537809bdba26e7c9e61de8f3070d2347160f9d34010effbf2ec7e94f5749f", + "0x92c1b8631953b40d3cc77eee2c72a064b999c09a9b92c11d8fa7b4072966273901c9dba25f9f79f384d9f11a56f3fc7a", + "0xa4b00dc0ab67b2300abc9c516e34daf444d6497b066a90cfe3381ed2812304ed37b14f3b948990443dc6c1cf1bed460c", + "0xa9e9f7e13c9f031bc7b9e6f1417c7abcc38894fe7d3f54869ee277afd2efa3e6fb50757dd36c8c94d591e0abdea322cc", + "0x84f3e98f831792b5ad14bcfe62a4c9f296476c6087c4c1ec7767fc642fbca141ff6a3deeb8b4d4106a9cda5a9937eea0", + "0x8eb1a7931bbea9a714226fd74b0100ab88355287d9b0a349c095e9b5809b98f237ffd706bce7d67a770da355fb9cec7b", + "0x9738ef8739e1742c1f26b51a1621be0b89d37406a370c531e236f635c7064c661818817bb3858908986aa687b28b21be", + "0xa9cf3ce8501b003ccaf57552a4c4ec31081e44526d3aa3791d3dc4a7e438a357c0956f93c500356186d8fd4588ffac5e", + "0xa7af6a219cca59225839a9de5b19263cb23d75557d448bc7d677b62591a2e068c45e5f4457cceb3e9efa01d0601fc18a", + "0x972a24ece5eda7692cbb6fb727f92740451bc1281835e2a02931b2b05824a16b01dbe5edd03a0ed5b441ff25a5cc0188", + "0xb21d1ec7597ce95a42f759c9a8d79c8275d7e29047a22e08150f0f65014702f10b7edce8c03f6e7ab578ce8c3b0ec665", + "0xa13a1c7df341bd689e1f8116b7afc149c1ef39161e778aa7903e3df2569356ad31834fa58ceb191485585ce5ef6835c3", + "0xa57bdb08119dc3bc089b5b2b5383455c4de0c2fcdac2dcfa21c7ac5071a61635ff83eceb7412f53fab42d1a01991de32", + "0xb2968748fa4a6921ee752d97aa225d289f599a7db7a222450e69706533573ded450380c87f8cdd4a8b8c8db1b42b5c97", + "0x8718ec04e0d5f38e3034ecd2f13dfde840add500f43a5e13457a1c73db0d18138f938690c8c315b5bcbeb51e8b9a2781", + "0x82094789e26c4a04f2f30bdb97b9aecca9b756cbd28d22ab3c8bed8afc5b2963340ddfc5a5f505e679bf058cbc5dcbb8", + "0xa35b8a566dd6ab67eddc2467906bffc76c345d508e52e9e4bb407b4f2b2c5f39b31d5a4bf5022f87bf7181dc6be2fe41", + "0xa8c93b1e893d4777c0e3a1b4bef3be90c215781501407c4011457fc3240e13524b4d2bea64a6d0a3efe3f3b0dae9b8ab", + "0x877095ad18b1e5870818f7a606127ba1736a0b55b0dbcd281ec307c84b08afc0c9117e3a880fe48bfc225fbf37671a97", + "0x84405ee0421ed2db1add3593df8426a9c1fcc8063e875f5311a917febc193748678dd63171d0c21665fb68b6d786c378", + "0xa52cdc8209c3c310bed15a5db260c4f4d4857f19c10e4c4a4cfe9dfc324dfac851421bb801509cf8147f65068d21603c", + "0x8f8a028a70dda7285b664722387666274db92230b09b0672f1ead0d778cee79aae60688c3dfd3a8ed1efdeda5784c9d4", + "0xa0be42fecc86f245a45a8ed132d6efc4a0c4e404e1880d14601f5dce3f1c087d8480bad850d18b61629cf0d7b98e0ae0", + "0x83d157445fc45cb963b063f11085746e93ab40ece64648d3d05e33e686770c035022c14fdf3024b32b321abf498689ad", + "0x8a72bbf5a732e2d4f02e05f311027c509f228aef3561fc5edac3ef4f93313845d3a9f43c69f42e36f508efcc64a20be0", + "0xb9ca29b0ec8e41c6a02f54d8c16aebf377982488cbe2ed1753090f2db4f804f6269af03e015d647a82ef06ffaa8cba6c", + "0xb4df3858d61bbb5ded1cf0be22a79df65ae956e961fbb56c883e1881c4c21fe642e3f5a0c108a882e553ac59595e3241", + "0x86457d8890ac8858d7bab180ef66851247c2bf5e52bf69a4051d1d015252c389684fcc30bb4b664d42fbf670574ab3a3", + "0x86d5576ea6dfa06d9ebce4cd885450f270c88a283e1e0d29cab27851c14ed2f00355e167b52e1539f1218ad11d8f13dd", + "0x883ad1364dc2a92388bfafaa9bc943c55b2f813525831e817a6208c666829a40455dde494eba054b2495a95f7ce69e8a", + "0x8942371e6925231c2c603b5f5a882d8404d39f0c7c4232557c2610b21c2c07f145466da798ea78b7932da2b774aa3128", + "0xa799eb71496783cc7faf12c9d9804bf6180699a004b2f07fc5cc36840f63ce7eee7dde9275819a9aa3f8d92dc0d47557", + "0x8eb3fb5c769548ee38c7882f51b959c5d5a42b5935269ccf987d6ddbb25a206e80c6000bcc328af149e0727c0b7c02c0", + "0x8f3910d64e421a8f2d8db4c7b352ba5b3fc519d5663973fea5962efe4364fb74448770df944ef37ffe0382648fb56946", + "0xb41413e0c26ff124cf334dab0dc8e538293d8d519d11cc2d10895a96b2064ac60c7da39f08589b38726cffa4c3f0bfef", + "0xb46ef2eb10abae0f35fa4c9c7ee2665e8044b8d9f91988a241da40fd5bbc63166925582151941b400006e28bbc5ba22a", + "0xb8baa8b4c420bb572a3b6b85479b67d994c49a7ebfe1274687d946a0d0b36dfed7630cfb897350fa166f5e2eff8f9809", + "0x964b46d359c687e0dcfbdab0c2797fc2bd1042af79b7418795b43d32ffca4de89358cee97b9b30401392ff54c7834f9f", + "0x8410d0203d382ebf07f200fd02c89b80676957b31d561b76563e4412bebce42ca7cafe795039f46baf5e701171360a85", + "0xb1a8d5d473c1a912ed88ea5cfa37c2aea5c459967546d8f2f5177e04e0813b8d875b525a79c29cb3009c20e7e7292626", + "0xafaab9a1637429251d075e0ba883380043eaf668e001f16d36737028fded6faa6eeed6b5bb340f710961cee1f8801c41", + "0xaef17650003b5185d28d1e2306b2f304279da50925f2704a6a3a68312f29fe5c2f2939f14e08b0ba9dee06ea950ad001", + "0x97bcc442f370804aa4c48c2f8318d6f3452da8389af9335e187482d2e2b83b9382e5c297dce1a0f02935e227b74e09a3", + "0x8a67a27b199f0bcd02d52a3e32f9b76a486b830ec481a49a4e11807e98408b7052b48581b5dd9f0b3e93052ec45dfb68", + "0xb113bf15f430923c9805a5df2709082ab92dcdf686431bbad8c5888ca71cc749290fa4d4388a955c6d6ee3a3b9bc3c53", + "0x8629ca24440740ce86c212afed406026f4ea077e7aa369c4151b6fa57bca7f33f9d026900e5e6e681ae669fd2bd6c186", + "0x933a528371dcecc1ec6ded66b1c7b516bd691b3b8f127c13f948bfbcda3f2c774c7e4a8fbee72139c152064232103bdf", + "0x8568ddd01f81a4df34e5fa69c7f4bb8c3c04274147498156aec2e3bd98ea3e57c8a23503925de8fa3de4184563a2b79e", + "0x8160874ec030f30fda8f55bcf62613994ff7ed831e4901c7560eac647182b4a9b43bfaff74b916602b9d6ae3bfcaf929", + "0xae71c48d48cf9459800cdf9f8e96bc22e2d4e37259e5c92a2b24fbe2c6ca42675e312288603c81762f6ceb15400bc4c9", + "0xb05f39bb83fda73e0559db1fd4a71423938a87ad9f060d616d4f4a6c64bf99472a2cbfb95f88b9257c9630fc21a0b81f", + "0x80c8479a640ed7a39e67f2db5ad8dfd28979f5443e8e6c23da8087fc24134d4b9e7c94320ffa4154163270f621188c27", + "0x9969ba20ee29c64cb3285a3433a7e56a0fe4ddc6f3d93e147f49fe021bed4a9315266ebb2fb0eb3036bb02001ae015e6", + "0xa198c89fef2ab88e498703b9021becc940a80e32eb897563d65db57cc714eaa0e79092b09dd3a84cfab199250186edcc", + "0x8df14a3db8fe558a54d6120bad87405ba9415a92b08c498812c20416c291b09fed33d1e2fcf698eb14471f451e396089", + "0x81e245ef2649b8a5c8d4b27188dd7e985ef6639090bdc03462c081396cf7fc86ed7d01bfe7e649d2b399255e842bdc21", + "0x8659f622c7ab7b40061bcf7a10144b51ad3ab5348567195924f2944e8c4ce137a37f1ba328e4716c10806f3fb7271689", + "0xa575d610fc8fe09334ca619ecdadf02d468ca71dd158a5a913252ca55ea8d8f9ce4548937c239b9cb8ab752a4d5af24a", + "0x94744549cd9f29d99f4c8c663997bdfa90e975b31f1086214245de9c87b0c32209f515a0de64d72d5ef49c09b0a031fa", + "0x80a8677862b056df59e350c967a27436c671b65d58854e100115bac9824ba177e94c2a1bfcaa191a071b9cefdbee3989", + "0x91be9a5504ec99922440f92a43fe97ddce2f21b9d94cd3a94c085a89b70c903696cec203bbab6d0a70693ba4e558fb01", + "0x8c5a0087bcd370734d12d9b3ab7bc19e9a336d4b49fc42825b2bfedcd73bb85eb47bf8bb8552b9097cc0790e8134d08c", + "0x933aa9e6bd86df5d043e0577a48e17eea3352e23befdbb7d7dcac33b5703d5ace230443ac0a40e23bf95da4cc2313478", + "0x984b7ee4bd081ee06c484db6114c2ce0ba356988efb90f4c46ff85ed2865fb37f56a730166c29ef0ae3345a39cdeae7a", + "0xae830f908ea60276c6c949fb8813e2386cf8d1df26dcf8206aa8c849e4467243e074471380ed433465dc8925c138ea4c", + "0x874c1df98d45b510b4f22feff46a7e8ed22cfc3fad2ac4094b53b9e6477c8dfc604976ca3cee16c07906dece471aa6c6", + "0xa603eb60d4c0fb90fa000d2913689126849c0261e6a8649218270e22a994902965a4e7f8c9462447259495fe17296093", + "0xa7c73d759a8ad5e3a64c6d050740d444e8d6b6c9ade6fb31cb660fa93dc4a79091230baccb51c888da05c28cb26f6f3f", + "0xa4411b79b6a85c79ea173bd9c23d49d19e736475f3d7d53213c5349ebb94a266d510d12ba52b2ac7a62deaaaec7339b8", + "0x943b84f8bbcee53b06266b5c4cd24d649d972593837fe82b0bf5d5e1bbc1a2bf148e1426c366d7c39ab566b10224cadc", + "0x8300012096a8b4cefecc080054bf3ceb0918162ba263c6848860423407796b5eb517170c0bad8e4905ac69a383055a21", + "0x8244a1e3ad41908c6f037e2f8db052e81f281646141334829f36c707f307448b9ab79a7f382a1e8d86f877c90b59271c", + "0x8eca1b74687802ecc36a5d39e4516a9dee3de61a2047252d9ed737b49e0090c386e9d792ac004c96337681c7f29a16ad", + "0xb70fa47535f0524835039a20036c61e77f66146ad79d3d339214d8744742db41ceeb577c829d000011aeafbb12e09579", + "0x84b3abbce48689f3adbb99889c7fd1f3e15ab455d477e34f5151c5c1c358ed77a5b6a581879f7e0f1f34106e0792e547", + "0xab45ecb58c0ef0dbce3d16afc6ac281e0d90ec48741ea96a141152647e98fcc87f3a3ff07ba81f3179118453ce123156", + "0x90d231a145ba36a59087e259bbfc019fa369201fcfeaa4347d5fd0a22cd8a716e5a797f3cc357f2779edb08f3b666169", + "0xa4f6074d23c6c97e00130bc05f25213ca4fa76c69ca1ace9dece904a2bdd9d987661f5d55023b50028c444af47ff7a08", + "0x933af884939ad0241f3f1f8e8be65f91d77ac0fb234e1134d92713b7cfb927f1933f164aec39177daa13b39c1370fac8", + "0x80d1db6933ce72091332ae47dc691acb2a9038f1239327b26d08ea9d40aa8f2e44410bbda64f2842a398cbe8f74f770f", + "0xa7a08605be2241ccc00151b00b3196d9c0717c4150909a2e9cd05538781231762b6cc6994bebbd4cddae7164d048e7b2", + "0x96db0d839765a8fdbbac03430fa800519e11e06c9b402039e9ae8b6503840c7ecac44123df37e3d220ac03e77612f4e4", + "0x96d70f8e9acd5a3151a8a9100ad94f16c289a31d61df681c23b17f21749c9062622d0a90f6d12c52397b609c6e997f76", + "0x8cf8e22273f7459396ff674749ab7e24c94fe8ab36d45d8235e83be98d556f2b8668ba3a4ec1cb98fac3c0925335c295", + "0x97b7e796a822262abc1a1f5a54cb72a1ea12c6c5824ac34cd1310be02d858a3c3aa56a80f340439b60d100e59c25097d", + "0xa48208328b08769737aa1a30482563a4a052aea736539eceab148fa6653a80cb6a80542e8b453f1f92a33d0480c20961", + "0xb612184941413fd6c85ff6aa517b58303b9938958aa85a85911e53ed308778624d77eadb27ccf970573e25d3dfd83df7", + "0xb3717068011648c7d03bbd1e2fc9521a86d2c3ae69113d732c2468880a3b932ebec93596957026477b02842ed71a331b", + "0xa0ad363e1352dcf035b03830fef4e27d5fd6481d29d5e8c9d51e851e3862d63cdcbaf8e330d61c1b90886921dac2c6fd", + "0x8db409fdacfa4bfdaf01cc87c8e97b53ca3a6e3a526d794eaad1c2023f3df4b888f1bf19fee9a990fe6d5c7c3063f30c", + "0xb34d6975310ab15938b75ef15020a165fc849949065d32d912554b51ffa1d3f428a6d1a396cb9329367670391de33842", + "0x9117285e9e6762853fc074b8a92b3923864de2c88c13cea7bab574aaf8cdd324843455d2c3f83c00f91f27c7ecc5592a", + "0xb4b2e8f190ea0b60819894710c866bf8578dd1b231ae701d430797cc7ede6e216e8ca6a304f3af9484061563645bf2ab", + "0x8c493c6853ab135d96a464815dd06cad8b3e8b163849cdefc23d1f20211685753b3d3e147be43e61e92e35d35a0a0697", + "0x9864d7880f778c42d33cf102c425e380d999d55a975a29c2774cad920dfddb80087a446c4f32ed9a6ab5f22ec6f82af0", + "0x90f67fe26f11ca13e0c72b2c2798c0d0569ed6bc4ce5bbaf517c096e7296d5dd5685a25012f6c6d579af5b4f5d400b37", + "0xa228872348966f26e28a962af32e8fa7388d04bc07cfc0224a12be10757ac7ab16a3387c0b8318fcb0c67384b0e8c1a4", + "0xa9d9d64bba3c03b51acf70aeb746a2712ddafe3b3667ae3c25622df377c2b5504e7ab598263bec835ab972283c9a168b", + "0x932128971c9d333f32939a1b46c4f7cf7e9d8417bd08dc5bd4573ccbd6ec5b460ac8880fb7f142f7ef8a40eef76d0c6d", + "0x964115e7838f2f197d6f09c06fbb2301d6e27c0ecdf208350cf3b36c748436dac50f47f9f9ac651c09ab7ad7221c7e43", + "0xa5941f619e5f55a9cf6e7f1499b1f1bcddcc7cf5e274efedaaad73a75bc71b1fc5c29cd903f6c69dc9a366a6933ca9d1", + "0xa154bf5eaec096029e5fe7c8bf6c695ae51ace356bb1ad234747776c7e1b406dee2d58864c3f4af84ed69f310974125e", + "0xb504e6209d48b0338ab1e4bdab663bac343bb6e0433466b70e49dc4464c1ec05f4a98111fd4450393607510ae467c915", + "0x813411918ea79bdde295393284dc378b9bdc6cfcb34678b9733ea8c041ac9a32c1e7906e814887469f2c1e39287e80f8", + "0x8be0369f94e4d72c561e6edb891755368660208853988647c55a8eed60275f2dd6ee27db976de6ecf54ac5c66aaf0ae6", + "0xa7e2701e55b1e7ea9294994c8ad1c080db06a6fc8710cd0c9f804195dce2a97661c566089c80652f27b39018f774f85e", + "0x956b537703133b6ddf620d873eac67af058805a8cc4beb70f9c16c6787bf3cc9765e430d57a84a4c3c9fbdd11a007257", + "0x835ae5b3bb3ee5e52e048626e3ddaa49e28a65cb94b7ecdc2e272ff603b7058f1f90b4c75b4b9558f23851f1a5547a35", + "0x85d67c371d1bf6dc72cca7887fa7c886ce988b5d77dc176d767be3205e80f6af2204d6530f7060b1f65d360a0eaeff30", + "0xa84a6647a10fcef8353769ef5f55a701c53870054691a6e9d7e748cbe417b3b41dbb881bae67adc12cb6596c0d8be376", + "0x87ffe271fc0964cb225551c7a61008d8bcb8b3d3942970dbcc2b9f4f9045a767971880368ea254e2038a3a0b94ecf236", + "0x964bb721c51d43ee7dd67c1a2b7dd2cc672ce8fad78c22dcddb43e6aab48d9a4a7dc595d702aa54a6fb0ffabf01f2780", + "0xa89b3f84bb7dcbe3741749776f5b78a269f6b1bebb8e95d3cc80b834fd2177c6be058d16cacfd0d5e1e35e85cde8b811", + "0xb4314538e003a1587b5592ff07355ea03239f17e75c49d51f32babe8e048b90b046a73357bcb9ce382d3e8fbe2f8e68b", + "0x86daf4bf201ae5537b5d4f4d734ed2934b9cf74de30513e3280402078f1787871b6973aa60f75858bdf696f19935a0e2", + "0xb1adf5d4f83f089dc4f5dae9dbd215322fa98c964e2eaa409bf8ca3fa5c627880a014ed209492c3894b3df1c117236c4", + "0xb508d52382c5bac5749bc8c89f70c650bb2ed3ef9dc99619468c387c1b6c9ff530a906dfa393f78f34c4f2f31478508a", + "0xa8349a5865cb1f191bebb845dfbc25c747681d769dbffd40d8cedf9c9a62fa2cbc14b64bb6121120dab4e24bef8e6b37", + "0xaf0500d4af99c83db8890a25f0be1de267a382ec5e9835e2f3503e1bac9412acf9ff83a7b9385708ef8187a38a37bc77", + "0xb76d57a1c1f85b8a8e1722a47057b4c572800957a6b48882d1fc21309c2e45f648a8db0fcff760d1dbc7732cf37c009b", + "0xb93c996cec0d3714667b5a5a5f7c05a7dc00bbc9f95ac8e310626b9e41ae4cc5707fac3e5bd86e1e1f2f6d9627b0da94", + "0x93216fdb864217b4c761090a0921cf8d42649ab7c4da1e009ec5450432564cb5a06cb6e8678579202d3985bd9e941cef", + "0x8b8be41105186a339987ae3a5f075fbc91f34b9984d222dfed0f0f85d2f684b56a56ab5dc812a411570491743d6c8b18", + "0x959b72782a6b2469e77fe4d492674cc51db148119b0671bd5d1765715f49fa8a87e907646671161586e84979ef16d631", + "0x86b7fc72fb7e7904ea71d5e66ba0d5d898ace7850985c8cc4a1c4902c5bf94351d23ce62eed45e24321fb02adfa49fc8", + "0xa2f244e7c9aa272cb0d067d81d25e5a3045b80b5a520b49fd5996ece267a7f1bea42e53147bbf153d9af215ea605fc9e", + "0x81aa2efa5520eebc894ce909ba5ce3250f2d96baa5f4f186a0637a1eea0080dd3a96c2f9fadf92262c1c5566ddb79bab", + "0xb607dd110cfe510d087bcff9a18480ba2912662256d0ab7b1d8120b22db4ad036b2266f46152754664c4e08d0fc583f6", + "0x8f588d5f4837e41312744caac5eee9ddc3ad7085871041694f0b5813edf83dc13af7970f7c9b6d234a886e07fa676a04", + "0x924921b903207783b31016cbec4e6c99e70f5244e775755c90d03a8b769738be3ba61577aca70f706a9c2b80040c9485", + "0xae0a42a222f1a71cd0d3c69ffb2f04c13e1940cce8efabe032629f650be3ceed6abb79651dbb81cb39a33286eb517639", + "0xa07d7d76460f31f5f0e32e40a5ea908d9d2aebf111ac4fadee67ef6540b916733c35a777dcdc05f6417726ca1f2d57dd", + "0x88d7f8a31f8c99794291847d28745e5d0b5d3b9684ca4170b686ffbb5bb521a3ef6746c3c8db22e4250a0cdff7939d96", + "0x849573071fd98c020dc9a8622a9eff221cb9f889bde259e7127a8886b73bef7ad430b87750915658918dcfb6b7b4d8d3", + "0xb12d59f732fa47fad175d6263734da8db89230fd340a46ad1cdee51e577041a5c80bf24cd195593e637daf1a66ef5a98", + "0xabbcfb8a4a6d5e269ee1ac5e277df84416c73ca55ec88317f73608201af25af0cb65b943c54684a5651df3a26e3daca2", + "0xab157f589bdbaf067a6a7ba7513df0492933855d39f3a081196cf2352e0ddc0162d476c433320366e3df601e0556278d", + "0xa86c0619b92e5ae4f7daa876a2abc5ba189156afc2fa05eef464dfa342ba37fc670d0dc308ad3822fcb461ab001bac30", + "0xa3f292946476cfe8d5e544a5325439a00e0165a5f9bf3bb6a53f477baeac7697cc0377745536681aa116f326ce911390", + "0x8aecbbfd442a6a0f01c1c09db5d9d50213eb6f1ff6fab674cde3da06a4edff3ed317e804f78300c22ef70c336123e05d", + "0x834ed4b58211fcd647d7bf7c0a3ba9085184c5c856b085e8a0fcd5215c661ef43d36f3f0f6329a9f1370501b4e73b6e4", + "0xa114ea5ad2b402a0de6105e5730907f2f1e458d28ae35144cf49836e0ad21325fe3e755cfb67984ae0a32e65402aad1e", + "0xa005f12bed97d71cee288b59afe9affb4d256888727343944a99913980df2c963fe02f218e6ea992f88db693a4498066", + "0xa010f286ab06b966e3b91ff8f1bdbe2fe9ab41a27bc392d5787aa02a46e5080e58c62c7d907818caae9f6a8b8123e381", + "0x857bd6df2ddef04dbc7c4f923e0b1696d3016c8bfed07fdfa28a3a3bd62d89b0f9df49aae81cbb6883d5e7b4fadae280", + "0xb3927030da445bc4756ac7230a5d87412a4f7510581fb422212ce2e8cf49689aca7ba71678743af06d4de4914c5aa4a0", + "0xb86403182c98fcce558d995f86752af316b3b2d53ba32075f71c7da2596747b7284c34a1a87de604fcc71e7e117a8add", + "0x98dd19b5527733041689b2a4568edaf6aa0fe1a3dd800c290cda157b171e053648a5772c5d3d4c80e5a795bc49adf12e", + "0x88a3c227bb7c9bff383f9ad3f7762245939a718ab85ae6e5e13180b12bf724d42054d3852b421c1cd1b3670baddecb63", + "0xb3cfd9ad66b52bbe57b5fff0fad723434d23761409b92c4893124a574acc1e6b1e14b4ec507661551cbbe05e16db362e", + "0x923e1bb482cf421dd77801f9780f49c3672b88508a389b94015fd907888dc647ee9ea8ec8d97131d235d066daf1f42b7", + "0x8d5e16240f04f92aa948181d421006bdbc7b215648fb6554193224d00cf337ebbb958f7548cf01b4d828acffb9fbc452", + "0x8b2b8f18ad0559746f6cda3acca294a1467fb1a3bc6b6371bc3a61a3bfe59418934fa8706f78b56005d85d9cb7f90454", + "0xa9316e2a94d6e31426d2ae7312878ba6baaac40f43e2b8a2fa3ab5a774c6918551554b2dbb23dc82f70ba3e0f60b5b0d", + "0x9593116d92cf06b8cd6905a2ce569ee6e69a506c897911f43ae80fc66c4914da209fc9347962034eebbc6e3e0fe59517", + "0x887d89d2b2d3c82b30e8f0acf15f0335532bd598b1861755498610cb2dd41ff5376b2a0bb757cb477add0ce8cfe7a9fc", + "0xb514cfe17875ecb790ad055271cc240ea4bda39b6cfa6a212908849c0875cb10c3a07826550b24c4b94ea68c6bb9e614", + "0xa563d5187966d1257d2ed71d53c945308f709bcc98e3b13a2a07a1933dc17bcb34b30796bd68c156d91811fbd49da2cb", + "0xa7195ccc53b58e65d1088868aeeb9ee208103e8197ad4c317235bb2d0ad3dc56cb7d9a7186416e0b23c226078095d44c", + "0xa838e7a368e75b73b5c50fbfedde3481d82c977c3d5a95892ac1b1a3ea6234b3344ad9d9544b5a532ccdef166e861011", + "0x9468ed6942e6b117d76d12d3a36138f5e5fb46e3b87cf6bb830c9b67d73e8176a1511780f55570f52d8cdb51dcf38e8c", + "0x8d2fc1899bc3483a77298de0e033085b195caf0e91c8be209fd4f27b60029cbe1f9a801fbd0458b4a686609762108560", + "0x8f4e44f8ca752a56aa96f3602e9234ad905ad9582111daf96a8c4d6f203bf3948f7ce467c555360ad58376ee8effd2ba", + "0x8fb88640b656e8f1c7c966c729eb2ba5ccf780c49873f8b873c6971840db7d986bdf1332ba80f8a0bb4b4ee7401468fa", + "0xb72aa3235868186913fb5f1d324e748cd3ce1a17d3d6e6ea7639a5076430fe0b08841c95feb19bb94181fe59c483a9eb", + "0xb8b102690ebb94fc4148742e7e3fd00f807b745b02cbe92cd92992c9143b6db7bb23a70da64a8b2233e4a6e572fc2054", + "0x8c9ae291f6cd744e2c6afe0719a7fc3e18d79307f781921fb848a0bf222e233879c1eca8236b4b1be217f9440859b6ce", + "0xa658ede47e14b3aad789e07f5374402f60e9cacb56b1b57a7c6044ca2418b82c98874e5c8c461898ebd69e38fecd5770", + "0x89c0cb423580e333923eb66bda690f5aca6ec6cba2f92850e54afd882ba608465a7dbb5aa077cd0ca65d9d00909348ab", + "0xaed8e28d98d5508bd3818804cf20d296fe050b023db2ed32306f19a7a3f51c7aaafed9d0847a3d2cd5ba5b4dabbc5401", + "0x96a0fcd6235f87568d24fb57269a94402c23d4aa5602572ad361f3f915a5f01be4e6945d576d51be0d37c24b8b0f3d72", + "0x935d0c69edd5dfa8ed07c49661b3e725b50588f814eb38ea31bcc1d36b262fae40d038a90feff42329930f8310348a50", + "0x900518288aa8ea824c7042f76710f2ea358c8bb7657f518a6e13de9123be891fa847c61569035df64605a459dad2ecc8", + "0x947d743a570e84831b4fb5e786024bd752630429d0673bf12028eb4642beb452e133214aff1cfa578a8856c5ebcb1758", + "0xa787266f34d48c13a01b44e02f34a0369c36f7ec0aae3ec92d27a5f4a15b3f7be9b30b8d9dd1217d4eeedff5fd71b2e5", + "0xa24b797214707ccc9e7a7153e94521900c01a1acd7359d4c74b343bfa11ea2cdf96f149802f4669312cd58d5ab159c93", + "0x97f5ee9c743b6845f15c7f0951221468b40e1edaef06328653a0882793f91e8146c26ac76dd613038c5fdcf5448e2948", + "0x80abd843693aed1949b4ea93e0188e281334163a1de150c080e56ca1f655c53eb4e5d65a67bc3fc546ed4445a3c71d00", + "0x908e499eb3d44836808dacff2f6815f883aeced9460913cf8f2fbbb8fe8f5428c6fc9875f60b9996445a032fd514c70f", + "0xae1828ef674730066dc83da8d4dd5fa76fc6eb6fa2f9d91e3a6d03a9e61d7c3a74619f4483fe14cddf31941e5f65420a", + "0xa9f4dbe658cd213d77642e4d11385a8f432245b098fccd23587d7b168dbeebe1cca4f37ee8d1725adb0d60af85f8c12f", + "0x93e20ee8a314b7772b2439be9d15d0bf30cd612719b64aa2b4c3db48e6df46cea0a22db08ca65a36299a48d547e826a7", + "0xa8746a3e24b08dffa57ae78e53825a9ddbbe12af6e675269d48bff4720babdc24f907fde5f1880a6b31c5d5a51fbb00e", + "0xb5e94dfab3c2f5d3aea74a098546aa6a465aa1e3f5989377d0759d1899babf543ad688bb84811d3e891c8713c45886c5", + "0xa3929bada828bd0a72cda8417b0d057ecb2ddd8454086de235540a756e8032f2f47f52001eb1d7b1355339a128f0a53b", + "0xb684231711a1612866af1f0b7a9a185a3f8a9dac8bde75c101f3a1022947ceddc472beb95db9d9d42d9f6ccef315edbc", + "0xaf7809309edbb8eb61ef9e4b62f02a474c04c7c1ffa89543d8c6bf2e4c3d3e5ecbd39ec2fc1a4943a3949b8a09d315a6", + "0xb6f6e224247d9528ef0da4ad9700bee6e040bbf63e4d4c4b5989d0b29a0c17f7b003c60f74332fefa3c8ddbd83cd95c1", + "0xadbcec190a6ac2ddd7c59c6933e5b4e8507ce5fd4e230effc0bd0892fc00e6ac1369a2115f3398dfc074987b3b005c77", + "0x8a735b1bd7f2246d3fa1b729aecf2b1df8e8c3f86220a3a265c23444bdf540d9d6fe9b18ed8e6211fad2e1f25d23dd57", + "0x96b1bf31f46766738c0c687af3893d098d4b798237524cb2c867ed3671775651d5852da6803d0ea7356a6546aa9b33f2", + "0x8036e4c2b4576c9dcf98b810b5739051de4b5dde1e3e734a8e84ab52bc043e2e246a7f6046b07a9a95d8523ec5f7b851", + "0x8a4f4c32ee2203618af3bb603bf10245be0f57f1cfec71037d327fa11c1283b833819cb83b6b522252c39de3ce599fa5", + "0xad06ed0742c9838e3abaaffdb0ac0a64bad85b058b5be150e4d97d0346ed64fd6e761018d51d4498599669e25a6e3148", + "0x8d91cb427db262b6f912c693db3d0939b5df16bf7d2ab6a7e1bc47f5384371747db89c161b78ff9587259fdb3a49ad91", + "0xae0a3f84b5acb54729bcd7ef0fbfdcf9ed52da595636777897268d66db3de3f16a9cf237c9f8f6028412d37f73f2dfad", + "0x8f774109272dc387de0ca26f434e26bc5584754e71413e35fa4d517ee0f6e845b83d4f503f777fe31c9ec05796b3b4bc", + "0xa8670e0db2c537ad387cf8d75c6e42724fae0f16eca8b34018a59a6d539d3c0581e1066053a2ec8a5280ffabad2ca51f", + "0xac4929ed4ecad8124f2a2a482ec72e0ef86d6a4c64ac330dab25d61d1a71e1ee1009d196586ce46293355146086cabba", + "0x845d222cb018207976cc2975a9aa3543e46c861486136d57952494eb18029a1ebb0d08b6d7c67c0f37ee82a5c754f26f", + "0xb99fa4a29090eac44299f0e4b5a1582eb89b26ed2d4988b36338b9f073851d024b4201cd39a2b176d324f12903c38bee", + "0x9138823bc45640b8f77a6464c171af2fe1700bdc2b7b88f4d66b1370b3eafe12f5fbb7b528a7e1d55d9a70ca2f9fc8e6", + "0x8ac387dc4cf52bc48a240f2965ab2531ae3b518d4d1f99c0f520a3d6eb3d5123a35ef96bed8fa71ee2f46793fa5b33b3", + "0x864adec6339d4c2ba2525621fceabd4c455902f6f690f31a26e55413e0722e5711c509dc47ce0bcc27bbdc7651768d2d", + "0xa0a52edb72268a15201a968dabc26a22909620bda824bd548fb8c26cc848f704166ed730d958f0173bd3b0a672f367bd", + "0x949e445b0459983abd399571a1a7150aab3dd79f4b52a1cd5d733e436c71c1d4b74287c6b0ce6cc90c6711ba4c541586", + "0x858966355dac11369e3b6552f2b381665181693d5a32e596984da3314021710b25a37d8c548b08700eea13d86cb22f21", + "0x974bcbb8d38c5e6518745cc03ad436e585b61f31d705e7e2e5085da9655d768ac4d800904f892c3dab65d6223e3f1fd6", + "0x8092b6506b01308bf6187fde5ebd4fa7448c9a640961ba231be22ac5fa2c7635ef01e8b357722c7695d09b723101ea2a", + "0xa5b8ef360bf28533ee17d8cd131fff661d265f609db49599085c0c7d83b0af409a1b5c28e3a5e5d7f8459a368aa121e8", + "0xb031b6d5e3ceab0f0c93314b3b675f55cf18cbc86f70444af266fe39cb22fd7dad75d8c84e07f1c1bfa2cb8283e1361a", + "0x93ad489e4f74658320c1cceed0137c023d3001a2c930ed87e6a21dbf02f2eb6ad1c1d8bcb3739c85dcfbecb040928707", + "0xb15e4ec2cdab0d34aec8d6c50338812eb6ecd588cf123a3e9d22a7ca23b5a98662af18289f09e6cdd85a39a2863c945c", + "0xb304f71a9717cf40c22073f942618b44bf27cd5e2ed4a386ad45d75b0fcb5a8dafd35158211eaf639495c6f1a651cedb", + "0xb82d78d3eaaa7c5101b7a5aae02bd4f002cd5802d18c3abcda0dd53b036661c6d3c8b79e0abe591eab90b6fdc5fef5e3", + "0xabbd1884243a35578b80914a5084449c237ee4e4660c279d1073a4d4217d1b55c6b7e9c087dfd08d94ac1416273d8d07", + "0x92f4b61c62502745e3e198ec29bca2e18696c69dcb914d1f3a73f4998d012b90caf99df46e9bb59942e43cce377fe8fd", + "0x906e79df98185820c8208844e1ba6bd86cb96965814b01310bd62f22cbec9b5d379b2ef16772d6fc45a421b60cfd68fe", + "0xa0eae2784ef596e2eb270dd40c48d6c508e4394c7d6d08d4cc1b56fde42b604d10ba752b3a80f2c4a737e080ef51b44f", + "0x94c084985e276dc249b09029e49a4ef8a369cd1737b51c1772fbb458d61e3fe120d0f517976eba8ffa5711ba93e46976", + "0x83619a0157eff3f480ab91d1d6225fead74c96a6fd685333f1e8e4d746f6273e226bad14232f1d1168a274e889f202f1", + "0xa724fe6a83d05dbbf9bb3f626e96db2c10d6d5c650c0a909415fbda9b5711c8b26e377201fb9ce82e94fa2ab0bf99351", + "0xa8a10c1b91a3a1fa2d7fd1f78a141191987270b13004600601d0f1f357042891010717319489f681aa8a1da79f7f00d5", + "0xa398a2e95b944940b1f8a8e5d697c50e7aa03994a8a640dfad4ea65cfb199a4d97861a3ec62d1c7b2b8d6e26488ca909", + "0xa2eedfe5452513b2a938fffd560798ef81379c5a5032d5b0da7b3bb812addbaad51f564c15d9acbbfc59bb7eddd0b798", + "0xab31c572f6f145a53e13b962f11320a1f4d411739c86c88989f8f21ab629639905b3eedb0628067942b0dc1814b678ca", + "0xad032736dd0e25652d3566f6763b48b34ea1507922ed162890cd050b1125ec03b6d41d34fccba36ec90336f7cdf788ed", + "0x83028a558a5847293147c483b74173eca28578186137df220df747fccd7d769528d7277336ea03c5d9cdd0bc5ae3d666", + "0xab5d182cd1181de8e14d3ef615580217c165e470b7a094a276b78a3003089123db75c6e1650bf57d23e587c587cd7472", + "0xa4793e089fbdb1597654f43b4f7e02d843d4ab99ee54099c3d9f0bd5c0c5657c90bb076379a055b00c01b12843415251", + "0x98bdc52ee062035356fb2b5c3b41673198ddc60b2d1e546cb44e3bb36094ef3c9cf2e12bbc890feb7d9b15925439d1ea", + "0xa4f90cca6f48024a0341bd231797b03693b34e23d3e5b712eb24aba37a27827319b2c16188f97c0636a0c115381dc659", + "0x8888e6c2e4a574d04ba5f4264e77abc24ccc195f1a7e3194169b8a2ceded493740c52db4f9833b3dbf4d67a3c5b252cb", + "0x83dc4e302b8b0a76dc0292366520b7d246d73c6aebe1bdd16a02f645c082197bcff24a4369deda60336172cefbcf09af", + "0xa4eb2741699febfeb793914da3054337cc05c6fa00d740e5f97cb749ae16802c6256c9d4f0f7297dcdbb8b9f22fc0afa", + "0x8b65557d5be273d1cb992a25cfce40d460c3f288d5cb0a54bdef25cbd17cdea5c32ec966e493addf5a74fd8e95b23e63", + "0x97c6577e76c73837bcb398b947cb4d3323d511141e0ddd0b456f59fbb1e8f920a5c20d7827a24309145efddee786140f", + "0xabcc0849ffe2a6a72157de907907b0a52deece04cf8317bee6fe1d999444b96e461eac95b6afde3d4fe530344086a625", + "0x9385c0115cb826a49df1917556efa47b5b5e4022b6a0d2082053d498ec9681da904ecf375368bb4e385833116ea61414", + "0x8b868c1841f0cdc175c90a81e610b0652c181db06731f5c8e72f8fafa0191620742e61a00db8215a991d60567b6a81ca", + "0xa8df15406f31b8fcf81f8ff98c01f3df73bf9ec84544ddec396bdf7fafa6fe084b3237bf7ef08ad43b26517de8c3cd26", + "0xa9943d21e35464ce54d4cc8b135731265a5d82f9ccf66133effa460ffdb443cdb694a25320506923eede88d972241bf2", + "0xa1378ee107dd7a3abcf269fd828887c288363e9b9ca2711377f2e96d2ed5e7c5ec8d3f1da995a3dcbedf1752d9c088fc", + "0x8a230856f9227b834c75bdebc1a57c7298a8351874bf39805c3e0255d6fd0e846f7ad49709b65ec1fd1a309331a83935", + "0x877bcf42549d42610e1780e721f5800972b51ba3b45c95c12b34cb35eeaf7eac8fa752edd7b342411820cf9093fea003", + "0x84c7a0b63842e50905624f1d2662506b16d1f3ea201877dfc76c79181c338b498eceb7cad24c2142c08919120e62f915", + "0x8e18b1bd04b1d65f6ed349b5d33a26fe349219043ead0e350b50ae7a65d6ff5f985dd9d318d3b807d29faa1a7de4fe42", + "0x8ea7b5a7503e1f0b3c3cd01f8e50207044b0a9c50ed1697794048bbe8efd6659e65134d172fb22f95439e1644f662e23", + "0xb1954a2818cad1dad6d343a7b23afa9aa8ad4463edc4eb51e26e087c2010927535020d045d97d44086d76acdb5818cbf", + "0xa5271ea85d0d21fa1ff59b027cf88847c0f999bbf578599083ff789a9b5228bc161e1c81deb97e74db1a82a0afd61c50", + "0xaa2fa4c05af3387e2c799315781d1910f69977ec1cfea57a25f1a37c63c4daaa3f0ecd400884a1673e17dd5300853bcf", + "0xb1cd2a74ca0b8e6090da29787aef9b037b03b96607983a308b790133bd21297b21ca4e2edec890874096dbf54e9d04c3", + "0x801931607ec66a81272feaa984f0b949ad12d75ecf324ba96627bd4dc5ddead8ebf088f78e836b6587c2b6c0b3366b6c", + "0x95d79504710bdf0ad9b9c3da79068c30665818c2f0cdbba02cc0a5e46e29d596032ac984441b429bd62e34535c8d55b0", + "0x9857d41e25e67876510ff8dadf0162019590f902da1897da0ef6fc8556e3c98961edb1eb3a3a5c000f6c494413ded15e", + "0x8740c9ffe6bd179c19a400137c3bd3a593b85bd4c264e26b4dfb9e2e17ac73e5b52dfacc1dcb4033cfc0cd04785f4363", + "0x977f98f29d948b4097a4abdf9345f4c1fb0aa94ba0c6bf6faa13b76f3a3efc8f688e1fe96099b71b3e1c05041118c8d1", + "0xa364422b1239126e3e8d7b84953ce2181f9856319b0a29fcab81e17ac27d35798088859c1cfc9fc12b2dbbf54d4f70b3", + "0xa0f6ba637f0db7a48e07439bb92ddb20d590ce9e2ed5bab08d73aa22d82c32a9a370fe934cbe9c08aeb84b11adcf2e0e", + "0xa2c548641bd5b677c7748327cca598a98a03a031945276be6d5c4357b6d04f8f40dd1c942ee6ec8499d56a1290ac134d", + "0x9863e9cc5fbcdbd105a41d9778d7c402686bfd2d81d9ed107b4fda15e728871c38647529693306855bee33a00d257a7e", + "0xa54173bf47b976290c88fd41f99300135de222f1f76293757a438450880e6f13dbde3d5fe7afc687bdfbcfc4fbc1fc47", + "0xb8db413917c60907b73a997b5ab42939abd05552c56a13525e3253eb72b83f0d5cc52b695968a10005c2e2fe13290e61", + "0xa1f8388ef21697c94ba90b1a1c157f0dc138e502379e6fc5dc47890d284563e5db7716266e1b91927e5adf3cde4c0a72", + "0x9949013a59d890eb358eab12e623b2b5edb1acbee238dfad8b7253102abc6173922e188d5b89ec405aa377be8be5f16d", + "0xa00fdb7710db992041f6ddb3c00099e1ce311dea43c252c58f560c0d499983a89de67803a8e57baa01ee9d0ee6fa1e44", + "0xa8b1bcbed1951c9cdb974b61078412881b830b48cd6b384db0c00fa68bcc3f4312f8e56c892ea99d3511857ef79d3db9", + "0x8f3ee78404edc08af23b1a28c2012cee0bdf3599a6cb4ea689fc47df4a765ef519191819a72562b91a0fbcdb896a937e", + "0x8155bbb7fa8d386848b0a87caae4da3dec1f3dade95c750a64a8e3555166ccc8799f638bd80ed116c74e3a995541587a", + "0xabfe30adbc0a6f1fd95c630ed5dac891b85384fa9331e86b83217f29dff0bd7cad19d328485715a7e3df9a19069d4d2f", + "0x89d0783e496ee8dbb695764b87fb04cee14d4e96c4ba613a19736971c577d312079048142c12ce5b32b21e4d491d281b", + "0x856b8dbc9c5d8f56b6bb7d909f339ca6da9a8787bba91f09130a025ab6d29b64dbf728ba6ed26e160a23c1cdb9bc037b", + "0x8a30dd2ea24491141047a7dfe1a4af217661c693edf70b534d52ca547625c7397a0d721e568d5b8398595856e80e9730", + "0xae7e1412feb68c5721922ed9279fb05549b7ef6812a4fd33dbbbd7effab756ab74634f195d0c072143c9f1fd0e1ee483", + "0xb7ce970e06fa9832b82eef572f2902c263fda29fdce9676f575860aae20863046243558ede2c92343616be5184944844", + "0x85ed0531f0e5c1a5d0bfe819d1aa29d6d5ff7f64ad8a0555560f84b72dee78e66931a594c72e1c01b36a877d48e017ca", + "0xb8595be631dc5b7ea55b7eb8f2982c74544b1e5befc4984803b1c69727eac0079558182f109e755df3fd64bee00fcaa5", + "0x99e15a66e5b32468ef8813e106271df4f8ba43a57629162832835b8b89402eb32169f3d2c8de1eb40201ce10e346a025", + "0x844c6f5070a8c73fdfb3ed78d1eddca1be31192797ad53d47f98b10b74cc47a325d2bc07f6ee46f05e26cf46a6433efb", + "0x974059da7f13da3694ad33f95829eb1e95f3f3bfc35ef5ef0247547d3d8ee919926c3bd473ab8b877ff4faa07fcc8580", + "0xb6f025aecc5698f6243cc531782b760f946efebe0c79b9a09fe99de1da9986d94fa0057003d0f3631c39783e6d84c7d5", + "0xb0c5358bc9c6dfe181c5fdf853b16149536fbb70f82c3b00db8d854aefe4db26f87332c6117f017386af8b40288d08f9", + "0xa3106be5e52b63119040b167ff9874e2670bd059b924b9817c78199317deb5905ae7bff24a8ff170de54a02c34ff40a4", + "0xad846eb8953a41c37bcd80ad543955942a47953cbc8fb4d766eac5307892d34e17e5549dc14467724205255bc14e9b39", + "0xb16607e7f0f9d3636e659e907af4a086ad4731488f5703f0917c4ce71a696072a14a067db71a3d103530920e1ec50c16", + "0x8ed820e27116e60c412c608582e9bb262eaaf197197c9b7df6d62b21a28b26d49ea6c8bb77dfde821869d9b58025f939", + "0x97bc25201d98cde389dd5c0c223a6f844393b08f75d3b63326343073e467ac23aacef630ddc68545ea874299ba4a3b4f", + "0xb73c9695ad2eefd6cc989a251c433fab7d431f5e19f11d415a901762717d1004bb61e0cc4497af5a8abf2d567e59fef4", + "0xadaabe331eea932533a7cc0cf642e2a5e9d60bbc92dd2924d9b429571cbf0d62d32c207b346607a40643c6909b8727e2", + "0xa7b1bbfe2a5e9e8950c7cb4daab44a40c3ffab01dc012ed7fe445f4af47fa56d774a618fafe332ab99cac4dfb5cf4794", + "0xb4a3c454dcd5af850212e8b9ba5fe5c0d958d6b1cabbf6c6cfe3ccbc4d4c943309c18b047256867daf359006a23f3667", + "0xa5c0b32f6cef993834c1381ec57ad1b6f26ae7a8190dd26af0116e73dadc53bb0eeb1911419d609b79ce98b51fdc33bc", + "0xac2f52de3ecf4c437c06c91f35f7ac7d171121d0b16d294a317897918679f3b9db1cef3dd0f43adb6b89fe3030728415", + "0x94722ae6d328b1f8feaf6f0f78804e9b0219de85d6f14e8626c2845681841b2261d3e6a2c5b124086b7931bf89e26b46", + "0xa841a0602385d17afabca3a1bb6039167d75e5ec870fea60cfcaec4863039b4d745f1a008b40ec07bca4e42cb73f0d21", + "0x8c355f0a1886ffced584b4a002607e58ff3f130e9de827e36d38e57cb618c0cb0b2d2dea2966c461cb3a3887ede9aef1", + "0xa6a9817b0fc2fd1786f5ba1a7b3d8595310987fb8d62f50a752c6bb0b2a95b67d03a4adfd13e10aa6190a280b7ee9a67", + "0xa1d2e552581ecbafeaef08e389eaa0b600a139d446e7d0648ac5db8bbbf3c438d59497e3a2874fc692b4924b87ff2f83", + "0xa1b271c55389f25639fe043e831e2c33a8ba045e07683d1468c6edd81fedb91684e4869becfb164330451cfe699c31a8", + "0x8c263426e7f7e52f299d57d047a09b5eeb893644b86f4d149535a5046afd655a36d9e3fdb35f3201c2ccac2323a9582e", + "0xb41c242a7f7880c714241a97d56cce658ee6bcb795aec057a7b7c358d65f809eb901e0d51256826727dc0dc1d1887045", + "0x93001b9445813c82f692f94c0dc1e55298f609936b743cf7aae5ebfa86204f38833d3a73f7b67314be67c06a1de5682d", + "0x82087536dc5e78422ad631af6c64c8d44f981c195ddea07d5af9bb0e014cdc949c6fa6e42fce823e0087fdb329d50a34", + "0x8e071861ceba2737792741c031f57e0294c4892684506b7c4a0fc8b2f9a0a6b0a5635de3d1e8716c34df0194d789ae86", + "0xb471c997e1e11774bd053f15609d58838a74073a6c089a7a32c37dd3f933badf98c7e5833263f3e77bc0d156a62dd750", + "0x8d2d8686fb065b61714414bb6878fff3f9e1e303c8e02350fd79e2a7f0555ded05557628152c00166ce71c62c4d2feaa", + "0xae4c75274d21c02380730e91de2056c0262ffcecf0cbdb519f0bdb0b5a10ae2d4996b3dc4b3e16dbaea7f0c63d497fef", + "0x97140d819e8ca6330e589c6debdee77041c5a9cedb9b8cbd9c541a49207eeb7f6e6b1c7e736ec8ba6b3ab10f7fcd443a", + "0xaf6659f31f820291a160be452e64d1293aa68b5074b4c066dac169b8d01d0179139504df867dc56e2a6120354fc1f5be", + "0xa5e5d8088a368024617bfde6b731bf9eee35fc362bed3f5dfdd399e23a2495f97f17728fec99ca945b3282d1858aa338", + "0xa59cfc79d15dbdde51ab8e5129c97d3baba5a0a09272e6d2f3862370fdbaf90994e522e8bd99d6b14b3bb2e9e5545c6f", + "0xa30499b068083b28d6c7ddcc22f6b39b5ec84c8ee31c5630822c50ea736bb9dca41c265cffc6239f1c9ef2fd21476286", + "0x88ffe103eca84bbe7d1e39a1aa599a5c7c9d5533204d5c4e085402a51441bb8efb8971efe936efbbfa05e5cb0d4b8017", + "0xb202356fbf95a4d699154639e8cb03d02112c3e0128aab54d604645d8510a9ba98936028349b661672c3a4b36b9cb45d", + "0x8b89bb6574bf3524473cff1ff743abcf1406bd11fb0a72070ccd7d8fce9493b0069fb0c6655252a5164aee9e446ea772", + "0x93247b1038fa7e26667ee6446561d4882dc808d1015daafb705935ddc3598bb1433182c756465960480f7b2de391649e", + "0xb027f94d3358cbb8b6c8c227300293a0dee57bf2fee190a456ad82ecfb6c32f8090afa783e2ab16f8139805e1fb69534", + "0xa18bb1849b2f06c1d2214371031d41c76ffa803ee3aa60920d29dbf3db5fbfac2b7383d5d0080ba29ce25c7baa7c306b", + "0x827bf9fd647e238d5ac961c661e5bbf694b4c80b3af8079f94a2484cb8fba2c8cf60e472ebcd0b0024d98ae80ad2ff5a", + "0x838e891218c626a7f39b8fd546b013587408e8e366ecc636b54f97fa76f0a758bc1effa1d0f9b6b3bc1a7fcc505970a0", + "0x836523b5e8902d6e430c6a12cff01e417d2bd7b402e03904034e3b39755dee540d382778c1abe851d840d318ebedce7f", + "0x850a77dda9ac6c217e2ef00bf386a1adec18b7f462f52801c4f541215690502a77ef7519b690e22fdf54dc2109e0ca38", + "0xa8265c6ae7b29fc2bda6a2f99ced0c1945dd514b1c6ca19da84b5269514f48a4f7b2ccbab65c9107cfd5b30b26e5462f", + "0xab3d02ee1f1267e8d9d8f27cc388e218f3af728f1de811242b10e01de83471a1c8f623e282da5a284d77884d9b8cde0e", + "0x831edaf4397e22871ea5ddee1e7036bab9cc72f8d955c7d8a97f5e783f40532edbbb444d0520fefcffeab75677864644", + "0x80484487977e4877738744d67b9a35b6c96be579a9faa4a263e692295bb6e01f6e5a059181f3dd0278e2c3c24d10a451", + "0xaae65a18f28c8812617c11ecf30ad525421f31fb389b8b52d7892415e805a133f46d1feca89923f8f5b8234bd233486a", + "0xb3a36fd78979e94288b4cefed82f043a7e24a4a8025479cc7eb39591e34603048a41ee606ee03c0b5781ebe26a424399", + "0xb748b3fc0d1e12e876d626a1ba8ad6ad0c1f41ea89c3948e9f7d2666e90173eb9438027fadcd741d3ae0696bd13840f1", + "0xacdd252d7c216c470683a140a808e011c4d5f1b4e91aeb947f099c717b6a3bad6651142cde988330827eb7d19d5fb25c", + "0xb9a25556a6ca35db1ed59a1ec6f23343eab207a3146e4fc3324136e411c8dba77efd567938c63a39c2f1c676b07d8cdb", + "0xa8db6aef8f5680d2bdb415d7bcaae11de1458678dcb8c90c441d5986c44f83a9e5855662d0c1aace999172d8628d8fe1", + "0xaf58147108e9909c3a9710cc186eab598682dca4bfd22481e040b8c000593ecb22c4ede4253ac9504e964dfa95a9b150", + "0x8dd8bb70f1c9aec0fcc9478f24dfc9c3c36c0bf5ff7a67c017fa4dab2ec633fbd7bc9d8aa41ea63e2696971ed7e375f5", + "0xaa98d600b22aff993a4d7a3ccabd314e1825b200cb598f6b797d7e4d6a76d89e34a4d156c06bddfc62f2ef9b4c809d1d", + "0x8a8fc960d6c51294b8205d1dabe430bef59bda69824fa5c3c3105bef22ac77c36d2d0f38ffc95ce63731de5544ccbeff", + "0xb6d1020efe01dc8032bd1b35e622325d7b9af9dcd5c9c87c48d7d6ebc58644454294c59b7f4b209204b5b1f899f473bf", + "0x8a750dc9fe4891f2dfe5759fb985939810e4cdc0b4e243ff324b6143f87676d8cb4bcb9dfb01b550801cedcaaa5349e2", + "0x98c13142d3a9c5f8d452245c40c6dae4327dd958e0fda85255ea0f87e0bcbaa42a3a0bd50407ed2b23f9f6317a8a4bc5", + "0x99f2b83d9ec4fc46085a6d2a70fd0345df10f4a724c1ba4dee082a1fde9e642e3091992ebf5f90a731abcb6ec11f6d9b", + "0xb218546ab2db565b2489ea4205b79daa19ef2acbf772ccaaa5e40150e67ea466090d07198444b48e7109939aa2319148", + "0x84f9d1d868e4b55e535f1016558f1789df0daa0ead2d13153e02f715fe8049b1ce79f5bc1b0bbbb0b7e4dd3c04783f3f", + "0x80d870d212fbddfdda943e90d35a5a8aa0509a7a1e7f8909f2fcb09c51c3026be47cc7a22620a3063406872105b4f81a", + "0xb5b15138ff6551fac535d4bbce2ea6adc516b6b7734b4601c66ec029da2615e3119dc9ad6a937344acfd7b50e4a1a2ae", + "0x95d2f97652086e7ceb54e1d32692b1c867ffba23c4325740c7f10d369283d1b389e8afa0df967831ade55696931e7934", + "0x8a5b580403e1a99cd208f707e8ce0d3f658c8280417683f69008d09cc74d835a85f7380f391b36ead9ac66d9eedd1cbe", + "0xa8b0c90bff34c86720637b5a2081f0f144cfe2205c1176cacd87d348609bc67af68aed72414dc9aa6f44a82c92c2a890", + "0x865abbdd96c496892c165a8de0f9e73348bf24fce361d7a9048710178a3625881afb0006e9f5ee39124866b87904c904", + "0xace67bb994adef4b6f841cdf349195608030044562780a7e9b00b58a4ff117268a03ff01e5a3a9d9d7eff1dd01f5f4bf", + "0xb9371d59185b3d2d320d3fefeadb06ba2aa7d164352fb8dc37571509509fa214d736d244ac625a09a033a10d51611e2e", + "0xa8ef992771422dcf2d6d84386fde9fe5dba88bfded3dfcd14074ca04331b4fd53a7f316615cdfaf10ed932cbb424a153", + "0x868cbc75f8f789ea45eded2768a1dac0763347e0d8e8028d316a21005f17be179d26d5965903e51b037f2f57fe41765d", + "0xb607111bcdfd05fa144aa0281b13ee736079ebbbf384d938a60e5e3579639ed8ef8eb9ca184868cdb220a8e130d4a952", + "0xaca55702af5cae4cae65576769effd98858307a71b011841c563b97c2aa5aeb5c4f8645d254f631ed1582df3dbbf17da", + "0xb9b5cbace76246e80c20dfcc6f1e2c757a22ab53f7fd9ff8a1d309538b55174e55e557a13bf68f095ff6a4fa637ef21a", + "0x8571b0a96871f254e2397c9be495c76379faf347801cb946b94e63212d6a0da61c80e5d7bebbabcd6eaa7f1029172fe5", + "0x902540326281e6dc9c20d9c4deaaf6fbbbcc3d1869bd0cf7f081c0525bea33df5cfa24ead61430fda47fb964fcc7994b", + "0x841af09279d3536a666fa072278950fabf27c59fc15f79bd52acb078675f8087f657929c97b4bc761cbade0ecb955541", + "0xa1f958b147ddf80ab2c0746ba11685c4bae37eb25bfa0442e7e1078a00d5311d25499da30f6d168cb9302ea1f2e35091", + "0x863d939381db37d5a5866964be3392a70be460f0353af799d6b3ed6307176972686bd378f8ad457435a4094d27e8dfb7", + "0x835cd4d7f36eff553d17483eb6c041b14280beb82c7c69bca115929658455a1931212976c619bafb8179aed9940a8cc6", + "0x8d0770e3cb8225e39c454a1fc76954118491b59d97193c72c174ecc7613051e5aed48a534016a8cf0795c524f771a010", + "0x91aa4edb82f6f40db2b7bd4789cc08786f6996ebed3cb6f06248e4884bc949793f04a4c5ea6eefe77984b1cc2a45d699", + "0x8fb494ca2449f659ff4838833507a55500a016be9293e76598bbae0a7cb5687e4693757c2b6d76e62bd6c7f19ed080bb", + "0xb59b104449a880a282c1dd6a3d8debb1d8814ef35aab5673c1e500ee4cb0e840fb23e05fa5a0af92509c26b97f098f90", + "0xaca908e3bad65e854ae6be6c5db441a06bcd47f5abafdfa8f5a83c8cd3c6e08c33cab139c45887887a478338e19ceb9f", + "0x806f5d802040313a31964fc3eb0ee18ac91b348685bed93c13440984ee46f3d2da7194af18c63dea4196549129660a4e", + "0xae4b2dca75c28d8f23b3ab760b19d839f39ff5a3112e33cb44cff22492604a63c382b88ec67be4b0266924dd438c3183", + "0x99d1c29c6bd8bf384e79cd46e30b8f79f9cbc7d3bf980e9d6ffba048f0fc487cac45c364a8a44bb6027ad90721475482", + "0xa16e861c1af76d35528c25bf804bfc41c4e1e91b2927d07d8e96bffe3a781b4934e9d131ecf173be9399800b8269efac", + "0xa253303234fb74f5829060cdcef1d98652441ab6db7344b1e470d195a95722675988048d840201c3b98e794b1e8b037c", + "0x905ac8a0ea9ce0eb373fb0f83dd4cbe20afb45b9d21ae307846fd4757d4d891b26a6711924e081e2b8151e14a496da18", + "0xb485315791e775b9856cc5a820b10f1fa5028d5b92c2f0e003ba55134e1eddb3eb25f985f2611a2257acf3e7cfdfab5e", + "0xb6189c0458b9a043ebc500abc4d88083a3487b7ac47ed5e13ab2a41e0a1bee50d54a406063f92bc96959f19e822a89a7", + "0xa30e15f995fd099a223fc6dc30dad4b8d40bee00caa2bc3223ba6d53cd717c4968a3e90c4618c711ed37cc4cd4c56cf3", + "0xa1b1ed07fcc350bb12a09cd343768d208fc51a6b3486f0ece8f5a52f8a5810b4bc7ab75582ec0bc2770aed52f68eace5", + "0x88aa739fbae4bece147ba51a863e45d5f7203dbc3138975dc5aef1c32656feb35f014d626e0d5b3d8b1a2bda6f547509", + "0xab570f3c8eabfca325b3a2ea775ef6b0c6e6138c39d53c2310329e8fb162869fde22b0e55688de9eb63d65c37598fca3", + "0x89d274762c02158e27cb37052e296a78f2b643eb7f9ae409f8dac5c587d8b4d82be4ef7c79344a08ebec16ac4a895714", + "0x99c411d2ad531e64f06e604d44c71c7c384424498ecd0a567d31ec380727fb605af76643d0d5513dd0a8d018076dd087", + "0x80d0777fa9f79f4a0f0f937d6de277eec22b3507e2e398f44b16e11e40edf5feff55b3b07a69e95e7e3a1621add5ed58", + "0xb2430a460783f44feb6e4e342106571ef81ad36e3ddd908ec719febeb7acaf4b833de34998f83a1dab8f0137a3744c11", + "0xb8f38ccfc7279e1e30ad7cefc3ea146b0e2dff62430c50a5c72649a4f38f2bac2996124b03af2079d942b47b078cc4f8", + "0xa178a450a62f30ec2832ac13bbc48789549c64fc9d607b766f6d7998558a0e2fad007ae0148fc5747189b713f654e6ba", + "0x98c5ede296f3016f6597f7ccc5f82c88fd38ed6dc3d6da3e4a916bfd7c4c95928722a1d02534fe89387c201d70aa6fd2", + "0xa8cc5e98573705d396576e022b2ba2c3e7c7ece45cd8605cb534b511763682582299e91b4bb4100c967019d9f15bbfaf", + "0x848480ea7b7d9536e469da721236d932870b7bbee31ccf7ae31b4d98d91413f59b94a1e0d1786ee7342295aa3734969c", + "0xb88ea38f9ee432f49e09e4e013b19dff5a50b65453e17caf612155fff6622198f3cba43b2ea493a87e160935aaaf20a9", + "0x949376934a61e0ef8894339c8913b5f3b228fa0ae5c532ad99b8d783b9e4451e4588541f223d87273c0e96c0020d5372", + "0x96f90bb65ca6b476527d32c415814b9e09061648d34993f72f28fae7dc9c197e04ef979f804076d107bb218dfd9cb299", + "0xa4402da95d9942c8f26617e02a7cef0ebc4b757fac72f222a7958e554c82cc216444de93f659e4a1d643b3e55a95d526", + "0x81179cbc26a33f6d339b05ea3e1d6b9e1190bd44e94161ae36357b9cdf1e37d745d45c61735feed64371fe5384102366", + "0xad4dc22bdbd60e147fdac57d98166de37c727f090059cfc33e5ee6cf85e23c2643996b75cf1b37c63f3dc9d3c57ffa18", + "0x8a9b1b93dc56e078ce3bb61c2b0088fd6c3e303ba6b943231cc79d4a8e8572f4109bbde5f5aa7333aae3287909cb0fe2", + "0x8876ef583bc1513322457a4807d03381ba1f4d13e179260eaa3bddfede8df677b02b176c6c9f74c8e6eab0e5edee6de6", + "0xb6c67e228bf190fbaeb2b7ec34d4717ce710829c3e4964f56ebb7e64dc85058c30be08030fa87cc94f1734c5206aef5f", + "0xa00cb53b804ee9e85ce12c0103f12450d977bc54a41195819973c8a06dcb3f46f2bf83c3102db62c92c57ab4dd1e9218", + "0xa7675a64772eefddf8e94636fb7d1d28f277074327c02eea8fae88989de0c5f2dc1efed010f4992d57b5f59a0ab40d69", + "0x8d42bb915e0bf6a62bcdf2d9330eca9b64f9ec36c21ae14bf1d9b0805e5e0228b8a5872be61be8133ad06f11cb77c363", + "0xa5b134de0d76df71af3001f70e65c6d78bed571bc06bfddf40d0baad7ea2767608b1777b7ef4c836a8445949877eeb34", + "0xaeadbc771eaa5de3a353229d33ed8c66e85efbd498e5be467709cb7ff70d3f1a7640002568b0940e3abd7b2da81d2821", + "0x8c28da8e57a388007bd2620106f6226b011ee716a795c5d9f041c810edf9cf7345b2e2e7d06d8a6b6afa1ee01a5badc1", + "0x8ed070626a4d39ffd952ddb177bc68fd35b325312e7c11694c99b691f92a8ea7734aeb96cf9cc73e05b3c1b1dcad6978", + "0xada83e18e4842f3d8871881d5dbc81aed88a1328298bfdc9e28275094bd88d71b02e7b8501c380fa8d93096cbc62f4fb", + "0x8befc3bec82dcf000a94603b4a35c1950ba5d00d4bed12661e4237afa75062aa5dcef8eac0b9803136c76d2dd424a689", + "0x97c6f36c91ca5ca9230bfcbf109d813728b965a29b62e5f54c8e602d14a52ac38fa1270de8bfe1ab365426f3fc3654c7", + "0xb01d192af3d8dbce2fe2fece231449e70eb9ac194ec98e758da11ca53294a0fa8c29b1d23a5d9064b938b259ea3b4fb5", + "0x819a2c20646178f2f02865340db1c3c6ebc18f4e6559dd93aa604388796a34bd9fed28ad3ccc8afc57a5b60bb5c4e4ec", + "0xa9ffc877470afc169fecf9ec2dc33253b677371938b0c4ffa10f77bb80089afa2b4488437be90bb1bcf7586a6f4286e3", + "0xb533051c7ce7107176bcb34ad49fdb41fac32d145854d2fe0a561c200dcf242da484156177e2c8f411c3fdf1559ecf83", + "0x8fe2caff2e4241d353110a3618832f1443f7afe171fd14607009a4a0aa18509a4f1367b67913e1235ac19de15e732eb1", + "0x84705c6370619403b9f498059f9869fdf5f188d9d9231a0cb67b1da2e8c906ead51b934286497293698bba269c48aa59", + "0x899dddf312a37e3b10bdaaacc1789d71d710994b6ee2928ac982ad3fd8a4f6167672bc8bf3419412711c591afe801c28", + "0xb2f7916d946b903ded57b9d57025386143410a41a139b183b70aeca09cf43f5089ead1450fce4e6eb4fba2c8f5c5bbe5", + "0x8d5f742fe27a41623b5820914c5ca59f82246010fa974304204839880e5d0db8bc45ebab2ad19287f0de4ac6af25c09e", + "0xb93d4a1f6f73ac34da5ffbd2a4199cf1d51888bc930dc3e481b78806f454fcb700b4021af7525b108d49ebbbaa936309", + "0x8606f8d9121512e0217a70249937e5c7f35fbfe019f02248b035fa3a87d607bc23ae66d0443e26a4324f1f8e57fd6a25", + "0xb21312cdec9c2c30dd7e06e9d3151f3c1aceeb0c2f47cf9800cce41521b9d835cb501f98b410dc1d49a310fdda9bc250", + "0xa56420b64286bdddda1e212bba268e9d1ba6bdb7132484bf7f0b9e38099b94a540884079b07c501c519b0813c184f6b4", + "0x80b2cf0e010118cb2260f9c793cef136f8fa7b5e2711703735524e71d43bce2d296c093be41f2f59118cac71f1c5a2ff", + "0xadcb12d65163804d2f66b53f313f97152841c3625dbbda765e889b9937195c6fcd55d45cc48ebffabb56a5e5fe041611", + "0x8b8a42e50dc6b08ab2f69fc0f6d45e1ea3f11ba0c1008ee48448d79d1897356599e84f7f9d8a100329ed384d6787cfc4", + "0xaaa9c74afa2dec7eccfbd8bb0fc6f24ed04e74c9e2566c0755a00afdfdf3c4c7c59e2a037ec89c2f20af3fae1dd83b46", + "0xaa9f6e8fd59187171c6083ae433627d702eb78084f59010ff07aff8f821f7022ef5fbbe23d76814d811b720a8bfa6cc3", + "0xa56a3ded501659ad006d679af3287080b7ee8449e579406c2cae9706ef8bf19c1fc2eb2a6f9eaf2d3c7582cded73e477", + "0x81971e077c1da25845840222b4191e65f6d242b264af4e86800f80072d97d2a27a6adc87c3a1cb1b0dd63d233fbafa81", + "0xa6fa5453c4aaad2947969ee856616bf6448224f7c5bf578f440bcfc85a55beb40bef79df8096c4db59d1bd8ef33293ea", + "0x87c545adbfaaf71e0ab4bac9ae4e1419718f52b0060e8bb16b33db6d71b7248ae259d8dd4795b36a4bbb17f8fae9fd86", + "0xb4c7a9bc0910e905713291d549cec5309e2d6c9b5ea96954489b1dff2e490a6c8b1fa1e392232575f0a424ba94202f61", + "0x802350b761bcaba21b7afe82c8c6d36ee892b4524ab67e2161a91bbfa1d8e92e7e771efb1f22c14126218dd2cb583957", + "0xb4e7ddb9143d4d78ea8ea54f1c908879877d3c96ee8b5e1cb738949dcfceb3012a464506d8ae97aa99ea1de2abf34e3d", + "0xa49a214065c512ad5b7cc45154657a206ef3979aa753b352f8b334411f096d28fd42bca17e57d4baaafb014ac798fc10", + "0x8a80c70a06792678a97fe307520c0bf8ed3669f2617308752a2ab3c76fdf3726b014335a9b4c9cbcfc1df3b9e983c56f", + "0xa34721d9e2a0e4d08995a9d986dc9c266c766296d8d85e7b954651ad2ca07e55abb1b215898ee300da9b67114b036e0d", + "0x8cfce4564a526d7dca31e013e0531a9510b63845bbbd868d5783875ed45f92c1c369ce4a01d9d541f55f83c2c0a94f03", + "0xab3f5f03a5afc727778eb3edf70e4249061810eba06dc3b96b718e194c89429c5bfbec4b06f8bce8a2118a2fdce67b59", + "0xaa80c2529fc19d428342c894d4a30cb876169b1a2df81a723ab313a071cba28321de3511a4de7846207e916b395abcc9", + "0x82b7828249bf535ef24547d6618164b3f72691c17ca1268a5ee9052dba0db2fdd9987c8e083307a54399eab11b0f76b1", + "0x8fbcb56b687adad8655a6cf43364a18a434bf635e60512fad2c435cf046f914228fb314f7d8d24d7e5e774fb5ffb1735", + "0xa3010a61a2642f5ebbce7b4bc5d6ecb3df98722a49eb1655fe43c1d4b08f11dfad4bcec3e3f162d4cc7af6a504f4d47c", + "0xb3dcc0fdf531478e7c9ef53190aa5607fd053a7d2af6c24a15d74c279dbb47e3c803a1c6517d7e45d6534bb59e3527f5", + "0x8648f6316c898baaca534dff577c38e046b8dfa8f5a14ee7c7bc95d93ae42aa7794ba0f95688a13b554eeb58aeedf9ba", + "0x89fca6fc50407695e9315483b24f8b4e75936edf1475bcf609eed1c4370819abac0e6a7c3c44f669560367d805d9ba63", + "0xa367a17db374f34cd50f66fb31ba5b7de9dbe040f23db2dcc1d6811c0e863606f6c51850af203956f3399000f284d05f", + "0x91030f9ca0fff3e2dbd5947dcf2eba95eb3dbca92ee2df0ed83a1f73dbf274611af7daf1bb0c5c2ee46893ab87013771", + "0x84d56181f304ce94015ea575afeef1f84ea0c5dbb5d29fb41f25c7f26077b1a495aff74bd713b83bce48c62d7c36e42d", + "0x8fe2f84f178739c3e2a2f7dcac5351c52cbed5fa30255c29b9ae603ffd0c1a181da7fb5da40a4a39eec6ce971c328fcf", + "0xa6f9b77b2fdf0b9ee98cb6ff61073260b134eb7a428e14154b3aa34f57628e8980c03664c20f65becfe50d2bdd2751d4", + "0x8c6760865445b9327c34d2a1247583694fbeb876055a6a0a9e5cb460e35d0b2c419e7b14768f1cc388a6468c94fd0a0f", + "0xaf0350672488a96fe0089d633311ac308978a2b891b6dbb40a73882f1bda7381a1a24a03e115ead2937bf9dcd80572ad", + "0xa8e528ec2ee78389dd31d8280e07c3fdd84d49556a0969d9d5c134d9a55cd79e1d65463367b9512389f125ed956bc36a", + "0x942c66589b24f93e81fe3a3be3db0cd4d15a93fb75260b1f7419f58d66afaa57c8d2d8e6571536790e2b415eec348fd9", + "0x83fe4184b4b277d8bf65fb747b3c944170824b5832751057e43465526560f60da6e5bbee2f183cb20b896a20197168c7", + "0x88a71aada494e22c48db673d9e203eef7a4e551d25063b126017066c7c241ee82bedaa35741de4bd78a3dd8e21a8af44", + "0x8c642a3186ca264aac16ee5e27bd8da7e40e9c67ae159b5d32daa87b7de394bf2d7e80e7efb1a5506c53bfd6edd8c2c3", + "0x81855d6de9a59cef51bef12c72f07f1e0e8fe324fcc7ec3f850a532e96dcd434c247130610aaee413956f56b31cbb0dc", + "0xa01e61390dcd56a58ad2fcdb3275704ddfbedef3ba8b7c5fce4814a6cdd03d19d985dba6fd3383d4db089444ea9b9b4d", + "0x96494e89cbf3f9b69488a875434302000c2c49b5d07e5ff048a5b4a8147c98291ae222529b61bb66f1903b2e988e5425", + "0xb9689b3e8dddc6ec9d5c42ba9877f02c1779b2c912bba5183778dc2f022b49aed21c61c8ec7e3c02d74fe3f020a15986", + "0xa2a85e213b80b0511395da318cbb9935c87b82c305f717a264155a28a2ea204e9e726bae04ce6f012e331bd6730cbb9d", + "0x91b70f44c7d8c5980ce77e9033a34b05781cbe773854d3f49d2905cc711a3d87c20d5d496801ad6fd82438874ce732b8", + "0x884596417ff741bb4d11925d73852ffeea7161c7f232be3bdce9e6bbe7884c3a784f8f1807356ae49d336b7b53a2b495", + "0xae2aed8ab6951d8d768789f5bc5d638838d290d33ccc152edfb123e88ba04c6272b44294b0c460880451ad7b3868cc6a", + "0x89d8ebfb9beebc77189d27de31c55f823da87798a50bca21622cbf871e5d9f1d3182cf32ee9b90f157e6ce298e9efccf", + "0xafd00a4db4c2ed93cf047378c9402914b6b3255779f3bb47ded4ab206acb7eaebba0fd7762928e681b1aebcfee994adc", + "0xa2e49b6cd32e95d141ebc29f8c0b398bb5e1a04945f09e7e30a4062142111cd7aa712ac0e3e6394cfb73dd854f41ad77", + "0xae8e714ab6e01812a4de5828d84060f626358bb2b955f6fb99ae887b0d5ce4f67ebc079ab9e27d189bf1d3f24f7c2014", + "0xa3100c1eebf46d604e75ebf78569c25acf938d112b29ccbe1a91582f6bd8ef5548ae3961c808d3fb73936ac244e28dbc", + "0xa9a02dcff0e93d47ead9cdddc4759971c2d848580bf50e117eb100cafca6afeaa7b87208513d5f96b1e1440ffc1b0212", + "0x894ab01462137e1b0db7b84920a3b677fbb46c52b6f4c15320ef64f985e0fc05cec84cd48f389ce039779d5376966ea3", + "0xb1e40e8399ee793e5f501c9c43bde23538e3ce473c20a9f914f4a64f5b565748d13ab2406efe40a048965ee4476113e4", + "0xa5a7d97a19e636238968670a916d007bf2ce6ae8e352345d274101d0bbe3ac9b898f5b85814a7e4c433dd22ac2e000ff", + "0xb6394c43b82923231d93fd0aa8124b757163ba62df369898b9481f0118cb85375d0caac979a198ece432dbb4eb7cc357", + "0x82d522ae3ff4fe2c607b34b42af6f39c0cf96fcfe1f5b1812fca21c8d20cece78376da86dcbd6cdb140e23c93ae0bcb2", + "0xb6e0d986383bc4955508d35af92f2993e7e89db745f4525948c5274cfd500880cb5a9d58a5b13d96f6368bb266a4433e", + "0xb0b4325772ec156571d740c404e1add233fb693579f653b0fae0042b03157d3b904838f05c321d2d30f2dbd27c4d08ad", + "0xac41367250263a2099006ef80c30bac1d2f25731d4874be623b6e315c45b0dc9a65f530fce82fb3dc25bd0610008c760", + "0xb6c0b1ed7df53da04a6f3e796d3bfa186f9551c523bc67898bc0ecfc6b4a4a22f8c4d3bfc740ebf7b9fa5b0ea9431808", + "0x8e78fca17346601219d01e5cd6a4837161a7c8f86fe2a8d93574d8006da5f06ae7c48eea7d2b70992c2a69184619663c", + "0xa21f91f47e04fafbfafacf3185b6863766a2d0c324ccac2c3853a4748af5897dbbe31d91473b480f646121339c9bae2d", + "0xa464d68786ab1fc64bd8734fce0be6fbe8dc021d3e771ff492ada76eedff466577c25e282b7c8ab4c1fd95ef5ff3631e", + "0x829a24badc7714081e03509ccfb00818ce40430682c1c0e4a399cd10b690bda1f921aabcbf1edfb1d8a2e98e6c0cedd6", + "0x87ccf7e4bbcb818ef525435e7a7f039ecbb9c6670b0af163173da38cbdb07f18bc0b40b7e0c771a74e5a4bc8f12dfe2c", + "0x94087bd2af9dbeb449eb7f014cfbf3ee4348c0f47cde7dc0ad401a3c18481a8a33b89322227dee0822244965ae5a2abb", + "0x896b83ed78724dac8a3d5a75a99de8e056a083690152c303326aa833618b93ef9ec19ab8c6ef0efe9da2dbcccac54431", + "0x821e6a0d7ccf3c7bd6a6cc67cde6c5b92fb96542cb6b4e65a44bbc90bbc40c51ff9e04702cb69dd2452f39a2ff562898", + "0xb35b2096cda729090663a49cb09656c019fef1fc69a88496028d3a258ad2b3fd6d91ab832163eaa0077989f647e85e7e", + "0xb7857ef62c56d8bce62476cdb2ab965eddff24d932e20fc992bd820598686defe6cc0a7232d2be342696c2990d80721a", + "0xb343d974dfda3f6589043acd25d53aecf7c34b1e980ae135a55cda554ff55e531bc7c2dfe89b0d2c30e523c7b065dad1", + "0x8d139e16a73cd892b75f3f4e445a10d55d1118f8eeafc75b259d098338419e72e950df6ca49cb45677a3c4e16fb19cdc", + "0x817b8535bd759da392b2c5760c51b3952ecf663662a137c997f595c533cd561ed7e655673c11144242160e41d1f2dd71", + "0x817ee0f0819b0ccb794df17982d5b4332abff5fec5e23b69579db2767855642156d9b9acccf6ceab43332ccc8d2744dc", + "0x9835d2b652aec9b0eba0c8e3b6169567e257a6a3f274ec705dbc250ee63f0f8e4b342e47b9e0c280c778208483d47af8", + "0xb78c40177f54f0e6d03083a4f50d8e56b5aafdb90f1b047bb504777d6e27be5a58170330aee12fbaa5f1e9d4f944acfc", + "0xab8eebacf3806fac7ab951f6a9f3695545e2e3b839ca399a4ef360a73e77f089bb53d3d31dbd84ddfde55e5f013626e0", + "0x96c411fc6aecca39d07d2aff44d94b40814d8cfc4ee5a192fd23b54589b2801694d820a0dd217e44863ccff31dda891b", + "0x8249c424a0caf87d4f7ff255950bbc64064d4d1b093324bfe99583e8457c1f50e6996e3517bf281aa9b252c2a7c5a83a", + "0xacf6ed86121821a3dd63f3875b185c5ebe024bdb37878c8a8d558943d36db0616545a60db90789c0925295f45d021225", + "0xa37f155621a789f774dd13e57016b8e91b3a2512b5c75377ec8871b22a66db99655d101f57acaecd93115297caabfc21", + "0x92e60ee245bd4d349f1c656e034b1a7f0c6415a39ac4c54d383112734305488b3b90b0145024255735e0a32f38dba656", + "0xacec614e562ccfc93366309cfdc78c7d7ee0a23e3a7782a4fc4807b8803e6ebfb894a489d03e9a3c817ff2ec14813eba", + "0xb912f9dd26ed552cb14b007b893e6ed2494d12517e5761dbeb88521270144f8c3eb9571a0ad444b30a8a65e80bd95996", + "0x8375408dae79c547a29e9a9e5d4ec8241b36b82e45e4ca3b0c36d2227c02d17bb171528d3778eac3bbdc75d6c4e8a367", + "0x8c2d0e6e4406836da112edbbb63996408bb3cda4a2712fd245e4bb29a0100fdc89a2746d859b84a94565bc1cfa681813", + "0xa7431bf59e111c072d28c97626cd54fcdf018421d053a787d2aef454b91251ee8ff9d3702d06b088f92b9ad2bbebff15", + "0x8f3659b0fbeb90b7f30b7a49233325e806551a32911a654dca86e290b314483bbb33fe6482387bc48c35d85c1dd0441c", + "0x8dca5ba23f0bb76f7dacabf12886053552ba829a72827b472a2f01e19a893155cdce65f1fb670000f43e8c75ba015a31", + "0x8c1514c083c77624eeb5d995d60994a2866192e15c4474d0be4189fae0e9dbd62494ebb4c02fbc176b53be548abbc5a1", + "0x80498d2ed153381baf3b0f81da839ed0eea6af5796c422b8e59be805dba48c4395bb97824ac308170bb4f14f319c5ddf", + "0x84f5ebc3bf96362457993e9fa31493c31c4283075e2403f63d581b6b0db8a3df294b2085643f2007f4de38cb5d627776", + "0x958e6e38774da518193a98397978dbc73d1c3827b4996ec00b4183da2c305a187a0ada9aa306242814b229a395be83c9", + "0xab8b8fbf73845615e7fab3e09e96cc181159eab09f36b4c1239b3c03313c9aeb4bbb51e16316fe338b2319ed2571b810", + "0x977e4e33b33bd53394e591eba4f9a183e13704c61e467d74b28f4ad0b69aa51501a5221cb1e0e42bcb548ca518caa619", + "0xa9bb7ecb9846cc30d04aad56d253c3df7004cebb272f6adf7b40a84adef9f57291e0d08d64c961b9fc406cdb198aab9b", + "0x8d2b72dc36406a545a9da44e1fddfb953d4894710ca026d6421a4ac91e02d0373a599f2acfe41d8258bc9679cf6f43d3", + "0x904192fc8fe250f61ecb8a36abbbccae85f592bbf00c10039c30b5a1c733d752a04e4fd8a1000c6578616f8a16aa83a3", + "0x87f5fdfe20bbbf931b529ec9be77bbfcc398cad9d932d29f62c846e08a91d2f47ae56ad5345122d62a56f629f9a76c4d", + "0x84cc3a53b2e7b7e03015f796b6cb7c32d6ded95c5b49c233ac27fafa792994b43c93cda6e618b66fce381f3db69838ba", + "0xaab58da10d7bbe091788988d43d66a335644f3d0897bbc98df27dcc0c0fcee0ac72e24f1abdd77e25196a1d0d0728e98", + "0xa10ea8677c2b7da563d84aa91a314a54cab27bb417c257826ebdd3b045d2a0f12729fe630bbbf785d04874f99f26bee8", + "0xacc4970ef2a4435937a9b8a5a5a311226ca188d8f26af1adfcd6efb2376a59155b9a9ff1cff591bde4b684887d5da6e5", + "0x8dc7cf6fcca483c44eb55e7fb924bf3f76cf79b411ae4b01c6c968910877ac9c166b71350f4d935f19bdffb056477961", + "0xac2dd1182ded2054c2f4dbf27b71a0b517fb57193733a4e4e56aca8a069cff5078ffd3fd033683d076c1c639a4de63c7", + "0x932ec87c450cd0dc678daf8c63cd1bf46124fa472934e517fbbfb78199f288ff7f354b36e0cc6c8739d3f496cfe0913b", + "0xb0d631ced213e8492be60ea334dbe3b7799b86d85d5e8e70d02beef3ae87b1d76e1df3bdb5f7ba8a41904c96f6a64455", + "0x929d7239ead7575867e26b536b8badf2e11ca37840034d0e5c77039f8cce122eff5a1bf6e0bcadde6b3858e9f483d475", + "0xaaae5d372d02ee25b14de585af6fbc48f2c7cd2a6af4f08352951b45aa469599eff41e820df642ca1a0f881120e89dbe", + "0xb23c411741a6b059f04fa4f5fd9dd10e2a64915f2de6ea31e39c32f2f347a776a953320e5f7613fcb1167efe502f5c5c", + "0xa4581b0ae633fe29c6f09928e5efb16db019eeac57f79fef2fa1d3c9bee42ce0e852bc60b9d0133265373747e52a67a4", + "0x81b33afffd7b2575d4a9a1c5dd6eee675c084f82e06b9b3a52a3c9f76e087f12dca6e0ffddc42fb81ce1adb559d47a38", + "0x89cc890f06b424591556aabdfdbb36d7a23700425e90c9cfed7d3da226b4debe414ac5bdf175273828ce6c5355712514", + "0xa4399438be75cfae2bf825496704da5ed9001bed8538d8ac346c8cf0d4407808e9ee67573eb95fe1c6872ac21f639aaa", + "0xad537f7ce74a1ca9a46fc06f15c1c8a6c32363bd6ac78a3c579ed8f84252e38a914cac16709fe65360e822ef47896de4", + "0x8e53b69f5e3e86b86299452e20ea8068b49565d0d0ab5d50ce00158a18403ae44e1b078a3cfd3f919aa81eb049a30c6e", + "0xa59f2542c67a430fd3526215c60c02353ee18af2ff87cb6231a2564fe59b8efec421f18d8b8cc7f084675ecf57b3fd05", + "0xb8d9bac93ef56cb4026dd1c731d92260a608fd55b8321e39166678e1dab834d0efddb717685da87786caeb1aaf258089", + "0xaa2df56f4c6fe9e0f899116c37302675f796a1608338700f05a13e779eb7cf278e01947864a8c2c74cc9d9a763804446", + "0xb0108ff2e327dcb6982961232bf7a9a0356d4297902f4b38d380ff1b954bfbcae0093df0f133dd9e84d5966c7b1aada7", + "0xb06b813b01fe7f8cf05b79dc95006f0c01d73101583d456278d71cd78638df2b1115897072b20947943fa263ddab0cd6", + "0xaa41e6c4d50da8abf0ea3c3901412fe9c9dff885383e2c0c0c50ed2f770ada888a27ea08bbb5342b5ff402e7b1230f12", + "0xa48635dbb7debac10cb93d422c2910e5358ba0c584b73f9845028af4a763fd20da8f928b54b27782b27ca47e631ebf38", + "0x80a574c208e994799e4fa9ef895163f33153bc6487491d817c4049e376054c641c4717bda8efbeb09152fa421a7268a7", + "0xb592bfd78ae228afc219c186589b9b0b5c571e314976d1ed5c1642db9159d577679a73c049cfc3dcfefcd5a4f174eeea", + "0xaa1f08af3918c61eadf567a5b1a3cdcdfb1b925f23f1f9e3c47889762f4d979d64686ce1ce990055ef8c1030d98daa3b", + "0x857df4cfd56d41c6d0c7fcc1c657e83c888253bae58d33b86e0803a37461be5a57140a77fb4b61108d1d8565091ada1c", + "0x8fae66a72361df509d253012a94160d84d0b2260822c788927d32fd3c89500500908c8f850ef70df68ddaeb077fd0820", + "0xaa1dbefc9aef1e7b896ff7303837053c63cfb5c8a3d8204680d3228ac16c23636748fe59286468c99699ae668e769a0c", + "0xb64b1cb2ba28665ed10bad1dddc42f3f97383c39bad463c6615b527302e2aaf93eb6062946d2150bd41c329697d101be", + "0xb6d35e3b524186e9065cee73ea17c082feff1811b5ab5519dd7991cdff2f397e3a79655969755309bd08c7d5a66f5d78", + "0xa4dae7f584270743bbba8bb633bdb8bc4dcc43580e53d3e9e509ff6c327e384f14104b5bdfe5c662dc6568806950da37", + "0xaae84d3d9ad4e237b07c199813a42ed2af3bf641339c342d9abf7ebec29b5bd06249c4488ce5c9277d87f7b71b3ddd37", + "0xb82a463cf643821618a058bddf9f2acb34ac86a8de42a0fa18c9626e51c20351d27a9575398a31227e21e291b0da183e", + "0x8b6c921e8707aded3ea693f490322971b1a7f64786ef071bc9826c73a06bd8ae6bf21bc980425769627b529d30b253ce", + "0x80724937b27fc50f033c11c50835c632369f0905f413b1713a2b0a2274bec5d7a30438e94193d479ba6679dbe09a65ef", + "0xa1d9b259a2ca9cff8af6678b3af0a290c2f51e9cf26d5fe3c6a4fa3d28cbf33cb709b7f78b4f61cb9419427983c61925", + "0x96a3e69a5ed7a98ce59c4481f2ffb75be9542122ad0eb4952c84d4536760df217854d4ec561ce2f4a79d3793c22fa4f4", + "0x990c4d9a4a22d63a8976d34833cafc35936b165f04aed3504e9b435f0de1be4c83b097bbaa062483cf3dee3833b4f5b6", + "0xb9bf5e4b270aec4a0dc219457b5fed984b548892c4b700482525ba1a7df19284464f841dab94abfabcaa9a7b7a757484", + "0xacaecf49cb4786d17cf867d7a93bd4ffee0781766e11b5c1b29089ae0024c859d11b45828fbff5330b888543264d74a9", + "0xb0e1a0865b1e6f9e4a0e31d0c885526ac06678acc526fda5124742a2c303bd0e8871a0cb7951ec8ed9540fc247c8d844", + "0x82b3d327b3d1a631758451e12870816956cd0cef91fcf313a90dd533d5291193a0ff3cc447054564ce68c9b027a7ffd7", + "0xa2843602abb98f0f83e000f3415039788da1e9a096bfe8fed6b99bab96df948c814560424ffebe755cb72f40436fb590", + "0xab1c7b43cf838798d1e314bc26e04fc021e99a7bfbfc8ffde62fa8d7f92139de86a377289d5177021154229de01ede15", + "0x95e5cf5dd87ae3aed41b03c6c55f9dfad38dc126b17e7e587c156f7745c8da0bd1d60acb718fc1a03b61344f01e3de4d", + "0x86f021a3762bb47167f80d4ef1b1c873a91fe83409f9704f192efeebbc3ece0729cd2f92f63419907ea38ae47bc907d2", + "0xaaa1445dafbbcd645d4332d9806225e9346ee5ac6b22ad45e8922134fe12f3d433f567a6a4c19efdd9d5775a7de1e92f", + "0x8fd7e15688eef75df7b8bca3d61bc9fca4f56e047cdb6d0b864e7d1c4966eac27d6094b0c8482b49739f83ec51050198", + "0x80aab8b4d394eb011d4ec6a4c2815617308c9b847c6fa6a3d7e6af1c79420ef6ff2a13934a398581c40ee4cf1cac02ac", + "0x8970b97ac076a1d8a321ce00eada0edf974a46bf3cc26f6854e4218cdfc8d2b0c32199d9658f254b4fbae5a2c5535f41", + "0xa1aa2ec5b03df0a630e73dd048680ed6d3032c324941423f45cd1f16038789e5e75b876a13948732e9079a422f66a9fc", + "0xb5fe5f5e2f2ae2beeb8e95859a02fc45f01f9fb0ebb2bd8ec9ec976b3e806228821a9775096d341d662bc536c4d89452", + "0xa2bc1f170b62d0d5788b02391337b2ab157c38e725694e80aeead7383e05599be0e2f0fa27ef05db007061809356e147", + "0xa8a69701d4a8d0d972390e9f831fd8e9f424b2c2ef069e56bd763e9e835b3ce5f7cf5de5e5c297c06ace4aa74df1067c", + "0xb43d551af4ff3873557efe3f3fb98e5ede9008492f181f4796dd1a6bcda8b9445c155e8146966baa812afae1abe06b48", + "0xb4b1dae44fd596813f30602ab20e9b1fb20cb1bd650daacc97b7e054e5c0178b8131d439a9e5b142ca483cc012a362b3", + "0xb95b8a94c30a831eaaebea98c65cc5d0228c78afd6603d4aa426d8186aecc951f1a11c33951f51df04c7e6fa43ffb5ae", + "0xb100059624cf9db371bec80013a57a8f296d006c139a8766308f1ea821c7eccc26cad65bc640ab3f6cef9062653bf17d", + "0x8e5a2cb76716e0000d13bce5ef87acac307362a6096f090f5f64e5c5c71a10fddfdee8435e7166ba8c3ad8c3f540f3e4", + "0x93d2c43e21588c1e83c4255c52604b4ac3f40e656352d1827e95dd5222a45aebff9674e34fbbe7ed21eca77bd9b8dcbc", + "0x8aeaed611546bb9073b07512a9a1f38a7f436ab45e11775a0f9754baaf63e9bcc7bb59b47546a5ded5e4ba2f698e3b5f", + "0xaf9e6792e74a1163fe27612f999a2f3cfa9048914c5bef69e3b2a75162bb0ce6ece81af699ad7f0c5278a8df0ba000d2", + "0x850bf2d5d34791c371a36404036ad6fdcd8fb62d1bb17a57e88bda7a78ea322397ce24d1abf4d0c89b9cf0b4cc42feb3", + "0x87f7e2a1625e2b7861b11d593aaac933ed08a7c768aebd00a45d893ed295bbb6ed865037b152bb574d70be006ddc1791", + "0x8dcce8f4ad163b29a2348ea15431c2c6ea1189ece88d2790e9f46b9125bd790b22503ec391bc2dee8f35419863b2c50c", + "0xb4bf5266c37f12421dd684b29517982d5e4b65dfdfba5fc7bd7479fd854aabf250627498f1e1188a51c0a88d848ec951", + "0x8651623c690247f747af8fdffdc3e5f73d0662bc3279fa2423a3c654af9b6433b9e5e0155f1ce53857e67388e7e3401d", + "0xb155120f196d52760129dde2e2b1990039b99484cdc948fa98095cd23da87679850f522e5955eae34ac267d2144160d3", + "0xaec8115e8d7b6601fbceeccf92e35845a06706d46acd188452c9f7d49abef14c6b3a9a9369a8bab2fd4eb9288e2aaca5", + "0x998a8ca4dc0f145f67a8c456f1d6a7323c4836fe036dcbb0f27eb1c596d121eb97369638a9908cfaf218c7706f266245", + "0xb235fbafac62802742ee3d26b1f4e887f7d2da4d711ba7f9bb6ca024de7beec1de66bb830ce96d69538f7dcb93c51b26", + "0x9258d2ddc21ab4e3edcde7eb7f6a382a29f1b626003cc6fdd8858be90f4ad13240072d8a8d44ef8de51ca4f477fa6c45", + "0x99d038487821c948142c678acd8c792960993dd8cb5e02cb229153a1ee9f88249f4ad9007f08e5d82e2a71fb96bb5f32", + "0xa88ee9dbc73d3d8e0f447b76fdb3a27936bde479a58d5799176885583dc93830ac58bca9087075950ea75100cf51af23", + "0x88b9b15816e5a0387153c1f4b90f613beb3ea4596037da01a81fdd2bcbd0baf5598db99f77e7694e5a0d35e822758108", + "0x907ae4b637d06b15846ee27d08c9c9af42df261c5bdd10cf5bc71f8e5ca34b33ac2405307023c50bdb8dc7b98a2cd5fe", + "0x9393d6900e1d2d1a1e42412fefd99578d9ac1d855c90a3e7930a739085496448609d674ca9b34016ad91f22d1cac538e", + "0xa28ac56b216730b7dcdb5ab3fc22d424c21a677db99a9897a89ed253ea83acfd9d83125133f5be6d9cd92298df110af8", + "0xb027590ee8766f1e352f831fda732adbaf77152485223ad5489ef3b0ce2d2e9f98d547c111fe133847ebb738987fb928", + "0xa9cc08fbd5c3fee8f77cf6eb996a5cafa195df5134dab000e4d0312f970a5577942ee89794e618074f49841f1f933a42", + "0xa8b3535c3df0b1a409d3fc740527ee7dd5ac21756115cde6f87f98cc7623f50cfcf16790689cab113ee7c35a5bd4879f", + "0xb61420227b97e5603ae8a716c6759b619f02b8fdc48acbf854352aa6519dad74b97bacc1723ca564cbf3ca48539ed773", + "0x853762498de80eebf955a6c8ddd259af463e4e25f0b6ba7b6a27b19bdbf4c585de55760a16e2d9345cdba6b2a02610f3", + "0xa711c1b13fc6c30745203c5d06390e6c82bd7c50f61734aa8d99c626faba30119bc910be63ec916c91ba53f8483c05a8", + "0xb488c0a793f4481f46b5875d96eecd73e46209a91677769f0890c5e002ecd7d4b1c9f4ba68c47fbed40e3857b1d8717a", + "0xa651c5e812ae65b1c66d92c607e80be330737ea49c1dcfe019c0ecea0f41a320406935bb09206a4abff0d1c24599b9ad", + "0x85e34e7d96e4b97db98a43247b6c244383b11ca10bf4777364acf509a6faa618bc973e2136a4693fbc8ab597e308fd5a", + "0x99837214102b394fffa7f3883759554c6bb7a070f5c809303595a44195e02b9a169460dc6bbffb62bdc0e7ced5f0a5c1", + "0xa952f89c0afb4bdae8c62b89cc3cfb60d0576ba4fe01a5d99534792f38d8848d919b3fc7577435d8443a044d2ee0bcfa", + "0xa1ac1f81acb29798acdfc493854663519e2d1b0e9d23d286ce33882c34b4c1c0bb43dd9638166d8026315a44d9ec92a8", + "0xac9c58aa38219ae659d23007cc7b97fd25b7b610b2d81a8f9f94ddb089efc49c049a8ea4c56e6eaf7b6498f422a97b3c", + "0x87e61d501c242b484fb9a937ef21d485f6678d75257fc8fe831b528979068cadbe7e12b49c34058ec96d70a9d179ab14", + "0xaa45f6852f35cc8b65a4a8b5380641d2602a4fa4e3a035db9664df3ac2e170b1280c4a8b7b55161430063e54de4158a6", + "0xa46975614ddde6d134753c8d82c381966f87203d6e5a5fb99a93b0d43aa461466b37f07b8d0973a1abd6ee2b40f24348", + "0x8d35f97297773422351f4d99564c1359ef1a10cfb60aa0e6c8985a78f39b4268486312c8ebf9dd2ef50a771aa03158eb", + "0x8497c6242102d21e8b3ade9a9896c96308ab39171ab74cbd94e304c47598e2c2a7b0a0822492ac5c076ba91d4176481d", + "0x973f8fcb5f26915b3a3ef6fe58cc44bc7f4e115cd0ad9727d8d1b8113e126ae2e253a19922c5433be4ab2311a839c214", + "0xae3ee9f1d765a9baf54b4617a289c3b24930aa8d57658a6b0b113bbf9b000c4a78499296c6f428bbb64755dfd4f795d2", + "0xa5be7a8e522ef3dcf9d2951220faf22bb865d050f4af2880b8483222ff7aad7c0866219fcc573df9d829c6efbb517f98", + "0xa5f3c7fabd7853a57695c5ad6d5b99167d08b5414e35ed1068ae386e0cb1ee2afbbe4d2b9024379b6fc3b10c39024d36", + "0x978d5592d4798c9e6baceff095413589461267d6a5b56cd558ec85011342da16f4365d879b905168256f61d36d891b1f", + "0xb7b6eaffa095ecbd76d6e1e88ceebabaf674d9ef7e331e875c6d9b9faa1762c800ff1ea597c214c28080f67a50a96c1e", + "0x8a1ab53ae5ceaa42e06e58dd8faf6c215fc09ba111ca9eeb800612334d30d5971448be90fec62ed194328aadd8c8eecc", + "0xa9ca532cac8ace9a9e845382f8a7840bf40cb426f2fcad8a2f40aadbb400b3a74021627cc9351b0966b841b30284962e", + "0x8dddeda8854c8e7ddc52676dd1d0fed1da610ed5415ddd7d25b835bd8420a6f83d7b67ec682270c9648b2e2186343591", + "0x888906aac64fd41d5c518a832d4e044fdc430cfe142fd431caf4676cafc58853ce576f098910d729011be0a9d50d67b5", + "0x96a3f886a2824e750b1e2ea5c587132f52a0c5e3ff192260d8783c666206bd8ebd539933816d7cdd97e4bc374e0b1edf", + "0xa150a29ffb2632cc7ec560983d9804cd6da3596c0c25956d27eb04776508eae809659fc883834269437871735de5f9ed", + "0x81f7ad4d2959d9d4009d1dfbc6fee38f930f163eb5eac11e98dc38bd2f7f224e3f5c767583f8e52d58d34f3417a6cf90", + "0x97ccac905ea7d9c6349132dd0397b6a2de9e57fd2d70f55e50860e019de15c20171a50b28a5c00ef90d43b838253b3d1", + "0x95694f00c21e8a205d6cbda09956b5b6ec9242ec8c799a91f515b07dcc7de3b6f573e2c0ba149f5a83700cda2d1df0f5", + "0x82bbc3c4a3b3997584903db30fffd182a266c7d1df3e913f908d5a53122fa12cf5acd11d915d85d5bd110fcc43cee736", + "0x8d3f24b4949aa1b4162c28dfbb9f813dd1d8b330f71325448dc45ea34d59b69ca95059402aae011e1b5aba6e536bc6ec", + "0x92c734c19752d24782331e74c9af97a8399ddfdd32954e91cda7363dba876aca4f730b451c50a8913950420682da8121", + "0x8653d2c79f77b8c7dcdf7e8dee42433998aeedf1b583abfca686d47a854de1b75e9a4351580c96d1a2a9532659203361", + "0x886f0e414cb558c1a534a1916d3531320a9b6024639712ffe18164ce6313993a553e2b9aafe9c0716318f81a5d0bb1da", + "0xb31b5efaba5a5020c3bcea0f54860e0688c2c3f27b9b0e44b45d745158f484e474d5d3b1a0044dd6753c7fb4bf8ace34", + "0xb2d615bbdfdc042d6f67a6170127392d99f0e77ae17b0e1be6786ff2f281795f1bf11f83f2e0f8723b5cdd1db1856e09", + "0xa6e014cca531e6ac2922239b5bee39d69d9ba6d0fa96a4b812217dd342657d35606f0b9c5a317efd423cdb1047815e3d", + "0xa8921736b69c9fbb29f443715174bac753e908251804620c542fad6cfbfda7bdfe287f2902f30b043a8a4b4818cfdeef", + "0x8d73a9949a042ec2dcefa476e454cd9877eee543b1a6b3b96a78ffcff87421e8b26dd54d5b3192ac32073cb36497acc3", + "0xb936a71ee8df0e48867f3790adf55dc8efc6585024128de2495f8873bd00fd9fa0984472125e801ed9c3cdce6698f160", + "0x82f69c06209c28f64874e850601dda56af44ffc864f42efa8f9c6a0758207bf0a00f583840982dec0a517ab899a98e5b", + "0xb7a0a14411101473406f30e82f14b13e6efc9699e7193c0be04bb43d1b49e8c54812ce0f9b39131a20379c4c39d3bbe3", + "0x81159c969f38107af3b858d7582b22925a7ccced02fae3698482d7e9cdc6c568e959651991c6cf16c53a997442054b61", + "0x8bf1116a206e0ce9199fcab6ed2b44a9e46e8143bff3ed3f1431f8d55508fe2728b8902670cfd8d9b316f575f288ed9d", + "0xa279b2149824b64144eb92f5a36b22036d34a52bd5a66e5da4b61fbc95af6eda8e485c7914f448abd8674fc14d268d9d", + "0x8b98279b5f3588d1a2f8589d2756458690a502728800f8d94b28e00df842a101c96ab9c5aee87c5bbe65552c0c383b80", + "0xb4a27a351ec54420f94e0a0a79d7c7a7337940399646631baca93eeab5fd429d7fb39428be77dcbce64a13eaa3c8ca1d", + "0x90c08baa29ec8338ffce381eae3d23ce3f6ba54e5242dec21dc3caaed69cac13f2ab5e8d9d719bc95720fa182eee399c", + "0x85156d65bb4fef69ffd539ab918b3286105ca6f1c36a74351ab3310b339727483433e8f8784791f47b4ba35ca933c379", + "0x923005013c27209d07c06a6b92b0cbb248a69c5e15c600bbcc643e8dcd2402adebd94dd4cafb44ec422a127e9780aaec", + "0x863b23eb5463a6ef5a12039edc2f8e18e3c97b244841bc50af02459b1bcc558367edf2f6e4fe69f45f37887469dd536d", + "0x87a4a7708a112724ff9b69ebb25d623b5cae362ae0946daed2ec80e917800dbfcd69f999c253542533242e7b9a5cc959", + "0x8bf4347ceea7f94b53564f26b1a4749a16f13bf71a9e03a546f906f7c423089820ff217066159b0637d9d6824e9c101c", + "0xab07eef925d264145971628a39e4dd93ff849767f68ed06065802cf22756fc6bf384cf6d9ab174bfc1a87bcc37b037aa", + "0x8e3f10a42fad43887d522dc76b1480063267991c2457c39f1e790e0c16c03e38a4c8e79a0b7622892464957bf517ebd8", + "0xa8722fc7b1acf0be18f6ddf3ee97a5a9b02a98da5bc1126a8b7bf10d18ee415be9a85668eb604ef5a1f48659bc447eb5", + "0x878d6b2a9c0aca8e2bc2a5eb7dd8d842aa839bbd7754860c396a641d5794eab88a55f8448de7dbddf9e201cbc54fe481", + "0xada881c167d39d368c1e9b283cf50491c6bfc66072815608ba23ab468cfbd31ca1bd7f140e158e0d9e4d7ebfa670bc2d", + "0xa2b48578fa899d77a7ee1b9cb1e228b40c20b303b3d403fd6612649c81e7db5a7313ba9702adc89627b5fd7439f8b754", + "0x8e051280e10551558dcb5522120ac9216281c29071c0371aaa9bde52961fe26b21d78de3f98cb8cd63e65cff86d1b25c", + "0xa7c5022047930c958e499e8051056c5244ae03beb60d4ba9fe666ab77a913a067324dfb6debcb4da4694645145716c9d", + "0x95cff6ec03e38c5ab0f6f8dccde252d91856093d8429b7494efc7772996e7985d2d6965307c7fdfa484559c129cca9f9", + "0x993eb550d5e8661791f63e2fa259ab1f78a0e3edad467eb419b076a70923fede2e00ddc48a961d20001aaae89fad11e8", + "0xabb2826e4d4b381d64787a09934b9c4fe1d5f5742f90858228e484f3c546e16ee8a2a0b0a952d834a93154a8b18f3d16", + "0xa922ca9f2061996e65ef38a7c5c7755e59d8d5ce27d577abcdd8165b23b4877398d735f9cb470a771335fc7d99ecb7fc", + "0x90f22862216f6bc1bbf5437740a47605d1ff5147b1f06f7b13fec446e4c5a4a4a84792cb244a1905f3478a36f8d7065b", + "0x87f3d9a86afef5b79ea1ca690ee1ee4bb9754b66f7c50a42ad6b99af7c222c853ca161f440a0a2a60b3b5a54e3493240", + "0x80a9ca9a2d33b9cf61976b3860d79f5d00de89a06ef043d2a52931809018aeb4ce70423cbef375b29c2c750c2c8704c2", + "0xb4e798ef1d615896108dae37ac50c1e859216ab6dbac11653e44d06ce5209057b4b0dd6d31dcfcda87664a23c8ef1cbd", + "0xaaed6d1e7c5b1db06f80dae6c24857daadfb0268f20e48a98fba4b76de1ebf65fb84c3be95fd6a418b498f8285ec63bd", + "0xaeceaa316c6369492c939f94809bc80e0857abac86c0d85be8066bbf61afbaaec67e28c572437a8d35c49dd596b3134f", + "0xb791c3d53ed34a7d1c8aa89b7953e3684c3cd529230824dc529739a5fbe74b58b87f01e56e7a169f61c508237ef67160", + "0x9351f8c80634386c45c0050d2f813193f9d839173be941e2092d729be5403632a2f18dffdc323d69eb0dc31fa31c5866", + "0x97693184d5c0056ae244dfb6709cafa23a795dc22d497a307a7f9cf442d7452024023c54a8d6bda5d90a355ba2c84f3a", + "0x85362daa003d23511ca174a8caafe83d52b6436dc4e43c4c049e5388d9211b5cbef3885896914d86d39be0dd1f910511", + "0xa2511b5fa34b24eeb0e1bcbcf872a569d1ff5570fe7b0fb48f5542f7fe57bad808d34b50afa87580866a6cb0eba02f27", + "0xb382e3327eb1401f2d378dbb56ac7250adde0961bd718575a64d264ffd44772c20752d4035c3ba60eb435e160b375e20", + "0xafad8a5d40b536c0720556845a6b257ed42165c14fb4b4a874717d107752f49ed9380c5b048df3aca67287bb8fc411a8", + "0x8fad0c98434ca5373c2d767868f679b76b4a8d04bca8240ea3f388558262c2d61b73b16fc1160932652b5688c25fffcf", + "0x83898008b5cbb6f08f8ef3ec179427869682bb4e8d38f6e6a687a214d4a307436afc64ee67d70a5a8ba9730bf839aecc", + "0xb85232e79913785fd82b06890706972b4ad7a309489930ae23390d51aa5189731f8a2df24800409a8c36b3dd6fc91275", + "0xa24ff26ec792f3701da4c5638c1fca4fa4dae95b01827d6200d583c4caf17ea3171393ba2a8c23d1ee8b88402916f176", + "0xadc5c7a7ff6b41d6cc386b7fc69d7bb04179bdf267864f9aa577f0f6a88438191fa81ebaf13055c2f2d7290be6421ace", + "0xa05e835abd502d31454d40a019010ff90b6b0b1f993075a35c9907aeab7a342ac0ba6144dc9379aada6119157970e9b2", + "0x85ff07ba58463e7f153fc83f11302e9061e648a5cbd272bb0545030b20e11facd8b3ff90c9ac8c280a704fbda5c9d1b0", + "0xa6c735ada8f4587da8cdad7ea3ada01650b5a3ecab8d81daa7a5f5de51ef4a6592b524692584306f06be3f6701f2870c", + "0xb138deee4e53ae8d677fae104f713ef1b8babfecec16b6a85785a66a72784eb09d44c3b63567222ade714e98f7d1604e", + "0xae79c1a49dafcdd972acd95d8ad0a35c02adc7fd736d4c44c3cd13df5789d339b5ea16bddbbd43e486a061ab31baa5c0", + "0xab3cf2371a1d7dcd0ffe3869a0178230964b06694bf258b2073ea66a2afccd845b38485da83d02e1d607d4c5c36b78a8", + "0xab9609f28a325fd01cb39540e3a714506c44e52ef28ee640f361deb5760aadbb23e804663b0fa20a66e239c33f8d8bb8", + "0x8ed95ea8e76e1b42823d7915a6aae77d93746f846bf602841dfce0e47543a36efb9ee7e5b42c73c3209d911225cc471b", + "0xa80b6162036d43811482323f0ce59eb18740e33a63d7c7bbbf3be206985919e5342d53a69df537d43e8b7d7f51e8892f", + "0x93c03d0a5083408ba00c125a8a9385213d4c860072f0297857b1235045819b904e07f2425c13a661d0a01d2e53347f4b", + "0xa6581200f00f96c461621e1d26b14a23687dd97eb9f7df4ba641a84340ee7306dc1796248fba4804f185947ad13b4385", + "0x8be174018fa40f7e0cedc5ae68f38969eb7695f2205e9c573641e533d56f68c20abf38a23d2f0dcac371e60b21b18615", + "0x857ad4ee3218c647c58f09b8ab22bcc8976f00a768ab1f708618e868e6143474be846422ce2710a0ed39b5155b6f13a1", + "0xa490bec40f322d599f26bcefcdddd8f2ef6576aa737d5ce7e8d5d422741abe749e3e6a48489aed8c560633f72857e3c2", + "0xa9c0ee339621f1c4a2410f9b4d2f03f1b558dae2973807b8bccd920e8feb7f65dfde3e79986b72ad21fcc4567240381d", + "0x8592251568e750a430f7d2c6ddbb3ec82a4dd9fd83efe389e69aa177fd97ac2c96c59a6e86db20d8e6f125d65b46c4d3", + "0xa4e2f4aa6a682913b423b097c4069c4e46a1f3af9556b1bfd0580d0fc01e3991488458049e0735b2a629684a79271c8f", + "0x8c4f6a3e738cf74112b08b1680be08158013ef8a515a81215d8a36c9b756786d1b4cb4563923463f3329292f4b48bf6d", + "0x8bace547353c02ea00dd547eeda7259aa354d4772dd5e0c486c723cf88627b7112e196b879c3c92a9561b674d9fc486d", + "0x8d372f4901e25e8db64fa098148d4a4e709b0e9dcb756d0f90dad99dea393054193ae1a33d292a3dd772ff7ba05e4b71", + "0xa8c7ea6a6a031ed23d65639f01f5423190775558f479700597df7ae7e338a6ae5e9b32f470aff20787ac8b7eec84df6c", + "0xb6e9dcba240fdbbf66033410a79a2dd3e9e1ffdf2eae949b3a9ed720e939d92339991dc3e70a5ac7d5253f317daf0b7d", + "0x974dec4cd61af75721071752c664d9c2a5121f06ff1515c56139a177a3ca825f763b69d431d4607e393fa74dcc91cc58", + "0x958863e6ad583a9d370a6db3639066982e44766904e7afa849b132f6666b7d08ab931131b3bec7a506d6583e93d56767", + "0x8b93a33b5da9b3300c20a96d80b894e3789c77041183c2cb21751579c8c96857f60cfc2f075201b64e95a78985c5b321", + "0xb726cb9f7ef34ddbc2fad82b3b0af0b30cc913e26c5a614ae5c19cc9c55c8e6dae069db5315a8dcb6d987415bb550ca8", + "0xa730f515398a71bddd66cab2ff996659d4e47dfbb08ce7958a41021f76d269b91c7498b708cd14b183a8ef469c772803", + "0xa4eb3b18132eb0f5337f14e01d63ca0bec0db6a43870f800e5491db756c2f5fce519d8dba5528b4bcef550d06b33699c", + "0xb1ab6621eec1ee6784e632e214693f39a14f3715991996b883d66200963e065c86fa0667f7bc36b93b40b5d90ff708c2", + "0x80486a26c3532ad6e19f76d8c9344e2626c07363fd495264927cb5935fa9565ece670dc98767afb04af6a9a5c9231075", + "0x8ee20e0df3c84a1c6b0e21bcc325cf99235b747ffe47f17fdfba548a358ca75cbcc331dd50db2311b400ae882256a608", + "0xaef4268959e5541e7ec69c921a1e81a8374d7e44bf1bb2debf4101cf3cd6b7d6ca7f441758b388de96b3e0edb5b97be9", + "0x8793629bd29d689ec94b016de8886cac6e2ca6638911babb22db4a787661422da0639a4e4089ebeb689d173abfe75950", + "0xb487b3551c20a29e9a5abbda8c50ff594826283e443c09e3ae09b914e46060b3f9abf70434444ce1487e2a74e562616b", + "0x8f11531cfc5997dd04b997cb87ba1831aa7041d5434fe72de66304e3f165d882fac891391fbb1eb955c65319e65293b6", + "0xb195136875fd02a75676c33cb3e60504d5964f7a9e81f4c8c8fd38af62e2145c55f765b3158664566191188ac678f381", + "0xb374174b0b3eb04fa49eb4ece45173f0db5d829eac370a20a62309566e0f98b18f72f3633626893c053b7be6bfbd2366", + "0xb2a2f6b0cf652775679b2d677048f2ed8c31a3269e6cddcc7a10e3e6fee89e486b50d9d55fbe452b79c4157c0270fb77", + "0x892177c364dc59032594e7a6fd032286ffdf4fa0b9e3baeb37ec839faebfd2fd46c57b2c9bfe9977b59c93a9cc0ead1d", + "0x8ab7c0038a7dbb2ef200dbbe9acbc875829ecad4883792d5c6ce283de67ccd9aa935a9cc7b30b2bd9de7fca7bf2a9a05", + "0x83745cfc78ca709835aa6c6a233c2b86fb31e3f9f6a8becf63e501f2841c4366fb7d131b746c9d3291afda714ff05579", + "0xa723dcb67925ef007e8339dc578d2622d9bb77cfda87cca0088854a59414c02338752c56116a6c1281917842e8467c38", + "0x8a098142da0af2254c425fdbbd0d1b1a17b2bd781391ab37f181775524b8563c64ab8a1602aee2ac6c0a82ba11a8b1d1", + "0xb13bd7529a9b351c5d395c794c28bcb0a3167f1c992e8c062eef47be9be27895945231d249c73a0b6949daa295e14944", + "0xa20dcd2fc2222eaae467d9f5db861040f58bcb991a26e5663ac3aa5e1ff13d0010657c5af586cc4621757add2b905073", + "0xb818f660c3cc4e9f273c25ceeabe562c8afa8ff88529c26f2cf45ae6b2813cca5f350e3cbd56f6257c4df41722dabd25", + "0xb225d5987108b24411bc389276f12509a45e86d5ad6b6d929af5274df0be11109c0fed329669a0acafdf3b0beaa8f2ec", + "0x91fcb6d04576d3c6bae947bb7843b430e5fb0592ae49b0a65dfa5791f4eaa4bf2c7f436c8de7360f217001c2b4e5c67a", + "0x8821f7a1424ca3fdc5d4a5606ad10dfaba6094cf36669fa9f84cf7617e50425405d14980780e1e18a1ecea7913cda896", + "0x990dcb7f38f56521a70cb71bf4522649fcd46ac052c7feabb0748dfcac9f9c0f95d29e070d32af3cd0adbf869535e17b", + "0xb0fac1029fe2c1100f24e2f4bf10c7672199fce53513c7dde2e8d9b00702edf0143e0e1dc7ceae7dcc6994edc2422b6f", + "0xa514ebb1a33451b4915c05114db0b10168393613744df848b24e43e09f0bda23baefd9d731075198aace586615ac7911", + "0x8b77f7953c2e67049fdca3653b8d8cf3f799677f79b954da02bdad8cc4d6c855c1c7c16b4f6f9ba35f46426ec28b2d84", + "0x875520cfbda16ec5b1d1d00f578a910d0fc052f17870ba093e22e310bb07648d34817cc2b8811b6f52de535f7046a0d0", + "0xb8c77b4be0b430851c4ff69e91cb770db1935d848198601393810ef395efab52deb9d5c6525472bab720273d5e0e7a79", + "0xb6d4d437146671bdea62fb6545395ea3df39f1cdef21b8476b68e7a25aa7354f847740576d6c9f187bbae9941f0ae450", + "0x95c642f1bccdb62cd6a2212dcdd6ff8d49aee426ca08b7cf3a9d15249d24a9eed5533f92a70c84498c0797f8a57efa27", + "0xb617978047ed0f748c305aa7f30c2dacd0db00baa67fe0c5ce346ef0e6991dc7e05f18dcb2702467421f8390f27aa815", + "0x86411c7a00b3e8b43bf22fb061b1f54ad9bbf632cd74395a478218389c0f544668acf3dd7726532d080ca7da9a5f8608", + "0x97bf684a8849626c4710a6992f6c11f6b5406fd4dfe9e6aa502425aaafe9827e2c435aaf9a5d3d2ba3a4c0e8aec79ba4", + "0x8b178e2a125b461d3180906ffba0af3dce614c64058501fdd35243ababf892d6fcdea4834ce42c25d5569452b782a709", + "0x8ebed2c8a25c61da6a6a8cb0d8f5ea179e28869753eacc728f2c076f7aed8598cd3aa0981f120f9e7ea55b3a689ae882", + "0xa6f235b8e655ca3d634740b53d8c0a757ecc75d2b8838b7948997c1985473d01943d935f687b86cee56cd47c8e773443", + "0xa7959c465a9646908b9d8032a589e41a7dd999f2ffc54bb42f22e5f8a4d8c493a31bcc7ea2cac6c8dbcc59acace7181b", + "0x96d0532df2e12da20a57cadb6cf5f6c4ee1aa4775629358c25f1d51677a3e96d1fe3b232532324b4f02f941952d4cc68", + "0x90f493473d686b639a30d1ddc9c72eae6e983f1236e162e58e967a477c0654973ea2e1bdf4ba1a44d7247bc1befc2cab", + "0x8b2d87876d9c4085102a07ebb41c565ba69acab99ffc03efc18f20e48d3f3bbe4fc6ddab9c78fe479d9ada80504d85ba", + "0x829a0fb3200a28e09cacd6c5346000e7786116ddfd898f37dfd17bef454a8abc0fe939ed8735c00769f7f2f33cd4f906", + "0x86194ec9e88ddb7150e8b03e7a535b6e99863fc6762835601efd03615aa97aaeb413cb210e86035086ed852b39c9d019", + "0xb02efd116a7189cb317ceae392bc301ae55470f0489fa89934e182aeb8c67e280299b975786fe9a470bff46827defb9b", + "0x87d7c3903bd22b12d815506f150373f518d47dfc6e5fd74347d88b518124c9923d1e4c98defeb3a45d53d50b423e2175", + "0xa1a430406b28254a7d6348bc98e697e9bab43839aa05d53faee97546f84541ea0b559162619b2045182938f69bf61cae", + "0x99d243c226c61c6697fb3d2594f3533fa5dfd7cfc87107908cacde337d7a077fa5a9dc702d26081b065edb1227498e65", + "0x800ee5006ab6217161f42db0cfc552a81728bb4fbd7af6e4620ea099a65ef6664184af3f65a07fcec7e965529c5b49bf", + "0x91bfd307579cadc8f81009558605be3edbcb8dbba271475803484017f40130b2b216aef4f620d960193be681877d3a53", + "0x96a060459dec458d19a6f8af6e49dc6c7c58c55dd18915c5fce5e0f4b4a422fce3b9632f6059388fe760289abf70f173", + "0x9921a37f3e657222c7fda3588418a9071409711d9f1fccede7494429f02a45fbc52d79fbb64e9ccd518f60d06d0520d3", + "0x81052b0d15773cb75975ca9230ebb2579700e489c7e3f07cd9cde206fef38b8139bd4976d2b4a7840495fc645f96df03", + "0x88ac37ba66d1de5e23878c992e4d54023729e97e77351f50dc5918d738b5a73faf1dc6feec7e85784761836ba1c6f778", + "0xae1e6072c13060775f6086d1ae1f88b627ffcb810fc0e0e97deea1f3a15ef0aaa52a6dce2563e4beedadc131af2a8281", + "0x8b60a340f5e4f90badf83001b495ac9f13974c3d2054ddcb3e6b8ca99dec5cd63a263e05c282454191ab2e087d5a2911", + "0x832e2d56ba69dbf817b2b9dbd25c1538d5b8dbf5d9bc05e6be85054a423ebb66a71b157e166e0b9444ac171b34b7ccc9", + "0x8586036fc7dde1e7e3ecb61663130c4529866ae9f5f5095b9fccd24a4c70eea899aae5f10ea1ba66d1665b2d83be35b0", + "0xa77969453b5c083a207913272b5b69d4ccbd8718bdf54be8fbe11b4bd0a2168aae3ba8f9362afa69c0ffa28d7e5a2340", + "0xb7fe9568c214baad0ac5f83745611b481f744ec1c4fa78a549b180dcf79633e5ba75dc20055012a13d849eb7a9be57d3", + "0xb01cad1d2a6c51c0ce88243d1f52f95fb5ee315a905079688027511f0c4ecd0563a3a81846709d272fa5ccb9665e8043", + "0x8eae0a21adfc569aa57237654021c2bdb2c6f0f52ccc90a126682c21a1f9413c63d285f92b2b2f8649150a9284bf70b7", + "0x942acc947192b5f3cf60e92383e5d35f79e7a5904e8e9fd1c8a351676c83ad29b0afb6578d555457cf909f8f4d27adfd", + "0xa74e092f8628fba9abcabc27e2e9f3d5a9a941dfe50a2dfde2ad179aabc73afd196676925c2d98643ab8b3d02bdb66ad", + "0x896159daa2afd757cf3f9d34af248ad68bb3c62e4c9ac49919422727479cf669098f270b9e645607a7d11adad4c889b2", + "0xa428d8370813d78e7a2a24eebd36e9da2f8bb3605e5a39b5fcda939b531c35a8ebaaa642ba556250a37bddeec90326fb", + "0xa5fa04eb60a1d5ee9820e78f42f7be15e1c02757b539aead995768c6209684d6c183c71d282e0c12a4c15c03f9a89d4d", + "0x93c77d5d220e40affa7269a6915c076c9aef4db552c643ae5d560a79c955b491c6346ca4cf11cbb7fe1894e28d47b065", + "0x802e605d2de745eef6981d88e7a57ef4046a2062725e8080995374cea2b3273c27f35b7774d0dcba014710d8d6c501f2", + "0x82f7169e6ec9b3e2bd450f35ea2e66d06bcf900acf5b73139677b48e078ce2e16599103027b2326770c99c0a690f2015", + "0xb0c8581879439f9b997551233fe2de71aa03604f9cec37a7b18c5854342d9b67be468f3cac4bf6f64fe8a0066248c498", + "0xa3f626848a4db6e9fb01cac90d3362ec521e969ebd5228af694ea3671061476149f13d652942ac1e39f65591fed740f9", + "0x88a8e759b9cbe16a7c16e43f4afa2de6100d2eafa4dee75ccd653ec38c919013d0a6b35c1ee1eaee7c1985b58bcc9e92", + "0xa3d5fc7aaea072798490616552d947e95f49cf02a420314307aafb555287ec607d75589ba24b009cd68299dc6f7942fa", + "0xa809cceeb84f9bcf3c3ddafde3041e7bc3b1d14df8830ab849002176a0725e6f16f70774d8962cb0b8ac0dc43c4ac66f", + "0xb8f2e46c031cc8fa160a08c2ebdfa85345ed14771b06daa9636b0e7792b7fddbc501dfc85cc626a01104a43a7d3230c3", + "0xb5367e2a521c318b802ce16ceac80c4b8139f73ddb10ddf38433397cda70a86ea1f051cc55626a4e99d27f30f3975ff5", + "0x96d963660121c1441cd13141279cd371a6a0aa18b6a20761b18df60aa9c14e13489afd83695a0921d5232efe72045f07", + "0x80818d492fd85d666bd91aaf6257b86527fdd796773c793407df1d4a0f91d74649a6bab4d15155c36ed4c6e0a32c5636", + "0x931e22918905fd6c230d3d867ea42861f3074d320d14e1929031924c8ac209a5c552b679b24563bb12f9749b4ee983bd", + "0xa4de2c333e74ed9bfa3c0bf6a0beb90427abd9aa4221294cda74331646b58ef46ed57cccc8798ba2b9309894b17cfd69", + "0x883881554c1d88c0ed8d3b6dec3d200f6fea69a77ace3e4d6f86b41506a23724b4394ec8384075f9c75c3868ba8a8e8e", + "0xaa0539ecf6ec9bf06f24443027f8f24b6b3d8c5b2084248eecd4bcad3c9a69716e1a0d01057f09a65bff1006ac5e157a", + "0x856d74d44c943c9e809b42dc493dff20eca03cb0cf5ed45108c69b1f90d8592a53ae8100e99380a274fafad23e74cdfc", + "0x9188257446661c88da093b7c5ce998135913f63842d7c1586065377b169ee35b062d925367fb9b909ca971f1188667b1", + "0x8d3aa57cdafbe998938787479f5d590c1484c6dbe94e6c487e57a746ef5252be0eaa5976d6270de7db64b6b92e57a0f7", + "0xb8f4d6997240f9eda5aca0c43323a828d1563c491b3db2087f60ac4120a3fcd06075fb42bb19d0339ab5ee3fb7db25d2", + "0xad247ea94b8ae1e81eae4c9fd7b39e6601b53cff47b2547ff90a3cca87192eae28408082774a1fd14bf9ab459b7a4f1f", + "0x9598598070f8bdbcc49056c40971e673726cd8c1bc4baa0b5124dfb5fb750e7baa7a7df18eae2bd91955ddcb1ec67955", + "0xb874131ab1608667fa60ea29092d090859eed1812e90c609afff96d79e82c5ba546f617f4c96fc32c9bba97431c1e9af", + "0xb00750a9cdc75c2a54f0d3cc99b0fe02300754f25166f7ac85ff41ab5e9cfcca33a29be76a480f12a2d410c7cd5032e5", + "0x84b5bd1c90bb6c66755b28ba4af493ca1b0c3a4df9f436aac67d2e07289053f925cf6a149a84e74e1027dc8758150179", + "0x99caf64bd9d193ff306e8ab5da3f1bb2a190a60c3a82099b8d03d17fa810dc53d176c21379f479e828f60d25beb3ffd0", + "0xa8fd9de502f1c261d5733430e5a18d8b7892a98c9529a016fc2ee53892ae965dcd9c75850bcda4c7edb980b8d88e60ea", + "0x848c02cac636e047028a3fe8c1bf4066fb7591b96b0340f8fbd476ff01b35fa3e37d309333771a134f24800e5f3f9289", + "0xa1eab1a06dcca3439f0166441e7e7f2f5b56f5f8aa9f45e411c561f556e0fb71c514c06c26ac53b49a576caca5faac3d", + "0xaa603f970dcbe953e700e61c151182c8d32cbbb53ceef572ac93383db33a4b098b5c7b267e42d514ca66b740c0925efe", + "0xb55fd5301bd700ddb0b4f72fabe9a91ad49759506101fa802ed1677e9553595aa4d2c66f7574e78d21ce882ce0120ae7", + "0x829137bc4da7b4886d3d04d2c39cbf4b1dc40c813ac1adb425c7b9abf9142b516314cab79c68454df5d71994ce416144", + "0xb83a3a22735001f783dd48a01c4fb3598a51ff3987e842b8045c71c035b9e43645a55254ca5911a5676ef4a8af12d056", + "0x8ca8d463deb13f9eef5e533bc39efaeb0c15631282c5c0deee1673b0053a7cccd514af09801dd6c158caa159fe9351ac", + "0xa9ffb1427828f3c456b9c8cc50782de1ab0029b9233a0fd998bad0fd014d27e15c4a32d1e16ad41bff748378b5abdf49", + "0x9627e29f725ddd86456aff813976bbc4a836f4deabf5ad9f73d1a260ceb30948824df9c8841e6b3c529652202be181b3", + "0xb52c988647fe3d9276eed3c262e1044f57fbb116c64cf4f207235c205b3fda0f3d789bf90f5217401b468d85fdfda404", + "0x833bbd6e2924f5c4446cb76b881d1434a5badce9eb9b003f85d076e297ad7ef45b822069fe54d17427a348c3263fb838", + "0xa067a36352db6f82a116cb87d3db5f60b18576852409e2076cbbfc7843af78866313a4969385a40271051dd195d51116", + "0x902b99545971f9a103f99d7399acc347ac46fe156166e51deefc0e92aebf5893460c69aeeae11f5af9f49418e289ce6c", + "0x9206a0e9ce9b9880f29ef0417c96931985f5d83bb17cebdbba4ff2af81a3d37155b04649426f698aed372e4f669599e6", + "0xb54a5d7c976e45c0b1d44433595eae9d1ae9aeabfd58cd5ecb0c5804756a7b01c9a517754423b4714a3695533a3114c8", + "0x91b612131e84580ece228b81ace83da0269b53f94d3c02a1a0879ebbd81bdc252064b3d03a7e140b43a90f237d9a45a0", + "0xa6cead3b8607eaeafe37135bd6de8fbd16f806c131eb71c8d36bfbe295d45b070255e50dabf076e2c3f6b8699be71d6a", + "0x931da21e67b11ba6ce438546a24d063bcd51aebe39b4220a78d9c0aab88b2d37969b5ef3502d835507f9c8d6d006714c", + "0x8fda408caa9daf01122a2308b7b9d328f52e1e2f138a8bec30492488f4d710e5e52524a6455a3a2ae2818ec8a610b650", + "0xad8ad5c189644352d90c462731c46145410e5adf38682bb80f95495dd64d9d13782537d68690847bbb06c6be7175dbc7", + "0x87bb5cc466ade60feb0961421c3fabdc8a7e20f11df8437bfff63d3f8bd25305002a396c9d0fa4fb9a9986d4717f12c4", + "0x827cff72870ba00c29064a7d2b4973f322d6b6de7924c93d8bf8825e7a0e8478c7748f90f5c716bf83c55b2795d315d8", + "0xa225895a8e94229776ceb51b05356291f2dce748be17a60d5aeb33ef8507c368bafe5d1d6eea927f28b9d1422b661b9a", + "0x8e011323ce670ff51c964241a6b72e0e0ffbb3ff9bb2762492323fc3a4abf4718091be0945287c7329850e4f74462cde", + "0xa2c03c2e5f4e9d3ef361f68b188451994ad1b24de9f323370559c8abfcdc7bffd289d92e78a5f6b104b0a12c84dab2ef", + "0xa22b4771116ce22276fab1fec6826610707ce8a342f9f60b079c4e0259dac3cc41c96c560dfd0ada6edd2828f7c0e8d6", + "0x97c17441d0af9be83b42097aa8b7cec84a253b9a2b957214b8fa93c26d2add46144faffa7b8a55312059b10690f711f1", + "0x94bdf348849f31a2737cbae5e5848aee711067bac85c11c2e68b44c398cfafbf3493a3226cd1ddf7a916e7613fc7b6f6", + "0x838f59c6e8469a8ec6fd40b978a3607439aaebe1e50ff707eec72c0b8278af05b477bf12a384b56d03e3d4eb91e56f67", + "0xa1940f0db58185e2b3aedd2b0bc2b73b4a65c68e09b046f38e9dcd4e13c94f5406bea92635190bf315e48ec64eceef2f", + "0xb2f4e0ae44e1f1210a91d8f280f17091fa994034ba8c991583f8182a323e9b3001a712e3584fc2d64ecbf2d319d076b2", + "0x9342b89c721338d02c7854cd7466fb24d93d7313b6114ea591e6607439c8ddb911d1cf35f01898e9c557982bdff8f9b6", + "0x8583fcab15be1dd14d5a415f4b14d706c8c62f058500f1344b37730c8be6741779691f87ded3cbcf6516468b373cafb0", + "0x8fa9587c7989646571ad9032f34cedd353caee14f5be5cde1e9e0a1710f90c08faf6fa96a60e1f150f761c9c8ae7417d", + "0x8d9ff904cc08141f5a9879f5f77dc600e6edbe859082231a4d819953890199bcc5f940b730ea688332f07e5279d49e1c", + "0xb5f82b46e5ef9a2df8d144202d6e2e4f3bdae8e2048d2af5ea7deb3f722fbe6d370401954e74ff0d8cb1010ffb1f38d5", + "0xa3b5b57d435b06ed70530e060002a8fea71746ad07d969ca23f22b5e52624527595b6a6d54b4e953fb7b7596bac378f0", + "0xb90f89390df6d4b7879b915aa3c29b8d779d035033f8873bb7ac54a14ec98f0d08c0e3bf696e2ffa7b5730d736f571f8", + "0x8e81e371b92887e43d95c0dbdcc9575282b26ccebdc8cbf46587e4f2a83b61e9bc0c6d7d1f114b9d21e04fd6c180b12a", + "0x8d682947c51dffc6e0fe0a486293c9ed121f441805168236393087cf62f2a429cca60bf0e472564844347d32c6bea27e", + "0xa8341ec7dd189fa7168759240224192c58209b53fc961c18082deba217928c399bde08ceae42bffd37c1135b4d14a845", + "0xa94bb076dcc5ee5ec82fac57c5b384c690df12631882bd1b960e1eb8c04f787bc22b7bac315b9dc5a8a098f17f051a0b", + "0xab64e1c6f01b87706c88a3bd974454a438722768de7340b834ccf93ea9880c14ee7c2181432acf51f980d56de73832ee", + "0xb7b0058bb724d879e5ad7aed6230297c54cb599ef659e86bf2cc84c38225899fb388391df9b2e6fdf063171937fd8c72", + "0xae856f4fb74c27cc98b67429186e7df4feb01278cd57bfd3170af6e52e0a23b9e926bf9565a890cfb4ae8f2d590b2cd5", + "0x804b9c6702f0596d328f92fc1ed5a30a7ba17b9204524135001b569233fc4937035031d079f52fd04968f37c24013898", + "0x84274ed1af6bd6a968583995622b4d18c6a2bc703ce0d0edce45bb736529b4836343dcd11911a94a134dca7877e6cab8", + "0x88808098463f7505034c3b6328c8a08186b33f7a981c08376e429dd64b79b97753170531ed078dd265ded4ec0a1ed8d5", + "0x92823bfb23a4eb84d3759e7d717f0c8641ece0927cd2ba8c728c26bb35df2629a838002f353c8d3d75eb19520aab5f25", + "0x8db36bae4d960cdb9c51f419d7ddc81f372e56be605bc96a9d4072b829f05527c37c8f255cc6115300a2a0d2e6568d89", + "0xa8fcdbd7f3b4d7ff04149a209feb75e97149e7efceaa42d66a6b8e432590fe7bd01f1a77fa8b47108f670b612e33fee9", + "0xa9f4c53c62db7e5dbdea6918862d3c6d24b5bd8732a218edf0ba61e9d1861182323d8ecd7bef8f895b42970b492f6e40", + "0x8b95bc7f07818f4d7b409aff8da0b2c2ae136cde386f53a71565cae9fd14c73c13cc1cfd79c0f97cd77839fb738c5b9a", + "0xadbd1d11adc756b51a571ddbcbf4392415231ddad93da09acfafee03a9e4f9e1ce3826110619e5271feadfaffce3e793", + "0x95d327c8bb195cdf25fd79c98f9406a6b0316214b1630ebcce95bdaeffafa36fc1accc6882e0e5d13a8db5c0f3c0e61c", + "0x8cb2f1e2fb25558869afdacc7bb866544cfdd566cefcd048b48d458a886130bd086ecb7600a960a7f2563c61cb326510", + "0xb3aa8c4bf5b933d89cd74ca7f7176d6624d562d7d58b041328b49d7562a30b489cb606abb3c49e85baf04c28e9cd1f44", + "0x97f9053a85250c420599827297453c2cfde087065b823d9e43139e6a9cac3a2ec40a1b6e2f0726bdc870fff215462f0b", + "0x878d5dbe6b881389c2ca126ff66d87127c9aaa3f62f0d2c1ec0ea2b279ac95f8a06710dce166415db227655e2345a04d", + "0xb2c33a6b4203e3ca5247f0890e475518317ffc44cfbb1da9a1ba02114e8b752bea618050b876de5cf3b1906140a64471", + "0xa56170c8313d2b5541a795bea9934d4425b185b5c409f0484df6f44f0e4bcbf50b860ff46b7245cd99c1cfa8fc1965b7", + "0x96e2b658e2876a14147385fc423d2702a3cb76962b6b437222cf9cea39ebf4bdc03bbf434b747866d4bf72b4ceefa639", + "0x89c4a74fa2f067e7ae49c84ef782c331bcc9245db7e941804e2e99d12e987b4d25cb827778ad4c3566c4fc68018650b6", + "0xa01d30cea7d01c80ff26650020fab02e78fc3842e2398a81b44b21d58d4e9816166ff4ed2418831fa995a28ff35cb6f1", + "0xb960c80b55a8845bbf24bc3f23b0110ca701f9544ab6a5bb7929330213cb471321e55c390ceca3e24bff69bdb0d331c0", + "0x802c5b13f22be7be0e5db11eb3be0f0ea7f9182c932265060ba05fba20ea093dd2810d3b969ee3e387e60fe6ee834e8d", + "0x92478f88ef7435d15e39a97916c736abb28ea318394b88678fddbbaab3eaf31776110936abad116a8ff6ca632dd12043", + "0xa6d3da0370c303001d5ed99d1db8bce1f26b0e442f0f042e36db9674e92dcd6e80465e772f1e669f99221caee3392fe9", + "0x938f04f70a8f947d6df2f0c0e9af3cce0c06edbb3c131970dd60884fc0b0a0959c504a2a36c3ff76dfe919905671626a", + "0xa7117e55224230822e9983df2132347eb7208cb6798f291df926ab51e04b1a1f78d5568c9a8924ee6f57426134360f20", + "0xb91074c77ad93fe48dc2b10c0c5a62ca3ab7d98345b919c52d84a9dc419b59fc1b267e1c2d4b2e120016ef84bbdb0cbe", + "0xaa175c6b6edf02fe8778762c9575581c0ee6efc9dbf99c291a41444a23a056b893be6c45333d907d0bbe9fb0eef84d08", + "0xad36dcb4e2ab425aa339ae464b038d550cb11186741dcf257f1b8b80ed4f32ffabbece45e2dc1525d4c3eeed819ea04f", + "0x91cb35c1ffa9cd5aebef523edb8325078da3eb5cf9e95c675a76446fc7692aaee6f949de064ca2f3e0f082cc3fa93e20", + "0x82622f9410c143a86bc4d756b3c7b324dc295231ce865de020d61cc0868f2c150a473cea3a5b756b36771ce1032415a5", + "0xa5c29996ad3a53468ece9356a5b4ccb68971ea1c89cf39644f1da2d4a477c2ea99bf791ef902b87c225d8c53d67c4c92", + "0x92893eceed1af34fa92b23dcbab175b6a0188a27dbac9ad3317c4e39955a763cb383ab13fb1c519cde311d8a4d12e8b3", + "0x8a093cb191b94b0200e38d31955f9d240e2be1edcd6810a2396a061f17c3ddc9c4f4d56766ddff4e121be7110e03b869", + "0x93981473df0cb1f4b47c7d9b64e3123dcf1593845b401e619f5d7c70b5dbea375d1ca43fca65845fcf0a6b2e0af43791", + "0xa6beb6b0697070f9562910add88d9ba91992f8da127b27be81868b1596d1012f09ea7ed601b4a6474c921a1a1a6d866c", + "0x92026b1ee30f2ed61c9f30337c3356844217926aabdff383c19ca3c21e0bc49811ca5b308012bee4ef250cfae1615800", + "0xac0ebaea6d35f84dac4ce648af096305ba68a7a0aea0a11ab2fbe3162075444a158433c98141bc92ef3b3400d6deb46a", + "0x83046f482dee24ac3ca83373f0d1b82ac1c4beda0f229a9011a81ec659ff5fc1fb105e219975b5c744308c77a24f71e4", + "0xaa5a312c47ff7248dcb9c6ffbe5a0628ccd565c07365c4413734d415cd4fb35772622ed833862dddff520a67c509c6a5", + "0xa02fb88805c34018ac33582e19ed0a7e4616acc3dd0867e5f21914c2031c05c6dca30b8b35b57c2b137750f3878a6f8c", + "0xa60528f1f14bf0c496491d46a0fbbd6c343e4eb3f1631e92f96a3c5e5c684091aabe5801df7a67f7c6dfd1b0d35269d4", + "0xa1fd8e7fad8ca05a340c05a051bb0eb4197eed345f4104629a9e38e234b09d789cc5537024615feb4a6177d32d39e39e", + "0x8e70e36c1aa070815440e19443f1f04aae23b1b59fdbcba43b47b94a026c82c8f66c5dfe54f826f4d95ee1930cdb8008", + "0x8234c1969fa7e9079661e4ca309b71b1aaa10f4372be0b963205c23a81f5a3d52ec08ba9ff65b37f832b52d631580d61", + "0xa18cb4134127fb37c4abca328cd0047378a2e1423490af2bd3eba9ffcc99ca81a3c22404c0886f21f65c7b93c41d7981", + "0xb46fa45fe538816de776eec086e040005706cb3eca097e290abfb6864e745c879868aac8361894f3c3564373ef9ad55c", + "0xb96ca43b96c59e95439f75d1e726a35a9362f0dbd34963b156e103e080a8126a8dc3501f9fd541ff3bcf4677f5c4a86b", + "0xa8e8c87c7301613818d57387009e601a7ab5cbdc2890f63d985c30c74f9cea2d0584c116baf0d9cd5594386ee93fc661", + "0xb47e4f1b9153ef0981f813948150f283b47a7346fd9921d51fe8e4daedaef78ddeb4fd467c2ccb7cebd9816243da1c6e", + "0xa370c202a99c8441ffe96fad0f801086d4d7cc7b960f6e98cca29ceedf492afddfd0f351c9c4d29ac008bc255ec1a2a8", + "0x8f5e6ce1655d1c059b006174e3f5a55c88e1821c97f9702ad8e8455d46c2a83ae4482f2d43edda74a835686ec45a8a15", + "0xa30421e694930a3b65d397b2720d5f8e1eec2b6e2bb5a28d3f9b0a84db9aabd83850268bae64c2b10e313cccf120151b", + "0x8abe87163046f7a9b18e2a3c0b66e258facc1b31431420e0b70354b7a60ebd250a784634a76692e7d6f4330b62114945", + "0x894f033cf077d4eb312e3258d9dca414356271abce1d6094ecce6d018c5fadb1c15d8d69451574ad0701a2876db191c5", + "0xb0923d64f88ffc872654e1a294bb1af8681689c21cf08f39afe51448a68e60a9a0a74ccce9969276a932a52c07d095a3", + "0xb9ca23b5be8725fae7fa710eefd45522889c50c29c26384e00b78a962384f0aeff9d15cb5910e9565da12a577eb7e5ba", + "0xb242ccf292757197a9f470f2d80ccddc48c7f1235ba026bc68a93be2738bc968e8a200aff3e2f4807216442eb3fc50dc", + "0xadc2c3b375b308524b79a024ff87d122055440643fea6fc0a651bdb312c7cbe6a456afa9d342bc76446d77d8daf08bc2", + "0xab645955356c2ebf2f3df9da275e01daf0b44a52afc309277d6d9ad1b05484e5ae0d9d41ad485fe481e5e362826a86ae", + "0x8de96ac587a4449fcc8b7fd0a51b4b5185d9c2eb3434f94cbadd092de1e26b0f6b3f7b15a37e8424b1429121ddca0ecd", + "0x94c70ad4e9b871566f3da98170b665a09788d421818299857cde0853789fb943cbcf7d4b2c95246ea7b72edc56a8e36c", + "0xb2574be63497843340700b701d5cc8be6d23125bd62058802ee67cce1f3b5f5602b27c93fea5611f27dc695ac563f042", + "0x869ec89da7850cedd88bcb3a50a15cece233119b31b64a61bf6b2310892ce42d8b473b584b11e61db29ed24ce8033f83", + "0x8fbaa269da8e28e9adf4c1b08f109da786dbe9cba871c32eecbfb10619b7a5d65a26f9bb33e201a8ed20b3de94003fbb", + "0x8bf7a059c37242caf7f821a6314e4e4adf799e0dd86b37892a7172598892c07272acebd05b534755c57b51556b2d610f", + "0xb4e72645fca459898cdd9214892ed08b5c99f82049c0a30d72bac0b9717caa9c6cc16c3dc7aa6ea4d42dcd2a6c175df6", + "0xa39170da87a3495da55bbb9701c5461f3403447174ed6a4af75712f7ba4ac35f51a4234bc4b94da888a0959ee109c0c7", + "0xb45675b2774ea7696089dbf7a0afe6c22e85fd0e4ef3db508fbaf96c9d07f700c991789206da9309fd291be696357c5f", + "0xb52899e3e3f6341eefcbe1291db6664bf3b6e8021d32fb9c3e37b6258a35c1da927747b2ce990937d6f4c6c3e7d020d2", + "0x84e5bdb3dfe19700d79dd3fabb0159ccfa084f7288db836c855b827613ce8071067c8d7ac5cc2b4e88ed7f84b690f6e1", + "0x801477d200b6d12fc6e0a9bab1c8211193ab06e44551e037a9b4c36fc2d4f67760b9ff4eba9a3bc7b6e177e891f64ff6", + "0xb6b71a5116d3c22af26a7530f535e9b7851f25a84e562a8f17a125d55b9b3fc1bd8cfe65bdcbeeb328409521e802051c", + "0x8687e21c34d7804c12489d30680d131ce2133e2981bfa993afd8a8eeda958ebd5e6881d342d725338659882d9f21cf98", + "0xa024e97a7c4de32b6383c34431994abc533ecdbd6be9bff836ec1af022f5a86773bf345c6f33273797a61fb70a8fd5d6", + "0x83f784f095da20ce5b31f54d6cb14b32a8a12675f0029289c9cd036b7c87a8077be2d04a62618685720e6ee69c875e97", + "0xb4e9dfe7cb9d9efd3fe00d99ae5e48769d4af4bf43d4e05c0b54c9cfd8bc854de96b8d3ebf4dcc06b9dac66b7471a0de", + "0xa08b79f9d4673afcf7f38b57f484f88feb7c908f597663a2417f92c348150c2be6b5603f914eba0d9d5bdd4e5c5572c1", + "0xb0eaf919589988798cb01ba0610cd1b7fa3c08715675ece8ecd5f9ef6d5d7b2c4c8ae1ea7dfd202237171aa3e6f9de74", + "0xabff99a98baae4dd0954052503ce81827781694a5ea8c1149f96a3adde75dc2d630e138598cd2ae7fdc7a654aa17df8f", + "0x83e369b8680d8b9d995222b033b4f4f3e3b20e782113c941325c7fa9c742feef8747e4a212d9aa23285a259cc4faef8d", + "0xb16d5855dd2716613697eba36e2fae0872aaea6999e91cf6552f93f9a0b85ed4f6ff922a91b50816bd6cf8e7a4513fc9", + "0x848373db600e32e741aa1d37726bbb28956783f89ce2d781e95fb1ee1adf4359968a141678af268077eae4c25503204e", + "0x93a0dd0fdac18a31875564505b4e28f9e8bb2915faae666538597731ac56cd77f23f2456461e2f672983fb24ad91f6e0", + "0xab1ebbe49fa56524b564bc2e43784147073e6ea5d27a9540fbf2e04d0f87c645ed2fd28b3e4982cc4c0af1734ee47a6f", + "0xb3ee30b733839edab6f61f0738e3f4afaeccf700d8dc7415684f193b36d70d07acd5780cf539f12e0fbf8d4683be773a", + "0x88388f2cbdec47a6b3ae460b69eb0d2130ac14de950c22fd86de03e40d02292bb93cebe62432da39d509c1289f785fef", + "0x9370c41a54b68ff486b4cc6329c3a851716ebf1d088d77a6c56dec93a18b8a77b596cde74cc17d2adb2b2f411a2e4bbb", + "0xb9083b60dc16531f77b05a955b51a237a8f8c0173d72c352c5ca441b55abbc890b14937e457aaec4be5cbbf80cae0099", + "0xaafff8f6c6ebaad952c65054dfc7c829453ec735331bf8135e06406b7a9f740c9a200dc48bb2175516b41f77dc160121", + "0xb43d31fbbaf10526809e9e5bd8bb47a76e0fabd7852ee7744404559ab89f0f215ff518f3271a6aa972a459cab82ac558", + "0xb581ede48c6ef34e678f91dc4b89507413e00e70712e3e8c32a80eed770ec8d8b98caee9702d068aeaca6f704be57bd8", + "0x8cb0a137e68b001a5ccac61de27cac9fb78d4af7b2f5a00b8d95d33ac19cc50c69e760c5e0330a85c0ded1edce0fe6f9", + "0xb947fca07c7aa6c2bf13048275402b00b77b28f1d0ba4b589fbcede13f93b5b931c588560ab8ceba23bb8e748031b55d", + "0x81753cced5ff819901740a9a584334e355b497cb699f0be5a52cd555a4c9f149535c7bb355b54407f7f0ec27de6c2e19", + "0xb3d59273951ce97838c4853ec329782a255b5fc7c848e7992ded1be28a5ada7fa3254123afe32607b9991ec6e0659b08", + "0x86b253de246f82be1cb0cef01e87c3d022ca1829d2cc7e6a160a5afbd3ca6b94d75739b122e3bb16f8bde28a8f3223ba", + "0xb728b659fa2d8487e061a37f7d14a4c2d70cc37497a8715695d8d332cb274deee2ce23b9b5f6a7408516c02c3d526a49", + "0x81277b46d98848a45abfbe39842495659dcbb80dee985a4fc91d77d52b815487aa8bb455f411fcce4c3879c7a075a93f", + "0xb05b6f1fb4a6e654f0ee6b83e08b58b57059bb0b7c490405bc8d963c4a2d6be39c558917977e554e1e9e3169961cbf3e", + "0x88f75fa7d016fb6442551ec071cc1e2beeb3ccd213d16d744f573a82f5d70f41dd1b18af71d5f9e73d87f2f6b7dbe889", + "0x81a46434f1bbd65a661a0ff45a0295b8fd8a42a7969c5953721bc98698b64bddee3f806876d1e9983063fdd0c11f99df", + "0x8b4f6d33c510a4c9c7d623d9ae0c9aa631fcb987704726b2a4d8519372123bce3c439202f25b5b47045ec14ce39a21a8", + "0x8d5112b330fb63cf6ef3d2164b404c14ff9907d685015701399a260951912b19b8f270f869df317e9050a127763d7980", + "0xaadab394e84dfb82db15ecd2427f39b62352c3e1647c3bcd14fb24ae830ad0116f0fed87ddb63963b424a4741961386e", + "0x81ca4e5600d00a3bda24cbdea7a532a4cbbd893c10e7ff10667c15ffa8138b91667abe5466b31a3dcdd60155c48538c1", + "0xad943af1b8a5fcfcf309ed8f2f916339f254cd555c71a407a47365a139306286a05a8314e1c70e20a65fccd75d36fa12", + "0xb16597a0b437060a390467bbfab94c0bdd695ae898894f4689f939e30cc2119cc08ecb594546304adf876f4e275ebcd9", + "0xa44a4e0a6693be356065891c27eefa040a1a79475be53d54d5fdcea7e0668ff9b35f850974000ed119f6865aa6faa721", + "0xadef27d1b6e6921f4eaf69c79e2e01f5174f7033eaafdd33edcfa5119af23f3a834ffe1bdf19576581b797abd1865b34", + "0x90c1e9202f3ffe28f8e1f58e9650dc4ff4dbc158005b6f2296ec36147e524b4f2f87f8aafc39db5b006fe0c491c92f45", + "0xac817cd54288b6f7fe6338415344fc9e7b669414051631ab2f27851c052c044be06bf7235d668e194bef695923256368", + "0xab14944ef653a14456d4ebc12e3196df3f1b4707c4e50b317b5ccc8ca3a0720f0330609f0e7e71793f6ca01583f38c70", + "0xad5353f2f380837e5ffdf079350b3d42935a0517861d03af98db5ed3ea8501abd68885c8c65f5a66e944b1874826a450", + "0x8b5583863f84af8443ce8970b02e26cc5d959e47efbf8a66a54106ab165f1f76b36423aee74c7b5402fd1c4d7c1adfe6", + "0xb3b46037eed9fc30e4f8f0da8bdbdcc40a38e22e876ce9fde981883017854aba82c18eb00887d92ad847d30082fe7271", + "0x98a2b6fc90b7ad172e4368c1e54675b75c8bf2096d91c9f2b60b3397d3be3b705aed5389845dbd68f0f84438cd0f7687", + "0xb155e800852a5f90a2eac69cc4483428da1dc2c31588a13c924e60a7616ce9baeb7d4b829c772b260277cadd8ed84719", + "0xb8b92c520a1302b0cf7d993a52e1dacd7f27bda9868d59c55687d995ae676b7070af4c0792a9bc1c2635d44a4fee01bb", + "0x96dfe9bde526b8fc829eda825f55168b88e8f4e43d4d708cc3060df03437b46e12a8ac70d7788aa75760f6294d3e84d8", + "0xa3fa66c54e2fa084ced3bd838614c6c33042f492a5745d167a723c60d5e7d6020ffd1747981a23f8b68df21ad8f0fa77", + "0xb573ca10cc41fc04a642f6f62c355a4fda69b94b8e95dbb02fd1ccce4bce1191356e1fd66d372159944eb36a7071f005", + "0xacd0a1c9abddfd0ea223eda1722aaada362d34234455bd1c6be115d41e535b16f12ca428da7820a757fa4c98884a385d", + "0x96f242eee99c4db383b8754fa7987c0c159652e1866faec905a8d3f010e0a1ad05bd77b9ea8dfd653738959180f58430", + "0x9215a9b672a5d6e435e0e0a45156e0e20f75cbbdf1d14940fed3ddb63d433bef643796c7a4fff881829ebb2b2eba9460", + "0xb8ad9bfceaf08dc5a874387219ddd1170bc3a5e25ed72d321d59ae713be5ddf9fdfbd3aa7ab163be28dfa0dd14614e19", + "0xa19a1050590bc500b32c502f393e407abc3d8e683d6f6b978873aff3e3299b18b1f6b59e2b0fe237d819dbdfcfdc98ca", + "0xa6870fb11d4429686e52e1f44c8dcfc7ea24a020df9570c021578dbc1f9bdc8cf797cb3a72d7fc52805dba35d59f2cd0", + "0xa7be733b64d5c06c127bd1c87250e42bfe30ca91ed8ce51e0b6e377f454e8f6fef7f99bff650695df2fd10c375da349b", + "0xa1b97145dab30330eea2cdc8739b2446a3704b64505fcea3dd8a9b4a72edf222e98d967d6fd7f76794acfd97aa091065", + "0xb2127049907d2a3b654d1c940b740bfba3dbaf660f86ea79c2f909af7c9fe2a07a1caeb1be12370aeffaf8faa50f1582", + "0x8a207701214bb28e99b0784e9228b1c34afa701966267fe7110f6f29f5bb41eaae6cdb98844d0400787978fabd224de8", + "0x9925147a383b6f5f814520220ffdbf20b214225882c3ef49b1a1ca677709176ec82466fb9c4be2dfbe5640afb63b014a", + "0x8416ad93871623fb555b5390b80de99edaaf317350cc0c1ae9d54d59517074d40061f315cce8ba2026d9c1e6f6a1009f", + "0xa315f943deebbf0a2cdbcf3f8323e215a406e9cbfbcc3f6288714cb3a6befb1bf71b2a21ff7a2ec4731c65044c45b6b5", + "0x8213e0c2539c24efd186ffa8b6dd401ad2233bc19166a0623b26dd1e93614bbf792823f5599ac116231e2efde9885709", + "0x8e5cafd2f34a127a4a896f05e4d929eef06972a1826b3566446942198df26d62f7679b987db2b3765d9d8058b1cd85c2", + "0xb5302b399c9cdf912fd59007ad4737255552663b1e56dbe64a7b2ddd88d2093c73ea319b45db2dd49d1e03f5bef1a0ae", + "0xa0c2bcfbed4b008e1a56e5d2f2419aa59d7dd0ebd990f1c18588de702ad0fa79f445d69965fa9381e700eda13b309378", + "0x80a44eea1ffe24c26b16b8e2e70ee519258b9ad4b3e83cc4e5cca88ebc48d0160066f8b91d0581095b0de2428390c8b3", + "0x84a90cb9c7d2f799f1c4ed060387a4b793ab41c5c3eaffd3b60face9b9c3bae93cd2017283bf3de1e3dac63d0d84dd42", + "0x81d22febca276a05ba9bbc5591ee087b0491beb35b4d9f8fc0d041d642a574667ddc57660b20f5c568f7d61fdcb41bda", + "0xa3ac965ac27a28e102a439b74fbfc157e75fd57620e4c0750a466165f8aeecb2191dcf8e656f7525aa50d9c7c69b0b5c", + "0x913c17434ff0d9fc52e2ece4fec71b37d4474a18f3ea26925c1be2b250434d49759f58033ba0fce1c6862c6197930dc4", + "0xac430559c151a5e461f67b49c7786c97e1653fa8698e9759ddbdd99f5daf17fc5a012ae6330739440880728f24eba7c9", + "0xb10d8e9f8aed9361b042d1398ec74364f7c7c1cc5c7f917060572761138bdbe89bf409389ee3879f93bc8032dd67b308", + "0x937271005a4cc6a6ec134870c1b56471aa84ed4f4af1b3d5f334bc0c42762fae0c9a6a2828d3de6151a76dad7b72781c", + "0xa10e4dcf51889f69e6bd4c052f8d4036b9571ced98a3d7d779cbcb9fa5c3a82228566ea7cc1d012bf56dea0a40c5a64c", + "0xa0ed026528d9a8bb3201bc9dcd20598933e8c72fd315deea8da63d06e97392aa729d98a55a8a60fa4d5573513ba5c9fe", + "0xb723fcd04cddbd4c36feae827a03746ffef251c4f4c55a88beedaeeee194430a99f566f483668a0d88b13e7a4a37f1de", + "0x84a2cdceed44828c7c05a6a762edec0165e434e7029df617d6646aba48776e6c3b823f40689cee136536f8c93e08a629", + "0xb786264e3a237ac3a1d56c9f4e87438dfed620c867100fd38b01287f5b755c7820937403bfb86644e082094d3e410a00", + "0x92cc35b2065fca157c7bba54410f8bd85907a01c9f760aa0ddb7a82cb55811d24cb4dc6b725367a6a1c293b809a48ead", + "0xa12bbf22b117f00164a42515bc57cc9e6c43cc77fb737ee3d0c0cad94cb50cd3847d61cab469cf8ca76f7958bdcfc771", + "0x85985b00de533bde2a757eddf53be79ea39091d16af3fc92327bcd1cd59bf2bf4411a334da29ad775e8ffaf3cea7d7b8", + "0xaf9eb24185b0d330d0ea1d0b0fa78af0dcf42ced81cb0128f16cafdea687a9c5582bb6d7c5744117b271cd0b3303f0b5", + "0x8c8aaa1d85ed6327f85d579767c7a9158d209171b3efcb3e8a9d9e534c078e821b6aade255101d2c9ef6d67ba66f10be", + "0xa450518a03ffb40e1df89e0f88fd55b5b06f4872cdfb7ec55f40dc40d9424b3b289866336c195bdd54597d95569e0096", + "0x81e61cc69f93c435bd77f155e80626a9c764dd92b6c76af15c41346527948d8a6ca87d6351a0fe7987e2ee3aa66a9625", + "0xb615e0cebf4fdff4cb23a20c8389c370915ba26aa703b28efe4ab070b1603d1c5b6541684acf46b52a915f6aee447539", + "0xa7f51885c7a71885cc84ef734ecd107e8bf5f7a25131415f671d143cc1de92859e65001125323c7985799993af6c410d", + "0xabfbf7a46f32066989c32f774edcc68163f085ca81e94fe8c9fb32f8d451bbb2c20ac45cd8d97f9e618ab40186933b1a", + "0x8cf35a522b5cac1934004aa9dd236bc77198d43272888afa860cfc79b4b28dabf7a3c74098f84510897566fdd609aa45", + "0x86aa927df78f7a06a4985eb0a4f0b93529cef14f9fd2812d46abffbf25e618ead14d99c70e3c3bb2e17f3f7fabc9c264", + "0x860f1b4f4a398e9a8bb4739587cf96979cfbbe1687b7e91e5bd1198db726391b09b1a261bf12e96698818f60b5bd3537", + "0x8e7c4ee19ff115881051e8637dce1f5d6c65e865d0c757e8ce41b6d7bcd86c7070cce60649692bbf28c868c7e2e1e2f4", + "0xacf7ba01b0220419f09169ac8d16e5cc13dce08e88c90b8fdfaa33aab417f011a20b79a178d8a9f7211589d2e0affd7d", + "0xb404bde8e715aefbb9f20a353b911b79173ef3e2cf0aba98b5ae6190b90597d65043b0b4e014ad9ea6c77da2d213ea12", + "0x97e3615d1c77a402253bb55da2d1cdf82de316cefffe42b1022c94b4818d6dc4a313731db85321c537914bdf716a875c", + "0x940e950b96a4096a578c6874d747515936652b9b113a5f27f5a834a610867b05f9881e2679b0b289b8527baa0009b6dd", + "0x8de15a13ca236a3a285ce6e6826c502ae7365bbe468b6e8ac67b15b0bb49be0e996f1eec81ef69e4b7f54f8e4779a054", + "0xa12244777eacb08ecd42b5676b3a51153022ab97e9353ace0f47c6054c22de9ba60d2a60f59a36841c2a791cb1b7c288", + "0x94f7580203e39a2642ee2e7c969b9911f011d7f3a90c398e1302d26edb3df03df1d0c43baa1c6cf90dde95296d49e742", + "0x82ead33144aaecab965faf63af384565992f38fc1066e71e33d53f43ac93892e27fe78c4eaca1cccbc53364e26ff31e9", + "0xa0c129e9706d354249a7f8aa664ccd7ede89aa1445c5547410814b56d10dc086720953363ab1da8ff5f1ed5d8e575104", + "0x93b3057bf3f74edc95237781ae012cc4b1d3fd0455565ceaac7110290aa518ac32478ba4eb9851555fa87270fcc84f1f", + "0x949c2fd0b94f31f7cbf00c679bd3f6ec1a2f4056654708d39edf1a450b4e19a6e251d0bb24eb765087e698f61d3fca2c", + "0x99fd2e50e211ccb66b895eb2fc42f260f3ad5767f04c2fe238b81dae98aa6e3977443a51f4fe7b43f499caabe45699a5", + "0x84fe19626503218f327b5325bfd7c0c3d2614b47d34964aa0259d564e769c6c81502132cc1765b0b31fbe39852706927", + "0xb43287ec29d9010bec4284de58fed48dd1e129bac79f09d45153c9949131782f77b11b0c9f8ee06a39e5e9bbaa8e2c6d", + "0x908902f3ed45482df2f94415fc8e5a308057a40c8905d7cbbd58ec4848e19276577b7f7e69e5e684a8b981738e10f7ef", + "0x85cc7d9c1eae372b4f88758cd6e21604b4bc9f0794e1e74b6d9de96347f81944d01331385fae7a38e5f6096c1dc23465", + "0xaf60288c702082fc258b3dbd6952c6b75c1641a623905f491b1e72f49b9d39b33d150a336450abd3911a4c128166acdf", + "0xa7d8ac7e589558c4014369ab6f4c1f2196205b03e4278152ec0dbbd7ba54e803c3369a71d364a773aac8dbbd117e4a13", + "0x9833aed34e48c206e9328073597aee1123f5bec085339b4e6839a389a429bf3042798a31fac1464ce963204adface76b", + "0x84631a4f012bbb62133030224b57deb32dcf464cacc8ffde7775adbe68707263ab5527a1c75e597e03aa703ba658b889", + "0xa686a61f6467858a2a4c13e70ad81b1901290d3e51bbc0c6e366f9e652f575e91b11c75f640ccef8b0c6c1b05a43c9a0", + "0xb585f0ffd5144907703b41539bfad7f9f058f5985f63db911064ba6b07af8da2796b84b16db42b8d11135c3f846cd9e2", + "0xb525539516c7bb25f1d7e165f269dc8c9eedbba74df44887e178ab8fd798e2a31f39812ca922d6b64d91564f14012a64", + "0x91e480d7568fd2fae39c35b0a8d623e66a3160fee1dd4e9097255004938b11ac1cd3918dc6a1e5fbcb700c95a547e5e8", + "0x936ef55c69b842b6177de71fa48dc5442bf5132116b214302f8f242ca36a273a6bbfbfaf373777104dadbe8e7da5e970", + "0x8e950c0f6688abdff8a3b8bd77be6da6f2565c7b55711f5860ea62a3ab1d51aac31821c602bc11a45e33c69e7dde3ea4", + "0x90eed4595104a0527f8db1e028ff622ff70db4eae99cf47f6c2a0246ec7b103570a6a9a877e32e9647cc74969006743d", + "0xb756344f6c4ea05b792e416d9bd9ce9dd4bd904e7622761f28a85628506bfc9d88a25e5f04db62fad30a92fb1d8d8556", + "0xad79ba76534c1a02ac3e9b7308d390792984cd75b7e1d0e5e4ff123642d99d4ea1825643091aa8117336333c40d5bd94", + "0x832b08144887de0c0341d84f6945450af8d7a4eb32367d7703118186c1be525df9382ce61fed5f3b65a0bb3449185f7f", + "0xa322fb944e46d8e47994820890c94af423674716da810ea1da71e0a7733ad72c22114ca39a4b59c98ce4291a5684c154", + "0xb982851a65140dbea79bd3b5487e236feccee051deddcc17c2853032efca289ddb6eaf64be3dd85a73012fdbe9d2d4f3", + "0x8eed5e230e201830b44b9fadca4e156fe1a16bf840cf29da0f381ea0587b20c226de2465c67e6268973e776809af68e1", + "0x81c8f1c04490f36e41a53ee1b5185cb8adbb37c258fd6c3be8c56835bf574c37183a94d55b6554fca35d6e6dd9af0133", + "0x8c4928724107cc16d36f2976677eac0b852fc4c3c0bb2f9cd4d59cd24a113faf33b2faf405c3fcce25be51d41e42c2c4", + "0x8e4ba842636fdfc4d71f0983538ea5037d420acd26abd12efca48c252eea85544b2fa9fccdfec4e7c2a6359baffa112d", + "0xb4315b84700e26dec26f3488d308430fdff4809c10d4c24309627911cbb769ffaad0d1ecccd622dd02194eaf5ba59f91", + "0xab888308f757faef32648c1db01650dbc9aea248b09d06e6efcc996d395f48ec96f2d54a02de441d753fe8737862d991", + "0x805094cfd77e207d5c75f3cad99f41f763ec15443052cfd758c6a82ba422d831a1103a7f9b100da49c28198279c3d3dc", + "0xad857f33243e4a2cd2a773700def21fc7f94939d1a6d2c2125ecd58fc206ccafb07a2c02a1cfce19857d3654aca2c70c", + "0xa4d12d40149953daa70b89a329e918e9d93efb4e8004a9357fe76682dab9662c8507e16db83e849340f05cdb4933a373", + "0xa0dbac2ed4b5d03606524245e8a31080eb5bd3e9a0c51dad88c3b18e3e6bc5d64953a81c8e60425b80107ee6b62b1fb4", + "0x86da05355900f327164a78901f6e3db857531b33b1e855df1a67a9ba222c6b05fdb6b0ffbacaeb1ba5b45ff8979b6b68", + "0x932c9873aa3e226dd922b5a616c75153bd0390ce8f332a414b9c8cb6606c2501a37a2aa88097bc7d8e2c4261706eb38c", + "0xaccd9cdf07ccdd42033ce3b105e00bfd39e2304b1e3d66f8b1128645634452c20f759ec45adcef2fdf04408f62c4cc04", + "0xb75cfdfc1cb48918752eab17eb579820ee6e71e6667abdb64df834ffc8c1362fbbc23ca2c80dee248fe1fbb72d87dfc8", + "0x88b998c73b00638fde7d3dd650a08c5ab996dac6ac34251337fbff3fb5ae4a25dd20c1a16c987ad7ded19eca23cea891", + "0x8afef0956c942571a27f504553fb312cca9e50ce41b44e0466d0516c5abe4d8acf4594cdb03b1ccdbe3f2e6a9093b713", + "0x9042cd83c5ff261e9ebda26398caa16cac2cb840d19062fa8ae50e044c27104972948318f4c866dc4d578798272d3e49", + "0xad536719a64570a2cd1d72b6590ea1d02c8c49f259a7867be26c8191445165954bcfad50ea12688ace3fdfb0e98143bd", + "0x97c86328d63d297b6bc9718dc1ad5a05b908a750d1c455c700d84315589128ce4eea958aef2bcf0fcf4adbd8e3ce58d1", + "0x8e592cf0802e6a9541eeb654dc55055e11f3d757847285197132935ca35bbb1a9156829a39384dfa6f645ff89eb36738", + "0xac16c614998944f77590bf3913a010e13f2d3bbf6a172293baf5983506c1a2d89989fb72e598f5bba1ea10a691377c93", + "0xab8e6f5b46baa6632de3621497bcbdd584decb999fe7d8a3364843a1e0b76497600630b6a24dd30119d8bcbfca29f335", + "0xabe1d3af5279e60122d9cea8cc6581c819d7a0e20e3715da0f6da7e02d13a7653db643bd946e2fa9ba338eca81fbe140", + "0x8c33bd831ecfb18d1d0713e16beba768e9c42df62170c1f8a16764912be77f2ac5915623d1d25e8c462aa9c2f6669ca4", + "0x903692becae4a6409f7bdb127d9b11de57a5739fe24218dcbaa0092648d5332dfeef29a908ee9e43e5e0a51a4c3639bc", + "0x92591e90347ae286acd365eba32cd9ad8f20f4c9cad2dc579b195147ff290adf0d776bcb3d4b04a25d68a941fc0c781b", + "0xb64bbccf860299aec16e1f95c768a1f337c740bde612e6ba260e393edb8b04540127194761c42597abb9bcb771c576c3", + "0x9194f056ccfdfeb78a11c5347e2255d7a7ebd1251f9aebc0b58feb68d3e03a7dbbb74e3ef7309455853adfb4694bd01a", + "0xaa4f15f6d6a53ae65b7f6f91e8981d07a5919d2138679a561f7bb608dc4596e45ca06c9441d51fb678b2ad89ae7a17ae", + "0x90e3d18507beb30bde08c5001faf489a19ab545c177efb3f73fbf5605f9a0abcdc8bfbc44f832d6028e3e0a834bea98f", + "0x8f31dc0118c8c88a6e79e502d10e57652b7aba8409a5bf572ca63fed6b7cbad7f28bbc92ac2264f649792fc1d0715085", + "0xa307d1067ea4c56437b6f8913aa8fcbf4a24580fc1e3336e7f6518f0f3adb9c4733090e459a3f737414ec0048179c30a", + "0xb7cc41fdf89595cd81a821669be712cd75f3a6c7a18f95da7d7a73de4f51bb0b44771c1f7cd3cd949e6f711313308716", + "0xa9dc74e197fe60e8c0db06b18f8fe536381946edecdf31e9bd90e1ebfcad7f361544884e2fe83c23b5632912ec284faf", + "0x8b3e1e81326d611567e26ed29108f33ddb838c45bbd1355b3ae7e5d463612af64b63fff9fa8e6f2c14c8806021a5a080", + "0x92f6537bca12778866335acc1eb4c3dfc2c8e7e5cf03399743dcea46aa66cac92ac2963b0892784263ad0ebe26ffdbf6", + "0xb5cc0061f7a3e41513199c7dd91ac60d727366482a4c7328527f7bd4fc3509412f711bb722b4413b3736a219b843d15d", + "0xb3e9711d68d2c6f6e2cc27e385d5f603d9a1c9a96edeefa1ffdf390439954d19504d6aadc566b47e229ad4940ef020d2", + "0xa09d0d3f0e5dc73a4a0827b72710b514bbfce4a7fcd5141d498a5aad6c38071077f50d3f91af897d9ab677b7041dedda", + "0xb177fe260f3b86e9ac21f1bfbe2682ae5dd8c9aecebb84f37054bdab6e39094e611ce582210ceeddde66adf759dadb6d", + "0xb0ac6595eba9f5dc4b2fd21856267cfbcfb5b12aa34ec69ca32b80071c5b652e85c25a224d80443d503bf25fbbfe07e9", + "0x81f3c0e11b196bd4a2e8f07f8c037002566dc9037da81f3988add458a520c24dd1be3d43d851e28c0c6a85de4b57a542", + "0xa44308c95615f7fedb2d2127012924468c015df9f48359cc2e36ab4223870b0bfc1e9040baabefdf5266f93afaad896b", + "0x8493ec4c32d5a13b81039f1b436eb83f259945dc950e3c6c2ccf5087ec56dd2f60890ed4edf01728b6a54950e19b35c6", + "0xa1a439ec2a6a95bdac9aaa925ff337ba956c0d236ab5318354270e73ed6b73b4ae2d27b4c1686cf97b6526d04e65be81", + "0xb4659b7b53c55a4b2bbe210b53520b392f893500e18990d843b72d7379d45fb44dd1dd2184348d6fd853d6b9ecc6b7c6", + "0xafb2c68d75d00130b0e1b4f250001920213121791698ec04262db714cf7b1408d39f6cc10421f954845aad5b8250b77e", + "0xb22b843b40a97210f94043b552f348f66743055a3f274856a738e7d90a625b80e9bbb80cbbb450e1666eb56b8bd5c60f", + "0x800895ced82fe13d5fff65a93b0051c3df698bf1221b682accfdb63e3970f669ca37025750697f4e8ff2a3322ad57be4", + "0xb21f598c50d7b9f4a584d548f85e42055ef8e24991906d973749090261584c7f4f5e984b528926f7e75375dd84d51af8", + "0x849b1c68192d18274598dd6d0bf48fb5ee3b1ba25b331cff2d06f345bef3bed49760ca5690848cf33388f6a9a32cd646", + "0xaeb6fd9478b10ef456f6bbb1e6dd19b14475e65497772d12cfc097948383d3fbd191bf95f046b8bf1989954118e483d0", + "0xb1b5e0ea2835f7fc8b66e7731e392b43d16cbce04b52906b6751ab1b91978899db5fecbdabc23a19dabb253005468136", + "0x91b6b1284770cf6f7ef35bc0b872b76c7763ffcfa68f9c8cfabcb2f264a66d47598bb9293f6a40f4c3dd33c265f45176", + "0xb9ffed029846487c2cfb8a4bb61782bd8a878f3afdb73c377a0ebe63139fa070e3fcdc583eec3a53fdc5a421ff1fa877", + "0x998007249d041b0b40ff546131cfc86d0b3598dcedf9a8778a223f7ed68ba4833b97324cbb1de91292b8ff51beab44b3", + "0x8eb77ce9e0e406bf6f002870fb2fd1447646dd240df9bd485f8e0869298a1fc799d8a41b130c04370e9a9cc5c7540ca5", + "0x853db8157462c46f2af7e8f94f2ed1c9b9a7ba2896b4973296898ff3d523d6e29e0b63a5d26cecd5e490b33c87a4cecf", + "0xb1436b6f3278768f0979ee852944258f2599977d255bea6fc912ba17c5dff5bdc850cf3e1fc52be9d6d188e868670f4f", + "0xa76acbc5832019b3b35667ab027feff49f01199a80016620f5c463dfcbfb51bf276ed17b7b683158ba450660cc7973eb", + "0x94540cdb051faf3ae8b8c52662868c2dab66bd02505c4f5f8eb4d6b2e2e5fd9a610890c5dcf8fd887eee796d2b5753a8", + "0xaa35099666bceccf4eb3b65b13bba88e30a8be93693ab6761d8e5523343e8d6dd42d977e66499352fe4e9e9784a1dd0d", + "0x894471aad17be54319083c4b5e40adcfacf7c36c4aab0b671030b7ef321c53590a25eccd836efd20f32a93185fd315bb", + "0x8f52a9f705bb0dea958fcfbd52e2b6c08ad0f89a07a6b2942c1b4c37eead0d97a38a9e9aeb08d5d59b7fa2a9347f738b", + "0x9031c16b4f936c9cab55585dc5064739f696c3347ee2c0792320c9f749e760d120e396e8485ffc79d81c9f3337ad3d1c", + "0x82090a0d0d9b05459ec1c328ecd4707c333b784e3aaa0ef0072cee1eac83f9a653a75d83b9f63512a8c41200494826b4", + "0x92c3a9553001f9ea4d67236b8ad1a33275378202cc1babc03f313895458f4b2549bfbbbdd37bfb8fbff0decb6b9f820a", + "0x88651868f4da37338a22bc553388df5dd1dd0cb78c4d7d07c637d8f6faef4bed72476fdcd4304d5bedf3514011135f08", + "0x83fa0141bfebd88063f1d787719721b4c6b19ecf565b866de9d7d5d1a890e0e3d859b364bb65f8f8e688654456a40263", + "0x90a7fab753e5d56dfc0e53a6b4e6ab14508220f3a62b3f3f30570c4c9ad225e74122635826c92e8e3227ec45e551432a", + "0x8fa375b0345bf6e5e062d108f9feaec91029345ecac67ccf1264eac77b8654cbfdda1f10579f481889c0e210254eadde", + "0xb83f06116da9daebdb013b26724523f077debaf6bc618b48a7a68858a98d275f7899c4ec73a0a827219b9248dd81c8c9", + "0x8be1cada55e0c5ebb4fd460b2d209ae5326285a20c8bdd54ed9d1a87302f4063c8730bfda52d9d40e0d6fe43a0628465", + "0xa68ad6f813743ec13a811f2ef3982c82d9d9ac1f7733936aa1e122f8dc7f4a305cc221579ab8fc170c3f123a1576f9ab", + "0x8878f1128214fdbbb8a0edd85223741e021508ab6d36c50d38680f2951ee713ea056ed03f62b9461897963d50ceefe0b", + "0xacc0d43d1b0260528b7425b260a5dea445b232b37240759fc65fe26f7c9d8e51569c5722bc33e94de6492f4ba1783504", + "0xad80b1dd717b076910ee5ceabcb762e75e4d094dc83b93b65c16de1f75bc712cef223c05d5579c1561829406c07a97d9", + "0xa6fc9803f9c09d95fc326cc284f42ea5566255eb215dba8a9afb0be155ea11bcc55938b2d16f01cd2f2eda218c715efb", + "0x83ad733dbdfbaae8095a403dbf09130513f4ed4f08dcf8dd76ce83d1ea72999b7eea3a7b731da0d2bc80a83c6ee0e3e0", + "0x8748912fbd08cb34a85416b0937d9c4327e9eed20d6e30aeb024a7253f14f1e0d774f3326e54738d71aae080e28da0fe", + "0x8997e78d8acf23051428af67183ae9b2c4aa42b503745ffe33df35a35103c589987e1473ab14dcd28ee78ebcb10d8e95", + "0xa2f340502a7eb3c4a36412e6f028321372c4fa18a4743945607424e932af1271fa3e6598a162c872072529576eba6283", + "0x868ccf19b5044ab93b45c9ed3ae34fcb504fe1453d6c4a1d12c325032cf01eb90356de82080ed897e97dba13cae33a02", + "0xac8867005fe4354d67aa37b866a7e581d2f94f7bd0b9f4efb5c2d1370ec13147a60692051b02fd00ae60b512bce9b1ff", + "0x8fd01886b046819c83c12bb779e432b25ba13713f9227be702074ec3abb2bba6be37220a0a26a4bd4171b99b14e32bc4", + "0xa128981ed199f92b5959975c150a93a62fec50b61c80a3fa0634d90fc8058f76f5cbee77aae6889af12d296b30e613cd", + "0x81fe618552ff7a36c9235c6d4066cf2f930b5b38de4089e18166e4a06ca5723eadd1976d25e34b74b3ce942300b23e5b", + "0xab1223ea049e6e0fbf9b611de7fd7c15e5e9637cbd73aa0e36aea08a7503ba6804f2aa807186fdc9aa7f4f9195f72e24", + "0xb97285286981b2665f898abc13f3243b63005bef8db4cab3f658bf6167036b61af400f08db0fc3c640a9c623b760690d", + "0xae3ddff7c1f0fbb6a13dbbc667a61e863c2c7c51c2051e33cd61620142e7e30a7e0c4c1f8fbb512aa3a8640267c6ac26", + "0x99c2a89d5bef236060e51c4f952664094c20fbfca647e5d24a55c1fb8df2f3df58244fbbf3635db07b1c29ee3234fa6f", + "0xa5010764d4b9cd3b410638334d1f70c5f4843f45b4f4a9316aaea5fbb2c510a97449dd7a07b49f47334a69d37d9955d3", + "0x86706d011dcdc9e9d165d01fea1df68dd74bedaf15a39f92893c030cafe96f4498c4c1fec2d2136354341b3f440a1462", + "0x88fd57eb62bd7dc35722f3a0576c2138403a2f663a2603482e8974a895cf56ddbb02657dc6b89eb2cf5c1f9d1aff6426", + "0xb0dfd4c68e3acb6bb8a776adaa421fc5e268ed4d5964bb90a727091e5113b55b3f9c6d33cedb3ee47ff7acc5df8b1749", + "0x93b92bc942e1a636fc5c2dc1840de5faf158a113d640d5a475b48e2c56ccccaf9db0e37e90ce74c4b3f5c9ac3b2eb523", + "0xb29a16fa1ea95cbfc1873c435ad40dc8495ba6341801b72bd95d908147dcffb1b4bb426dd635f3af4c88984f56594dd8", + "0xb8f367105e1a2d554ac30200c66aeb579d3d30a8953d20fb6ebba2d876ec39c52ea5d654f1bb89b8ddf3d9d651f31cdf", + "0xb5fbc228c983d08adf8612eba5b3db3acff604439226f86aa133b02cce4ffde2f977c8dbb8b446b4375673f71634c89d", + "0xa399bea37d3056e0559f6644faa0af93063b4b545d504d7e228d3dbbc294af83d3c4cf37fe026b63899b4e7d50fd08f5", + "0x928ef411a36414b24aea26fdbed4bdb1bb6bdc2d967e2553ce54c7c4e077e76869cea590257645c9129dd55ce025295c", + "0x9684a4adeed416a9ce82ad79b55c4a3adcfbd43950bc442ed8a340381caedb70f4baaaf821e3a152f483f965d8f56162", + "0x92558a37f214d6f4cb6d72cd2f4ad24dff9d17611b9e4a41ee5c741a5d1ca9e4053b0584533ef4da206110b5dc3e2a35", + "0x973bf0724d1785cc5e85d2a8ee8c354ad4cf557217ced0b7940f6f064024c20b2bfc5b144c820b5083da4bf70690de4d", + "0xadaf1389dfa528210ca9c2657c5ff10d51f7e3b18e93a59c37211be0506c3576cb2c04ec80cd0f82605e53c5a3556620", + "0x85b58b223b09fda6f3ab674d75e780c49eb2167837243df049281e8f4fed653811138b398db9cdfe7405fdb8485602fe", + "0x849504d3db408d80745a07e850b0a804607b91a59922a5d3bc40da2748c029c029419cda38d2a4485cc0824c6b2504f0", + "0xa3f4afcb353bc2582a02be758ebf0cd18752410ca2e64231176bfa23828423e0a450a65f241a9ed8eab36cae8d9c567b", + "0xae362786cdf121206537af9590d330abbc6dc328b53cdd145dbed0e5df1364c816aae757c4c81f9d619e3698dd32bcdf", + "0x9024cfa5b0101eb02ab97866d5a3832944e5aa6888484cfba3d856576b920787b364fba5956bd7c68a305afedc958201", + "0x8a116df09fed923acefb2aecf38a4fbc4b973ee964d67f03791d70bee6356af43ffca117d4e9463ffaf0e0d5d5e5a69f", + "0x9163016175c73f1bbc912ddfe03bd4e1db19c64951c8909ee6befe71a1249d838e0db49f03670bb4c5c9b2ab0fb4fef3", + "0x8f6357318d8d16e7240a02b05ce5a4976b6079d49daa258789c6dbf4a47950ebe9de6411780fab06c7c1f35651433380", + "0x8e63cbae8be7341892dbedee3111adf0307c4ee9e375181aa53478f5ba9cdce164d6ae890e5f480119a3a51c6e989165", + "0xa9782f30674a4874d91bfba7eda63aeb5dbe66b040c768d6a925d8ee135f0655ea56276b105239cc0668fc91ddb68cd1", + "0x8d9d94b61ab84ec08665cbe0244ea41756785df019e453ef078c19380bd44c39d2958e8465c72eacf41eed5696037805", + "0xb1470e6f5d2e314474937cb5a3bc30c8bf5fc3f79014945f6ee895fe20028ffc272f9d3a7320aac93e36c96d8a5454e3", + "0xa444911bbafc71179766594f3606b6eaff041826607fd3192f62dec05cd0f01b78598609a530f6930e8440db66f76713", + "0xa9823d44e2638fca7bcc8796cc91c3eb17f46ad6db9f7f6510e093727614aa3a4f9b2c4011ef91dc1c2d224d08d8d05b", + "0xab86020972c359ab98294212558b4b14862040139876c67fc494184b5c9bcea1dbe32fe0c8dd9e60be9daa304acd599a", + "0xb7e5cb685bbdcfdb1e48259a5d68d047846c8a35c5b3f90172fb183d1df40d22eaf0edaca2761a07c29c577000ccfed0", + "0x8c88319dae4b28989817e79e6667fd891181e8d2ed91b9c6b614985bca14b12982462ec58b17be0463c24bbb79dd62a1", + "0x8c1c6867e7107fb2178157c991b9c8b0f90c8d57a51220bf3650438ccabccf62da4db8a9916491e730ff3d0c106496e3", + "0xa00a79bd58da6528b9af033087260f9f3d00519eafb4746b355204ee994e89481591b508eaa5402821083e250d38467b", + "0x8785abd7c37690f6aa870ee5c799eef72e398a7898b6767f698515be277b9c2fc1af12ea89b0620a848221343a3b5ec3", + "0x8aadae68543db65cef71d0e230a09508d72061398ef2fabec0f856aacff2125b79c70e620744aaf331faf3dfc8afb9bc", + "0x8ff0cd437fcad9630b8a2333176a55e178db4142ec841581590594d74d5b53baeac5fb903fdf7bcf83e245b95b58285e", + "0xaf274e8fad6b190be4e5dc92d2705ba6ac0d7e1ea29e958a5cdd4cb764de46a56d9eef62c999a16e7c50a50b2d9fe3a8", + "0x865e6ec7d1aa848786d6a7a4e87a24d442311f0810b01ef5a74928ab59fdfd651e48880b49680047e5b0df6b3c7c2ecc", + "0x800706baaeb35bf3bc33bdea9a8b5cb00d82df407b3b7e1b781a9359cf44fb410ed311591080181b768aae223d9246aa", + "0xa9496389d0780b309c6998374ae159f58a8d0fe9a1c24c36cebcb45b27d818e653b51a8ee1f01e30a9b2c46a548126ef", + "0xb5fccf4fc3186661939fbee2e89c2aa0e3a6ad4907bcc98c7750520540c4c183b1bbfcdf47f2f1c5e75c3a30cdf30c75", + "0xa90028e39081b736e628c2230cc1338f9210ed01309a40fdf08d39c10cced2cdf71271013bea6dba3a0444fe47963106", + "0xa0815cbb325a8fecf2e1bcc5046644be32d43a8001bd5d8cf0022e4572cd0d481b3e717002f7ab21e16da5f5d16886d6", + "0xb2024787fcda52abc4138150f15e81f4a5be442929b1651ddccbfd558029912be4d61c3c9b467605fff640edf7392494", + "0xab5aa60032304a584cc9245a33f528eae7157808dedd1ad83ebae00aadc25dbe1cd5917eb8b6b2c800df15e67bdd4c4d", + "0x866643847ef512c5119f2f6e4e3b8d3f4abb885f530bb16fcef0edb698a5b0768905e51536283925b6795a5e68b60ddc", + "0x806aa99c9a46ee11cc3ebf0db2344b7515db8c45b09a46a85f8b2082940a6f7263f3c9b12214116c88310e706f8e973a", + "0xa6eada8b9ff3cd010f3174f3d894eb8bb19efdbff4c6d88976514a5b9968b0f1827d8ac4fe510fb0ba92b64583734a1e", + "0x98480db817c3abbc8b7baedf9bf5674ec4afcfd0cd0fd670363510a426dad1bcf1b1cb3bf0f1860e54530deb99460291", + "0x81ab480187af4a3dfbc87be29eca39b342a7e8e1d1df3fc61985e0e43d8d116b8eac2f1021bde4ae4e5e3606c1b67a21", + "0x8a37df12dc997bf9b800f8fd581a614a1d5e32b843f067d63d1ca7fde2e229d24413d3a8308ec1e8389bf88154adb517", + "0xb045a55ca0bb505bd5e8fcc4cfdd5e9af1a7d5fe7a797c7ede3f0b09712b37f493d3fcf6ef0e759d7e0157db1f583c95", + "0xad502e53a50691238323642e1d8b519b3c2c2f0fd6a0dd29de231f453be730cf1adc672887d97df42af0a300f7631087", + "0x80597648f10c6d8fcd7421caf4e7f126179633078a1724817d2adc41b783723f302eabc947a7ba7767166dacf4ce8fa1", + "0xaefb56427966c81081999dffbe89f8a0c402041929cd4e83d6612866cfbb97744f4ab802578349fbecc641fa9955e81b", + "0xa340e493fb3fb604eab864d4b18a6e40ba657003f1f88787e88e48b995da3d0ab4926ce438bdc8d100a41912a47dace0", + "0xa6d777bfc0895eac541a092e14499ff8bf7156689d916a678b50a1460583b38e68158984bea113a0a8e970d8a6799a85", + "0x90ce469410f0e8cfff40472817eb445770833cdcf2895a69bc32bcf959854d41712599ceb2b0422008d7300b05e62e02", + "0x815c51be91d8516d5adc2fd61b6600957ed07cf5fdc809aa652b059bea8ed179638a19077a3f040334032f0e7900ac8b", + "0xb3ec6c0c3c007c49c6b7f7fc2ffd3d3a41cdff5ad3ac40831f53bfc0c799ffeed5f440a27acc5f64432e847cc17dd82e", + "0x823637abeab5fb19e4810b045254558d98828126e9a2d5895a34b9e4b4f49ab0a5b3ee2422f1f378995ea05df5516057", + "0xac05412bcf46c254f6548d8107a63928bba19ab6889de5d331eb68cf4d8ce206055b83af4cb7c6c23b50188391e93f84", + "0x88514163c587068178302bc56e9a8b3ad2fa62afd405db92f2478bb730101358c99c0fe40020eeed818c4e251007de9c", + "0xb1e657d0f7772795b3f5a84317b889e8ded7a08ea5beb2ab437bebf56bcb508ae7215742819ed1e4ae3969995fe3b35d", + "0xa727d4f03027fe858656ca5c51240a65924915bd8bd7ffa3cfc8314a03594738234df717e78bb55a7add61a0a4501836", + "0xb601682830fc4d48ece2bdc9f1a1d5b9a2879c40c46135f00c2c3ae1187c821412f0f0cfbc83d4e144ddd7b702ca8e78", + "0xb5cfea436aa1f29c4446979272a8637cb277f282825674ddb3acac2c280662fb119e6b2bdd52c4b8dbf2c39b1d2070d6", + "0x85c211645ff746669f60aa314093703b9045966604c6aa75aae28422621b256c0c2be835b87e87a00d3f144e8ab7b5f0", + "0x867628d25bab4cb85d448fd50fdd117be1decdd57292e194a8baa0655978fae551912851660a1d5b9de7a2afbb88ef5c", + "0xa4e79c55d1b13c959ff93ddcf1747722c6312a7941a3b49f79006b3165334bab369e5469f1bddebadb12bfaff53806d5", + "0xac61f0973e84546487c5da7991209526c380e3731925b93228d93a93bce1283a3e0807152354f5fe7f3ea44fc447f8fe", + "0xa1aa676735a73a671a4e10de2078fd2725660052aa344ca2eb4d56ee0fd04552fe9873ee14a85b09c55708443182183a", + "0x8e2f13269f0a264ef2b772d24425bef5b9aa7ea5bbfbefbcc5fd2a5efd4927641c3d2374d0548439a9f6302d7e4ba149", + "0xb0aacdaf27548d4f9de6e1ec3ad80e196761e3fb07c440909524a83880d78c93465aea13040e99de0e60340e5a5503cd", + "0xa41b25ae64f66de4726013538411d0ac10fdb974420352f2adb6ce2dcad7b762fd7982c8062a9bac85cdfcc4b577fd18", + "0xb32d87d5d551f93a16ec983fd4ef9c0efcdae4f5e242ce558e77bcde8e472a0df666875af0aeec1a7c10daebebab76ea", + "0xb8515795775856e25899e487bf4e5c2b49e04b7fbe40cb3b5c25378bcccde11971da280e8b7ba44d72b8436e2066e20f", + "0x91769a608c9a32f39ca9d14d5451e10071de2fd6b0baec9a541c8fad22da75ed4946e7f8b081f79cc2a67bd2452066a9", + "0x87b1e6dbca2b9dbc8ce67fd2f54ffe96dfcce9609210a674a4cb47dd71a8d95a5a24191d87ba4effa4a84d7db51f9ba0", + "0xa95accf3dbcbf3798bab280cabe46e3e3688c5db29944dbe8f9bd8559d70352b0cfac023852adc67c73ce203cbb00a81", + "0xa835f8ce7a8aa772c3d7cfe35971c33fc36aa3333b8fae5225787533a1e4839a36c84c0949410bb6aace6d4085588b1e", + "0x8ef7faa2cf93889e7a291713ab39b3a20875576a34a8072a133fed01046f8093ace6b858463e1e8a7f923d57e4e1bc38", + "0x969ecd85643a16d937f148e15fb56c9550aefd68a638425de5058333e8c0f94b1df338eaab1bd683190bfde68460622b", + "0x8982f4c76b782b9b47a9c5aeb135278e5c991b1558e47b79328c4fae4b30b2b20c01204ff1afb62b7797879d9dee48e2", + "0xb5098b7ba813178ced68f873c8c223e23a3283d9f1a061c95b68f37310bca4b2934a3a725fff1de1341c79bb3ba6007e", + "0x97b160787009f7b9649ed63db9387d48a669e17b2aba8656792eb4f5685bb8e6386f275476b4dfbb1b4cb0c2a69bc752", + "0x88b69369c71daad6b84fa51a0f64a6962d8c77e555b13c035ad6fa1038e7190af455b1bd61ae328b65d6a14cf3d5f0d5", + "0xaf88b87801361f0de26bd2533554ee6f4d8067e3122b54161c313c52cc9eafea00661c5c43e2d533485d1f26da4e5510", + "0x98ab18e3bbcb23ac1e34439849e56009bb765ab2f2558ebfd0a57cbe742169f114bceb930533fb911b22cb5a8fe172bc", + "0x9027507f1725d81e5ac0f0854c89ab627df3020fe928cb8745f887bf3310086c58fca1119fd5cd18a7d3561c042d58de", + "0xa676583f8a26e6f8991a0791916ce785b596ce372812f5eb7b4243ba9367ea95c797170fdac5b0c5e6b7f6519cc2b026", + "0xb91b0ab32638aef3365035a41c6068e36d2303bfee8640565e16c9a56c21703270fd45946ce663238a72c053eb3f2230", + "0xaaf4cd1ac0a30906dcd2b66b37848c6cc443da511e0b0367fd792887fdaf1500551590440e61d837dbee9d24c9801108", + "0xa06f20a02d3cd76029baad5a12592f181738378a83a95e90470fa7cc82a5ae9d2ed824a20eeb1e96e6edc0619f298688", + "0xa465d379c3481b294efc3f2f940b651c45579607cf72d143b99705eae42103a0279eb3595966453130e18935265e35d6", + "0x892a8af7816a806295278027a956663ea1297118ede0f2a7e670483b81fb14dccacc7a652e12f160e531d806ca5f2861", + "0xb480917c0e8b6e00de11b4416a20af6c48a343450a32ee43224559d30e1fecdece52cc699493e1754c0571b84f6c02c2", + "0xb3182da84c81e5a52e22cebed985b0efc3056350ec59e8646e7fd984cdb32e6ac14e76609d0ffaca204a7a3c20e9f95d", + "0xa04ea6392f3b5a176fa797ddec3214946962b84a8f729ffbd01ca65767ff6237da8147fc9dc7dd88662ad0faefdb538c", + "0x95c0d10a9ba2b0eb1fd7aa60c743b6cf333bb7f3d7adedce055d6cd35b755d326bf9102afabb1634f209d8dacfd47f1a", + "0xa1a583d28b07601541fa666767f4f45c954431f8f3cc3f96380364c5044ff9f64114160e5002fb2bbc20812b8cbd36cb", + "0xa1a0708af5034545e8fcc771f41e14dff421eed08b4606f6d051f2d7799efd00d3a59a1b9a811fa4eddf5682e63102ea", + "0xab27c7f54096483dd85c866cfb347166abe179dc5ffaca0c29cf3bfe5166864c7fa5f954c919b3ba00bdbab38e03407d", + "0xac8c82271c8ca71125b380ed6c61b326c1cfe5664ccd7f52820e11f2bea334b6f60b1cf1d31599ed94d8218aa6fbf546", + "0xa015ea84237d6aa2adb677ce1ff8a137ef48b460afaca20ae826a53d7e731320ebdd9ee836de7d812178bec010dd6799", + "0x925418cda78a56c5b15d0f2dc66f720bda2885f15ffafb02ce9c9eed7167e68c04ad6ae5aa09c8c1c2f387aa39ad6d1b", + "0x87c00bba80a965b3742deacafb269ca94ead4eb57fdb3ed28e776b1d0989e1b1dba289019cfb1a0f849e58668a4f1552", + "0x948d492db131ca194f4e6f9ae1ea6ebc46ebbed5d11f1f305d3d90d6b4995b1218b9606d114f48282a15661a8a8051ca", + "0x8179617d64306417d6865add8b7be8452f1759721f97d737ef8a3c90da6551034049af781b6686b2ea99f87d376bce64", + "0x918e3da425b7c41e195ed7b726fa26b15a64299fe12a3c22f51a2a257e847611ac6cfcc99294317523fc491e1cbe60c4", + "0xa339682a37844d15ca37f753599d0a71eedfbbf7b241f231dd93e5d349c6f7130e0d0b97e6abd2d894f8b701da37cb11", + "0x8fc284f37bee79067f473bc8b6de4258930a21c28ac54aaf00b36f5ac28230474250f3aa6a703b6057f7fb79a203c2c1", + "0xa2c474e3a52a48cd1928e755f610fefa52d557eb67974d02287dbb935c4b9aab7227a325424fed65f8f6d556d8a46812", + "0x99b88390fa856aa1b8e615a53f19c83e083f9b50705d8a15922e7c3e8216f808a4cc80744ca12506b1661d31d8d962e4", + "0xa1cbd03e4d4f58fc4d48fa165d824b77838c224765f35d976d3107d44a6cf41e13f661f0e86f87589292721f4de703fb", + "0xb3a5dde8a40e55d8d5532beaa5f734ee8e91eafad3696df92399ae10793a8a10319b6dc53495edcc9b5cfd50a389a086", + "0x996e25e1df5c2203647b9a1744bd1b1811857f742aee0801508457a3575666fcc8fc0c047c2b4341d4b507008cd674c2", + "0x93e0a66039e74e324ee6c38809b3608507c492ef752202fff0b2c0e1261ca28f1790b3af4fdb236f0ed7e963e05c1ec0", + "0xb6084e5818d2d860ac1606d3858329fbad4708f79d51a6f072dc370a21fdb1e1b207b74bc265a8547658bfb6a9569bb3", + "0xa5336126a99c0ecfc890584b2a167922a26cae652dfc96a96ab2faf0bf9842f166b39ceaf396cd3d300d0ebb2e6e0ebf", + "0xb8b6f13ce9201decaba76d4eca9b9fa2e7445f9bc7dc9f82c262f49b15a40d45d5335819b71ff2ee40465da47d015c47", + "0xb45df257b40c68b7916b768092e91c72b37d3ed2a44b09bf23102a4f33348849026cb3f9fbb484adfea149e2d2a180ff", + "0xa50d38ee017e28021229c4bb7d83dd9cdad27ab3aa38980b2423b96aa3f7dc618e3b23895b0e1379ca20299ff1919bbf", + "0x97542cf600d34e4fdc07d074e8054e950708284ed99c96c7f15496937242365c66e323b0e09c49c9c38113096640a1b6", + "0x822d198629697dcd663be9c95ff1b39419eae2463fa7e6d996b2c009d746bedc8333be241850153d16c5276749c10b20", + "0x9217bc14974766ebdfbf6b434dd84b32b04658c8d8d3c31b5ff04199795d1cfad583782fd0c7438df865b81b2f116f9c", + "0x93477879fa28a89471a2c65ef6e253f30911da44260833dd51030b7a2130a923770ebd60b9120f551ab373f7d9ed80aa", + "0x87d89ff7373f795a3a798f03e58a0f0f0e7deab8db2802863fab84a7be64ae4dcf82ece18c4ddbefccd356262c2e8176", + "0xa3ba26bd31d3cc53ceeced422eb9a63c0383cde9476b5f1902b7fe2b19e0bbf420a2172ac5c8c24f1f5c466eecc615d4", + "0xa0fe061c76c90d84bd4353e52e1ef4b0561919769dbabe1679b08ef6c98dcfb6258f122bb440993d976c0ab38854386b", + "0xb3070aa470185cb574b3af6c94b4069068b89bb9f7ea7db0a668df0b5e6aabdfe784581f13f0cf35cd4c67726f139a8c", + "0x9365e4cdf25e116cbc4a55de89d609bba0eaf0df2a078e624765509f8f5a862e5da41b81883df086a0e5005ce1576223", + "0xa9036081945e3072fa3b5f022df698a8f78e62ab1e9559c88f9c54e00bc091a547467d5e2c7cbf6bc7396acb96dd2c46", + "0x8309890959fcc2a4b3d7232f9062ee51ece20c7e631a00ec151d6b4d5dfccf14c805ce5f9aa569d74fb13ae25f9a6bbe", + "0xb1dc43f07303634157f78e213c2fae99435661cc56a24be536ccbd345ef666798b3ac53c438209b47eb62b91d6fea90a", + "0x84eb451e0a74ef14a2c2266ff01bd33d9a91163c71f89d0a9c0b8edfcfe918fc549565509cd96eed5720a438ff55f7f2", + "0x9863b85a10db32c4317b19cc9245492b9389b318cf128d9bbc7ec80a694fcbbd3c0d3189a8cad00cc9290e67e5b361ee", + "0x8a150ee474ebe48bdfcac1b29e46ac90dcded8abbe4807a165214e66f780f424be367df5ef1e94b09acf4a00cd2e614d", + "0xa6677a373130b83e30849af12475e192f817ba4f3226529a9cca8baaefb8811db376e4a044b42bf1481268c249b1a66e", + "0xb969cbf444c1297aa50d1dfa0894de4565161cb1fc59ba03af9655c5bf94775006fe8659d3445b546538a22a43be6b93", + "0x8383167e5275e0707e391645dc9dea9e8a19640ecfa23387f7f6fcaddff5cde0b4090dfad7af3c36f8d5c7705568e8d8", + "0xa353ddbc6b6837773e49bb1e33a3e00ca2fb5f7e1dba3a004b0de75f94a4e90860d082a455968851ef050ae5904452e0", + "0xadeccf320d7d2831b495479b4db4aa0e25c5f3574f65a978c112e9981b2663f59de4c2fa88974fdcabb2eedb7adab452", + "0xafa0eacc9fdbe27fb5e640ecad7ecc785df0daf00fc1325af716af61786719dd7f2d9e085a71d8dc059e54fd68a41f24", + "0xa5b803a5bbe0ca77c8b95e1e7bacfd22feae9f053270a191b4fd9bca850ef21a2d4bd9bcd50ecfb971bb458ff2354840", + "0xb023c9c95613d9692a301ef33176b655ba11769a364b787f02b42ceb72338642655ea7a3a55a3eec6e1e3b652c3a179e", + "0x8fa616aa7196fc2402f23a19e54620d4cf4cf48e1adfb7ea1f3711c69705481ddcc4c97236d47a92e974984d124589e5", + "0xa49e11e30cb81cb7617935e8a30110b8d241b67df2d603e5acc66af53702cf1e9c3ef4a9b777be49a9f0f576c65dcc30", + "0x8df70b0f19381752fe327c81cce15192389e695586050f26344f56e451df2be0b1cdf7ec0cba7ce5b911dcff2b9325ae", + "0x8fbbc21a59d5f5a14ff455ca78a9a393cab91deb61cf1c25117db2714d752e0054ed3e7e13dd36ad423815344140f443", + "0xa9a03285488668ab97836a713c6e608986c571d6a6c21e1adbd99ae4009b3dde43721a705d751f1bd4ebf1ea7511dfed", + "0xb2f32b8e19e296e8402251df67bae6066aeefd89047586d887ffa2eacdf38e83d4f9dc32e553799024c7a41818945755", + "0x942cf596b2278ad478be5c0ab6a2ad0ceafe110263cc93d15b9a3f420932104e462cf37586c374f10b1040cb83b862e0", + "0xaaa077a55f501c875ceae0a27ef2b180be9de660ef3d6b2132eb17256771ce609d9bc8aaf687f2b56ae46af34ad12b30", + "0x90ac74885be1448101cf3b957d4486e379673328a006ea42715c39916e9334ea77117ff4a60d858e2ccce9694547a14f", + "0x9256cdfc2339e89db56fd04bd9b0611be0eefc5ee30711bcece4aadf2efcc5a6dcc0cfd5f733e0e307e3a58055dff612", + "0xa4c7384e208a0863f4c056248f595473dcde70f019ddaede45b8caf0752575c241bac6e436439f380ac88eee23a858e9", + "0xa3aa67391781e0736dddc389f86b430b2fc293b7bd56bfd5a8ec01d1dd52ed940593c3ad4ce25905061936da062b0af6", + "0x80299275ec322fbb66cc7dce4482ddd846534e92121186b6906c9a5d5834346b7de75909b22b98d73120caec964e7012", + "0xaa3a6cd88e5f98a12738b6688f54478815e26778357bcc2bc9f2648db408d6076ef73cced92a0a6b8b486453c9379f18", + "0xb07c444681dc87b08a7d7c86708b82e82f8f2dbd4001986027b82cfbed17b9043e1104ade612e8e7993a00a4f8128c93", + "0xaf40e01b68d908ac2a55dca9b07bb46378c969839c6c822d298a01bc91540ea7a0c07720a098be9a3cfe9c27918e80e8", + "0xabd8947c3bbc3883c80d8c873f8e2dc9b878cbbb4fc4a753a68f5027de6d8c26aa8fbbafeb85519ac94e2db660f31f26", + "0xa234f9d1a8f0cb5d017ccca30b591c95ec416c1cb906bd3e71b13627f27960f61f41ed603ffbcf043fd79974ec3169a8", + "0x835aaf52a6af2bc7da4cf1586c1a27c72ad9de03c88922ad172dce7550d70f6f3efcc3820d38cd56ae3f7fc2f901f7a0", + "0xae75db982a45ad01f4aa7bc50d642ff188219652bb8d521d13a9877049425d57852f3c9e4d340ffec12a4d0c639e7062", + "0xb88884aa9187c33dc784a96832c86a44d24e9ffe6315544d47fc25428f11337b9ffd56eb0a03ad709d1bf86175059096", + "0x8492ca5afcc6c0187b06453f01ed45fd57eb56facbeea30c93686b9e1dab8eaabd89e0ccb24b5f35d3d19cd7a58b5338", + "0x9350623b6e1592b7ea31b1349724114512c3cce1e5459cd5bddd3d0a9b2accc64ab2bf67a71382d81190c3ab7466ba08", + "0x98e8bf9bed6ae33b7c7e0e49fc43de135bffdba12b5dcb9ff38cb2d2a5368bb570fe7ee8e7fbe68220084d1d3505d5be", + "0xab56144393f55f4c6f80c67e0ab68f445568d68b5aa0118c0c666664a43ba6307ee6508ba0bb5eb17664817bc9749af0", + "0x827d5717a41b8592cfd1b796a30d6b2c3ca2cdc92455f9f4294b051c4c97b7ad6373f692ddafda67884102e6c2a16113", + "0x8445ce2bb81598067edaa2a9e356eda42fb6dc5dd936ccf3d1ff847139e6020310d43d0fec1fe70296e8f9e41a40eb20", + "0x9405178d965ee51e8d76d29101933837a85710961bb61f743d563ef17263f3c2e161d57e133afac209cdb5c46b105e31", + "0xb209f9ed324c0daa68f79800c0a1338bbaf6d37b539871cb7570f2c235caca238a2c4407961fcb7471a103545495ef2c", + "0x92ae6437af6bbd97e729b82f5b0d8fb081ca822f340e20fae1875bdc65694cd9b8c037a5a1d49aa9cae3d33f5bad414e", + "0x9445bdb666eae03449a38e00851629e29a7415c8274e93343dc0020f439a5df0009cd3c4f5b9ce5c0f79aefa53ceac99", + "0x93fdab5f9f792eada28f75e9ac6042a2c7f3142ba416bfdb1f90aa8461dbe4af524eee6db4f421cb70c7bc204684d043", + "0xa7f4dc949af4c3163953320898104a2b17161f7be5a5615da684f881633174fb0b712d0b7584b76302e811f3fac3c12f", + "0xa8ac84da817b3066ba9789bf2a566ccf84ab0a374210b8a215a9dcf493656a3fa0ecf07c4178920245fee0e46de7c3ec", + "0x8e6a0ae1273acda3aa50d07d293d580414110a63bc3fb6330bb2ee6f824aff0d8f42b7375a1a5ba85c05bfbe9da88cb5", + "0xa5dea98852bd6f51a84fa06e331ea73a08d9d220cda437f694ad9ad02cf10657882242e20bdf21acbbaa545047da4ce5", + "0xb13f410bf4cfce0827a5dfd1d6b5d8eabc60203b26f4c88238b8000f5b3aaf03242cdeadc2973b33109751da367069e1", + "0xa334315a9d61b692ad919b616df0aa75a9f73e4ea6fc27d216f48964e7daebd84b796418580cf97d4f08d4a4b51037cd", + "0x8901ba9e963fcd2f7e08179b6d19c7a3b8193b78ca0e5cf0175916de873ca0d000cd7ac678c0473be371e0ac132f35a2", + "0xb11a445433745f6cb14c9a65314bbf78b852f7b00786501b05d66092b871111cd7bee25f702d9e550d7dd91601620abb", + "0x8c2f7b8e7b906c71f2f154cc9f053e8394509c37c07b9d4f21b4495e80484fc5fc8ab4bdc525bd6cfa9518680ba0d1a2", + "0xb9733cebe92b43b899d3d1bfbf4b71d12f40d1853b2c98e36e635fdd8a0603ab03119890a67127e6bc79afae35b0bef2", + "0xa560f6692e88510d9ba940371e1ada344caf0c36440f492a3067ba38e9b7011caac37ba096a8a4accb1c8656d3c019b3", + "0xac18624339c1487b2626eef00d66b302bdb1526b6340d6847befe2fdfb2b410be5555f82939f8707f756db0e021ed398", + "0xafd9a3b8866a7fe4f7bc13470c0169b9705fcd3073685f5a6dcff3bdbbc2be50ac6d9908f9a10c5104b0bffc2bc14dad", + "0x97f15c92fe1f10949ed9def5dd238bc1429706e5037a0e0afb71c2d0e5845e2fed95a171c393e372077a7c7059f8c0e0", + "0x9453a1d4d09c309b70968ea527007d34df9c4cfd3048e5391aac5f9b64ca0c05dde5b8c949c481cfc83ef2e57b687595", + "0xb80e4b7c379ad435c91b20b3706253b763cbc980db78f782f955d2516af44c07bbfa5888cbf3a8439dc3907320feb25a", + "0x8939f458d28fefe45320b95d75b006e98330254056d063e4a2f20f04bcb25936024efe8d436d491ed34b482f9b9ae49c", + "0xa9ead2e833f71f7e574c766440c4b3c9c3363698c7ade14499a56003a272832ee6d99440887fa43ccdf80265b9d56b97", + "0xb6547a36934f05ce7b779e68049d61351cf229ae72dc211cc96a2a471b2724782f9355fdb415ea6f0ea1eb84fe00e785", + "0x828bfb3099b7b650b29b0f21279f829391f64520a6ab916d1056f647088f1e50fac9253ef7464eceab5380035c5a59c4", + "0x8d714b9ea650be4342ff06c0256189e85c5c125adf6c7aeca3dba9b21d5e01a28b688fc2116ce285a0714a8f1425c0b8", + "0x8a82eda041b2e72a3d73d70d85a568e035fbd6dc32559b6c6cfdf6f4edcb59a6ba85b6294a721aa0a71b07714e0b99ae", + "0xaf5665ebc83d027173b14ffb0e05af0a192b719177889fadc9ac8c082fda721e9a75d9ce3f5602dbfd516600ee3b6405", + "0xa68fdddf03d77bebdb676e40d93e59bd854408793df2935d0a5600601f7691b879981a398d02658c2da39dbbf61ef96c", + "0x8c001ebc84fcf0470b837a08a7b6125126b73a2762db47bbdc38c0e7992b1c66bac7a64faa1bf1020d1c63b40adc3082", + "0x8553889b49f9491109792db0a69347880a9cf2911b4f16f59f7f424e5e6b553687d51282e8f95be6a543635247e2e2c2", + "0xa2c269d6370b541daf1f23cc6b5d2b03a5fa0c7538d53ae500ef875952fe215e74a5010329ff41461f4c58b32ad97b3d", + "0xa5dae097285392b4eba83a9fd24baa03d42d0a157a37fae4b6efc3f45be86024b1182e4a6b6eadcf5efe37704c0a1ae5", + "0x89871a77d2032387d19369933cd50a26bda643e40cfd0ce73febe717a51b39fae981406fd41e50f4a837c02a99524ef9", + "0x8a76d495e90093ec2ac22f53759dc1cf36fbb8370fb586acbd3895c56a90bbf3796bcc4fc422ca4058adf337ead1402e", + "0xad4eb7576c4954d20623c1336c63662c2a6fb46ec6ef99b7f8e946aa47488dcb136eab60b35600f98c78c16c10c99013", + "0x894c2b120cec539feb1d281baaadde1e44beafedeeec29b804473fe024e25c1db652f151c956e88d9081fb39d27e0b19", + "0x9196bd5c100878792444c573d02b380a69e1b4b30cb59a48114852085058a5fd952df4afee3ecceb5c4ede21e1ed4a1a", + "0xa996fffc910764ea87a1eedc3a3d600e6e0ff70e6a999cb435c9b713a89600fc130d1850174efe9fc18244bb7c6c5936", + "0x8591bb8826befa8bee9663230d9a864a5068589f059e37b450e8c85e15ce9a1992f0ce1ead1d9829b452997727edcf9d", + "0x9465e20bb22c41bf1fa728be8e069e25cda3f7c243381ca9973cbedad0c7b07d3dd3e85719d77cf80b1058ce60e16d68", + "0x926b5ce39b6e60b94878ffeae9ff20178656c375fb9cfe160b82318ca500eb3e2e3144608b6c3f8d6c856b8fe1e2fbcf", + "0xa1ef29cbc83c45eb28ad468d0ce5d0fdd6b9d8191ba5ffa1a781c2b232ed23db6b7b04de06ef31763a6bfe377fa2f408", + "0x9328e63a3c8acf457c9f1f28b32d90d0eeadb0f650b5d43486a61d7374757a7ada5fc1def2a1e600fa255d8b3f48036f", + "0xa9c64880fcb7654f4dd08f4c90baac95712dd6dd407e17ea60606e9a97dc8e54dd25cb72a9bf3fc61f8d0ad569fe369d", + "0xa908eb7b940c1963f73046d6b35d40e09013bfbfbeb2ccd64df441867e202b0f3b625fa32dd04987c3d7851360abdffc", + "0xb3947b5ed6d59e59e4472cdb1c3261de1b5278fb7cb9b5fca553f328b3b3e094596861ea526eca02395f7b7358155b7b", + "0x99da7f190d37bc58945f981cf484d40fcf0855cf8178e2ce8d057c7f0a9d9f77425fdbce9ef8366f44f671b20fd27d0b", + "0x913976d77d80e3657977df39571577fdf0be68ba846883705b454f8493578baa741cfaede53783e2c97cc08964395d83", + "0x8d754a61e5164a80b5090c13f3e936056812d4ae8dc5cc649e6c7f37464777249bc4ae760a9806939131f39d92cca5bf", + "0x82ffd098480828a90cb221a8c28584e15904bad477c13b2e2d6ef0b96a861ce4a309a328fe44342365349456ad7c654f", + "0x89ae3ce4b0357044579ca17be85d8361bb1ce3941f87e82077dd67e43ec0f95edd4bd3426225c90994a81a99e79490b7", + "0xa170892074016d57c9d8e5a529379d7e08d2c1158b9ac4487ac9b95266c4fd51cb18ae768a2f74840137eec05000dd5a", + "0xaafd8acd1071103c7af8828a7a08076324d41ea530df90f7d98fafb19735fc27ead91b50c2ca45851545b41d589d0f77", + "0x8623c849e61d8f1696dc9752116a26c8503fd36e2cbbc9650feffdd3a083d8cdbb3b2a4e9743a84b9b2ad91ac33083f2", + "0xac7166ddd253bb22cdbd8f15b0933c001d1e8bc295e7c38dc1d2be30220e88e2155ecd2274e79848087c05e137e64d01", + "0xa5276b216d3df3273bbfa46210b63b84cfe1e599e9e5d87c4e2e9d58666ecf1af66cb7ae65caebbe74b6806677215bd0", + "0x88792f4aa3597bb0aebadb70f52ee8e9db0f7a9d74f398908024ddda4431221a7783e060e0a93bf1f6338af3d9b18f68", + "0x8f5fafff3ecb3aad94787d1b358ab7d232ded49b15b3636b585aa54212f97dc1d6d567c180682cca895d9876cacb7833", + "0xab7cb1337290842b33e936162c781aa1093565e1a5b618d1c4d87dd866daea5cebbcc486aaa93d8b8542a27d2f8694c7", + "0x88480a6827699da98642152ebc89941d54b4791fbc66110b7632fb57a5b7d7e79943c19a4b579177c6cf901769563f2f", + "0xa725ee6d201b3a610ede3459660658ee391803f770acc639cfc402d1667721089fb24e7598f00e49e81e50d9fd8c2423", + "0x98924372da8aca0f67c8c5cad30fa5324519b014fae7849001dcd51b6286118f12b6c49061219c37714e11142b4d46de", + "0xa62c27360221b1a7c99697010dfe1fb31ceb17d3291cf2172624ebeff090cbaa3c3b01ec89fe106dace61d934711d42d", + "0x825173c3080be62cfdc50256c3f06fe190bc5f190d0eb827d0af5b99d80936e284a4155b46c0d462ee574fe31d60983d", + "0xa28980b97023f9595fadf404ed4aa36898d404fe611c32fd66b70252f01618896f5f3fda71aea5595591176aabf0c619", + "0xa50f5f9def2114f6424ff298f3b128068438f40860c2b44e9a6666f43c438f1780be73cf3de884846f1ba67f9bef0802", + "0xb1eee2d730da715543aeb87f104aff6122cb2bf11de15d2519ff082671330a746445777924521ec98568635f26988d0c", + "0x862f6994a1ff4adfd9fb021925cccf542fca4d4b0b80fb794f97e1eb2964ef355608a98eec6e07aadd4b45ee625b2a21", + "0x8ce69a18df2f9b9f6e94a456a7d94842c61dea9b00892da7cf5c08144de9be39b8c304aeca8b2e4222f87ba367e61006", + "0xb5f325b1cecd435f5346b6bc562d92f264f1a6d91be41d612df012684fdd69e86063db077bc11ea4e22c5f2a13ae7bee", + "0x85526870a911127835446cb83db8986b12d5637d59e0f139ad6501ac949a397a6c73bd2e7fba731b1bb357efe068242c", + "0x8552247d3f7778697f77389717def5a149fc20f677914048e1ed41553b039b5427badc930491c0bae663e67668038fd1", + "0xa545640ee5e51f3fe5de7050e914cfe216202056cd9d642c90e89a166566f909ee575353cb43a331fde17f1c9021414e", + "0x8b51229b53cff887d4cab573ba32ec52668d197c084414a9ee5589b285481cea0c3604a50ec133105f661321c3ca50f5", + "0x8cdc0b960522bed284d5c88b1532142863d97bbb7dc344a846dc120397570f7bd507ceb15ed97964d6a80eccfef0f28e", + "0xa40683961b0812d9d53906e795e6470addc1f30d09affebf5d4fbbd21ddfa88ce441ca5ea99c33fd121405be3f7a3757", + "0xa527875eb2b99b4185998b5d4cf97dd0d4a937724b6ad170411fc8e2ec80f6cee2050f0dd2e6fee9a2b77252d98b9e64", + "0x84f3a75f477c4bc4574f16ebc21aaa32924c41ced435703c4bf07c9119dd2b6e066e0c276ff902069887793378f779e0", + "0xa3544bc22d1d0cab2d22d44ced8f7484bfe391b36991b87010394bfd5012f75d580596ffd4f42b00886749457bb6334b", + "0xb81f6eb26934b920285acc20ceef0220dd23081ba1b26e22b365d3165ce2fbae733bbc896bd0932f63dcc84f56428c68", + "0x95e94d40a4f41090185a77bf760915a90b6a3e3ace5e53f0cb08386d438d3aa3479f0cd81081b47a9b718698817265cd", + "0xb69bd1625b3d6c17fd1f87ac6e86efa0d0d8abb69f8355a08739109831baeec03fd3cd4c765b5ff8b1e449d33d050504", + "0x8448f4e4c043519d98552c2573b76eebf2483b82d32abb3e2bfc64a538e79e4f59c6ca92adff1e78b2f9d0a91f19e619", + "0x8f11c42d6a221d1fda50887fb68b15acdb46979ab21d909ed529bcad6ae10a66228ff521a54a42aca0dad6547a528233", + "0xa3adb18d7e4a882b13a067784cf80ea96a1d90f5edc61227d1f6e4da560c627688bdf6555d33fe54cab1bca242986871", + "0xa24d333d807a48dc851932ed21cbdd7e255bad2699909234f1706ba55dea4bb6b6f8812ffc0be206755868ba8a4af3f9", + "0xa322de66c22a606e189f7734dbb7fda5d75766d5e69ec04b4e1671d4477f5bcb9ff139ccc18879980ebc3b64ab4a2c49", + "0x88f54b6b410a1edbf125db738d46ee1a507e69bc5a8f2f443eb787b9aa7dbd6e55014ec1e946aabeb3e27a788914fb04", + "0xb32ee6da1dcd8d0a7fd7c1821bb1f1fe919c8922b4c1eeed56e5b068a5a6e68457c42b192cbaef5dc6d49b17fa45bc0f", + "0x8a44402da0b3a15c97b0f15db63e460506cb8bef56c457166aea5e8881087d8202724c539ef0feb97131919a73aefca8", + "0xb967e3fead6171fa1d19fd976535d428b501baff59e118050f9901a54b12cc8e4606348454c8f0fc25bd6644e0a5532e", + "0xb7a0c9e9371c3efbbb2c6783ce2cc5f149135175f25b6d79b09c808bce74139020e77f0c616fa6dcb3d87a378532529d", + "0xa54207782ffc909cd1bb685a3aafabbc4407cda362d7b3c1b14608b6427e1696817aeb4f3f85304ac36e86d3d8caa65b", + "0x98c1da056813a7bfebc81d8db7206e3ef9b51f147d9948c088976755826cc5123c239ca5e3fe59bed18b5d0a982f3c3f", + "0xae1c86174dfafa9c9546b17b8201719aecd359f5bbeb1900475041f2d5b8a9600d54d0000c43dd061cfda390585726ff", + "0xa8ee5a8be0bd1372a35675c87bfd64221c6696dc16e2d5e0996e481fec5cdbcb222df466c24740331d60f0521285f7d3", + "0x8ddadbe3cf13af50d556ce8fc0dd77971ac83fad9985c3d089b1b02d1e3afc330628635a31707b32595626798ea22d45", + "0xa5c80254baf8a1628dc77c2445ebe21fbda0de09dd458f603e6a9851071b2b7438fe74214df293dfa242c715d4375c95", + "0xb9d83227ed2600a55cb74a7052003a317a85ca4bea50aa3e0570f4982b6fe678e464cc5156be1bd5e7bba722f95e92c5", + "0xb56085f9f3a72bea9aa3a8dc143a96dd78513fa327b4b9ba26d475c088116cab13843c2bff80996bf3b43d3e2bddb1d6", + "0x8fa9b39558c69a9757f1e7bc3f07295e4a433da3e6dd8c0282397d26f64c1ecd8eb3ba9824a7cacfb87496ebbb45d962", + "0x879c6d0cb675812ed9dee68c3479a499f088068501e2677caeae035e6f538da91a49e245f5fcce135066169649872bee", + "0x91aa9fd3fed0c2a23d1edda8a6542188aeb8abee8772818769bdee4b512d431e4625a343af5d59767c468779222cf234", + "0xa6be0bb2348c35c4143482c7ef6da9a93a5356f8545e8e9d791d6c08ed55f14d790d21ee61d3a56a2ae7f888a8fd46ca", + "0x808ee396a94e1b8755f2b13a6ffbedef9e0369e6c2e53627c9f60130c137299d0e4924d8ef367e0a7fad7f68a8c9193c", + "0xad1086028fcdac94d5f1e7629071e7e47e30ad0190ae59aaebfb7a7ef6202ab91323a503c527e3226a23d7937af41a52", + "0x9102bdaf79b907d1b25b2ec6b497e2d301c8eac305e848c6276b392f0ad734131a39cc02ed42989a53ca8da3d6839172", + "0x8c976c48a45b6bc7cd7a7acea3c2d7c5f43042863b0661d5cd8763e8b50730552187a8eecf6b3d17be89110208808e77", + "0xa2624c7e917e8297faa3af89b701953006bf02b7c95dfba00c9f3de77748bc0b13d6e15bb8d01377f4d98fb189538142", + "0xa405f1e66783cdcfe20081bce34623ec3660950222d50b7255f8b3cc5d4369aeb366e265e5224c0204911539f0fa165e", + "0x8d69bdcaa5d883b5636ac8f8842026fcc58c5e2b71b7349844a3f5d6fbecf44443ef4f768eac376f57fb763606e92c9f", + "0x82fce0643017d16ec1c3543db95fb57bfa4855cc325f186d109539fcacf8ea15539be7c4855594d4f6dc628f5ad8a7b0", + "0x8860e6ff58b3e8f9ae294ff2487f0d3ffae4cf54fd3e69931662dabc8efd5b237b26b3def3bcd4042869d5087d22afcf", + "0x88c80c442251e11c558771f0484f56dc0ed1b7340757893a49acbf96006aa73dfc3668208abea6f65375611278afb02a", + "0x8be3d18c6b4aa8e56fcd74a2aacb76f80b518a360814f71edb9ccf3d144bfd247c03f77500f728a62fca7a2e45e504c5", + "0x8b8ebf0df95c3f9b1c9b80469dc0d323784fd4a53f5c5357bb3f250a135f4619498af5700fe54ad08744576588b3dfff", + "0xa8d88abdaadd9c2a66bc8db3072032f63ed8f928d64fdb5f810a65074efc7e830d56e0e738175579f6660738b92d0c65", + "0xa0a10b5d1a525eb846b36357983c6b816b8c387d3890af62efb20f50b1cb6dd69549bbef14dab939f1213118a1ae8ec2", + "0x8aadf9b895aeb8fdc9987daa937e25d6964cbd5ec5d176f5cdf2f0c73f6f145f0f9759e7560ab740bf623a3279736c37", + "0x99aeda8a495031cc5bdf9b842a4d7647c55004576a0edc0bd9b985d60182608361ed5459a9d4b21aa8e2bd353d10a086", + "0x832c8b3bfcd6e68eee4b100d58014522de9d4cefa99498bc06c6dca83741e4572e20778e0d846884b33439f160932bca", + "0x841f56ebefc0823ab484fc445d62f914e13957e47904419e42771aa605e33ab16c44f781f6f9aa42e3a1baf377f54b42", + "0xa6e40271d419e295a182725d3a9b541ffd343f23e37549c51ecaa20d13cf0c8d282d6d15b24def5702bfee8ba10b12ac", + "0x8ac00925ac6187a4c5cde48ea2a4eaf99a607e58b2c617ee6f01df30d03fafada2f0469178dd960d9d64cbd33a0087d8", + "0xb6b80916b540f8a0fe4f23b1a06e2b830008ad138271d5ba3cd16d6619e521fe2a7623c16c41cba48950793386eea942", + "0x8412c0857b96a650e73af9d93087d4109dd092ddf82188e514f18fcac644f44d4d62550bfa63947f2d574a2e9d995bbb", + "0xb871395baa28b857e992a28ac7f6d95ec461934b120a688a387e78498eb26a15913b0228488c3e2360391c6b7260b504", + "0x926e2d25c58c679be77d0e27ec3b580645956ba6f13adcbc2ea548ee1b7925c61fcf74c582337a3b999e5427b3f752f2", + "0xa165fa43fecae9b913d5dcfc232568e3e7b8b320ce96b13800035d52844c38fd5dbf7c4d564241d860c023049de4bcbc", + "0xb4976d7572fd9cc0ee3f24888634433f725230a7a2159405946a79315bc19e2fc371448c1c9d52bf91539fd1fe39574b", + "0xa6b461eb72e07a9e859b9e16dfa5907f4ac92a5a7ca4368b518e4a508dc43f9b4be59db6849739f3ef4c44967b63b103", + "0xb976606d3089345d0bc501a43525d9dca59cf0b25b50dfc8a61c5bd30fac2467331f0638fab2dc68838aa6ee8d2b6bc9", + "0xb16ea61c855da96e180abf7647fa4d9dd6fd90adebadb4c5ed4d7cd24737e500212628fca69615d89cb40e9826e5a214", + "0x95a3e3162eb5ea27a613f8c188f2e0dcc5cbd5b68c239858b989b004d87113e6aa3209fa9fad0ee6ecef42814ba9db1a", + "0xb6a026ab56d3224220e5bce8275d023c8d39d1bdf7eec3b0923429b7d5ef18cf613a3591d364be8727bb1fa0ba11eabb", + "0x949f117e2e141e25972ee9ccdd0b7a21150de7bbf92bbd89624a0c5f5a88da7b2b172ba2e9e94e1768081f260c2a2f8d", + "0xb7c5e9e6630287d2a20a2dfb783ffe6a6ff104ff627c6e4e4342acc2f3eb6e60e9c22f465f8a8dc58c42f49840eca435", + "0x872be5a75c3b85de21447bb06ac9eb610f3a80759f516a2f99304930ddf921f34cbffc7727989cdd7181d5fc62483954", + "0xa50976ea5297d797d220932856afdd214d1248230c9dcd840469ecc28ea9f305b6d7b38339fedb0c00b5251d77af8c95", + "0x80b360f8b44914ff6f0ffbd8b5360e3cabe08639f6fe06d0c1526b1fe9fe9f18c497f1752580b30e950abd3e538ad416", + "0xa2f98f9bf7fac78c9da6bb41de267742a9d31cf5a04b2fb74f551084ec329b376f651a59e1ae919b2928286fb566e495", + "0x8b9d218a8a6c150631548e7f24bbd43f132431ae275c2b72676abbea752f554789c5ff4aac5c0eeee5529af7f2b509ef", + "0xaa21a243b07e9c7b169598bf0b102c3c280861780f83121b2ef543b780d47aaa4b1850430ee7927f33ece9847c4e0e1a", + "0x8a6f90f4ce58c8aa5d3656fe4e05acccf07a6ec188a5f3cde7bf59a8ae468e66f055ac6dfc50b6e8e98f2490d8deedc5", + "0x8e39f77ca4b5149ffe9945ceac35d068760ba338d469d57c14f626dd8c96dbe993dd7011beff727c32117298c95ee854", + "0x83bd641c76504222880183edd42267e0582642c4993fe2c7a20ce7168e4c3cbf7586e1d2d4b08c84d9b0bf2f6b8800b8", + "0xa9d332993cf0c1c55130e5cf3a478eb5e0bfb49c25c07538accc692ef03d82b458750a7b991cc0b41b813d361a5d31e3", + "0xa0fc60e6a6015df9bee04cea8f20f01d02b14b6f7aa03123ab8d65da071b2d0df5012c2a69e7290baae6ed6dd29ebe07", + "0xa2949dde2e48788ceaac7ec7243f287ffe7c3e788cdba97a4ab0772202aeef2d50382bed8bf7eff5478243f7eabe0bda", + "0xa7879373ea18572dba6cf29868ca955ffa55b8af627f29862f6487ee398b81fe3771d8721ca8e06716c5d91b9ac587cb", + "0xb3c7081e2c5306303524fbe9fe5645111a57dffd4ec25b7384da12e56376a0150ab52f9d9cc6ca7bdd950695e39b766d", + "0xa634a6a19d52dcb9f823352b36c345d2de54b75197bcd90528d27830bd6606d1a9971170de0849ed5010afa9f031d5be", + "0x88f2062f405fa181cfdb8475eaf52906587382c666ca09a9522537cfebbc7de8337be12a7fd0db6d6f2f7ab5aefab892", + "0xb1f0058c1f273191247b98783b2a6f5aa716cf799a8370627fc3456683f03a624d0523b63a154fe9243c0dfd5b37c460", + "0xae39a227cc05852437d87be6a446782c3d7fbe6282e25cf57b6b6e12b189bdc0d4a6e2c3a60b3979256b6b5baf8f1c5f", + "0x802a1af228ab0c053b940e695e7ef3338f5be7acf4e5ed01ac8498e55b492d3a9f07996b1700a84e22f0b589638909cd", + "0xa36490832f20e4b2f9e79ee358b66d413f034d6a387534b264cdeac2bca96e8b5bcbdd28d1e98c44498032a8e63d94d2", + "0x8728c9a87db2d006855cb304bba54c3c704bf8f1228ae53a8da66ca93b2dac7e980a2a74f402f22b9bc40cd726e9c438", + "0xa08f08ab0c0a1340e53b3592635e256d0025c4700559939aeb9010ed63f7047c8021b4210088f3605f5c14fb51d1c613", + "0x9670fd7e2d90f241e8e05f9f0b475aa260a5fb99aa1c9e61cd023cbad8ed1270ae912f168e1170e62a0f6d319cf45f49", + "0xa35e60f2dd04f098bf274d2999c3447730fe3e54a8aff703bc5a3c274d22f97db4104d61a37417d93d52276b27ef8f31", + "0x859df7a21bc35daec5695201bd69333dc4f0f9e4328f2b75a223e6615b22b29d63b44d338413ca97eb74f15563628cb7", + "0xb2b44ad3e93bc076548acdf2477803203108b89ecc1d0a19c3fb9814d6b342afc420c20f75e9c2188ad75fdb0d34bb2d", + "0x941173ee2c87765d10758746d103b667b1227301e1bcfecef2f38f9ab612496a9abd3050cef5537bf28cfecd2aacc449", + "0x92b0bea30ebed20ac30648efb37bac2b865daaa514316e6f5470e1de6cb84651ff77c127aa7beed4521bda5e8fc81122", + "0xaf17bf813bb238cf8bb437433f816786612209180a6c0a1d5141292dc2d2c37164ef13bfc50c718bfcc6ce26369298a2", + "0x8461fd951bdfda099318e05cc6f75698784b033f15a71bce26165f0ce421fd632d50df9eeced474838c0050b596e672c", + "0x83281aa18ae4b01e8201e1f64248cc6444c92ee846ae72adb178cef356531558597d84ff93a05abf76bfe313eb7dbe86", + "0xb62b150f73999c341daa4d2f7328d2f6ca1ef3b549e01df58182e42927537fc7971c360fe8264af724f4c0247850ef12", + "0xa7022a201f79c012f982b574c714d813064838a04f56964d1186691413757befeeaada063e7884297606e0eea1b1ed43", + "0xa42ac9e8be88e143853fd8e6a9ff21a0461801f0ac76b69cca669597f9af17ecb62cccdcdcbe7f19b62ab93d7f838406", + "0x80f1ca73b6ba3a2fbae6b79b39c0be8c39df81862d46c4990c87cbf45b87996db7859d833abc20af2fcb4faf059c436a", + "0xb355943e04132d5521d7bbe49aea26f6aa1c32f5d0853e77cc2400595325e923a82e0ff7601d1aee79f45fd8a254f6ae", + "0x87142c891d93e539b31d0b5ead9ea600b9c84db9be9369ff150a8312fe3d10513f4c5b4d483a82b42bc65c45dd9dd3bd", + "0x823c3d7f6dda98a9d8c42b3fee28d3154a95451402accadb6cf75fc45d2653c46a569be75a433094fa9e09c0d5cf1c90", + "0xb3c3497fe7356525c1336435976e79ec59c5624c2fb6185ee09ca0510d58b1e392965e25df8a74d90d464c4e8bb1422b", + "0x88c48d83e8ddc0d7eea051f3d0e21bc0d3a0bb2b6a39ece76750c1c90c382a538c9a35dc9478b8ceb8157dcccbbf187a", + "0x93da81a8939f5f58b668fefdc6f5f7eca6dc1133054de4910b651f8b4a3267af1e44d5a1c9e5964dc7ab741eb146894b", + "0x8b396e64985451ac337f16be61105106e262e381ea04660add0b032409b986e1ac64da3bc2feae788e24e9cb431d8668", + "0x9472068b6e331ea67e9b5fbf8057672da93c209d7ded51e2914dbb98dccd8c72b7079b51fd97a7190f8fc8712c431538", + "0xac47e1446cb92b0a7406f45c708567f520900dfa0070d5e91783139d1bfc946d6e242e2c7b3bf4020500b9f867139709", + "0x896053706869fb26bb6f7933b3d9c7dd6db5c6bd1269c7a0e222b73039e2327d44bda7d7ae82bf5988808b9831d78bcd", + "0xa55e397fa7a02321a9fe686654c86083ecedb5757586d7c0250ec813ca6d37151a12061d5feca4691a0fd59d2f0fdd81", + "0xae23f08ac2b370d845036518f1bddb7fea8dc59371c288a6af310486effeb61963f2eef031ca90f9bdbcf0e475b67068", + "0xb5462921597a79f66c0fec8d4c7cfd89f427692a7ce30d787e6fd6acd2377f238ec74689a0fdbe8ef3c9c9bd24b908dc", + "0xae67e8ea7c46e29e6aae6005131c29472768326819aa294aaf5a280d877de377b44959adb1348fa3e929dcbc3ae1f2c0", + "0x84962b4c66500a20c4424191bdfb619a46cda35bdb34c2d61edcb0b0494f7f61dd5bf8f743302842026b7b7d49edd4b5", + "0x846f76286dc3cc59cb15e5dabb72a54a27c78190631df832d3649b2952fa0408ecde7d4dfdae7046c728efa29879fb51", + "0x8f76c854eaee8b699547e07ad286f7dadfa6974c1328d12502bd7630ae619f6129272fdd15e2137ffef0143c42730977", + "0x8007b163d4ea4ec6d79e7a2aa19d06f388da0b3a56f3ee121441584e22a246c0e792431655632bf6e5e02cb86914eebf", + "0xac4d2cecc1f33e6fb73892980b61e62095ddff5fd6167f53ca93d507328b3c05440729a277dc3649302045b734398af1", + "0x92d2a88f2e9c9875abaff0d42624ccb6d65401de7127b5d42c25e6adccd7a664504c5861618f9031ced8aeb08b779f06", + "0xa832c1821c1b220eb003fc532af02c81196e98df058cdcc9c9748832558362915ea77526937f30a2f74f25073cb89afb", + "0xb6f947ab4cc2baec100ed8ec7739a2fd2f9504c982b39ab84a4516015ca56aea8eef5545cfc057dd44c69b42125fb718", + "0xb24afacf2e90da067e5c050d2a63878ee17aaf8fd446536f2462da4f162de87b7544e92c410d35bf2172465940c19349", + "0xb7a0aa92deac71eaab07be8fa43086e071e5580f5dbf9b624427bdd7764605d27303ae86e5165bed30229c0c11958c38", + "0xb0d1d5bfa1823392c5cf6ed927c1b9e84a09a24b284c2cd8fcb5fda8e392c7c59412d8f74eb7c48c6851dff23ae66f58", + "0xa24125ef03a92d2279fb384186ca0274373509cfec90b34a575490486098438932ee1be0334262d22d5f7d3db91efe67", + "0x83e08e5fba9e8e11c164373794f4067b9b472d54f57f4dbe3c241cf7b5b7374102de9d458018a8c51ab3aed1dddf146f", + "0x9453101b77bb915ed40990e1e1d2c08ea8ec5deb5b571b0c50d45d1c55c2e2512ec0ceca616ff0376a65678a961d344d", + "0x92a0516e9eb6ad233d6b165a8d64a062ce189b25f95d1b3264d6b58da9c8d17da2cd1f534800c43efcf2be73556cd2ff", + "0x958d0b5d7d8faf25d2816aa6a2c5770592ad448db778dd9b374085baa66c755b129822632eaabcb65ee35f0bf4b73634", + "0x90a749de8728b301ad2a6b044e8c5fd646ccd8d20220e125cba97667e0bb1d0a62f6e3143b28f3d93f69cdc6aa04122a", + "0x84bd34c8d8f74dec07595812058db24d62133c11afed5eb2a8320d3bfc28e442c7f0cfd51011b7b0bb3e5409cb7b6290", + "0xaecc250b556115d97b553ad7b2153f1d69e543e087890000eaa60f4368b736921d0342ce5563124f129096f5d5e2ca9d", + "0x977f17ac82ed1fbf422f9b95feb3047a182a27b00960296d804fd74d54bb39ad2c055e665c1240d2ad2e06a3d7501b00", + "0xaf5be9846bd4879ebe0af5e7ad253a632f05aedfe306d31fe6debe701ba5aa4e33b65efc05043bc73aadb199f94baed4", + "0x9199e12ec5f2aaaeed6db5561d2dcc1a8fe9c0854f1a069cba090d2dff5e5ba52b10c841ccbd49006a91d881f206150d", + "0x8f4a96a96ed8ceaf3beba026c89848c9ca4e6452ce23b7cf34d12f9cc532984a498e051de77745bdc17c7c44c31b7c30", + "0xaf3f2a3dbe8652c4bfca0d37fb723f0e66aab4f91b91a625114af1377ad923da8d36da83f75deb7a3219cd63135a3118", + "0xa6d46963195df8962f7aa791d104c709c38caa438ddd192f7647a884282e81f748c94cdf0bb25d38a7b0dc1b1d7bbcf7", + "0x86f3de4b22c42d3e4b24b16e6e8033e60120af341781ab70ae390cb7b5c5216f6e7945313c2e04261a51814a8cb5db92", + "0xb9f86792e3922896cfd847d8ff123ff8d69ecf34968fb3de3f54532f6cd1112b5d34eeabdca46ae64ad9f6e7e5b55edc", + "0x83edfbcbc4968381d1e91ab813b3c74ab940eaf6358c226f79182f8b21148ec130685fd91b0ea65916b0a50bccf524ea", + "0x93b61daca7a8880b7926398760f50016f2558b0bab74c21181280a1baf3414fc539911bb0b79c4288d29d3c4ad0f4417", + "0xad541aeb83a47526d38f2e47a5ce7e23a9adabe5efeae03541026881e6d5ef07da3ac1a6ed466ca924fa8e7a91fcff88", + "0xac4bba31723875025640ed6426003ed8529215a44c9ffd44f37e928feef9fc4dfa889088131c9be3da87e8f3fdf55975", + "0x88fa4d49096586bc9d29592909c38ea3def24629feacd378cc5335b70d13814d6dac415f8c699ee1bf4fe8b85eb89b38", + "0xb67d0b76cbd0d79b71f4673b96e77b6cda516b8faa1510cfe58ff38cc19000bb5d73ff8418b3dab8c1c7960cb9c81e36", + "0x98b4f8766810f0cfecf67bd59f8c58989eb66c07d3dfeee4f4bbce8fd1fce7cc4f69468372eaec7d690748543bd9691d", + "0x8445891af3c298b588dec443beacdf41536adb84c812c413a2b843fd398e484eb379075c64066b460839b5fe8f80177c", + "0xb603635c3ed6fdc013e2a091fc5164e09acf5f6a00347d87c6ebadb1f44e52ff1a5f0466b91f3f7ffc47d25753e44b75", + "0x87ec2fc928174599a9dafe7538fec7dcf72e6873b17d953ed50708afff0da37653758b52b7cafa0bf50dfcf1eafbb46c", + "0xb9dbd0e704d047a457d60efe6822dc679e79846e4cbcb11fa6c02079d65673ee19bbf0d14e8b7b200b9205f4738df7c7", + "0x9591ec7080f3f5ba11197a41f476f9ba17880f414d74f821a072ec5061eab040a2acba3d9856ff8555dfe5eaeb14ca19", + "0xb34c9d1805b5f1ce38a42b800dec4e7f3eb8c38e7d2b0a525378e048426fed150dbfe9cc61f5db82b406d1b9ff2d10bf", + "0xa36fdc649dc08f059dfa361e3969d96b4cc4a1ebf10b0cd01a7dd708430979e8d870961fef85878f8779b8e23caafb18", + "0x88dfc739a80c16c95d9d6f73c3357a92d82fa8c3c670c72bee0f1e4bac9ec338e1751eb786eda3e10f747dd7a686900f", + "0x84a535ad04f0961756c61c70001903a9adf13126983c11709430a18133c4b4040d17a33765b4a06968f5d536f4bfb5c5", + "0x8c86d695052a2d2571c5ace744f2239840ef21bb88e742f050c7fa737cd925418ecef0971333eb89daa6b3ddfede268c", + "0x8e9a700157069dc91e08ddcbdde3a9ad570272ad225844238f1015004239c542fceb0acce6d116c292a55f0d55b6175e", + "0x84d659e7f94e4c1d15526f47bc5877a4ef761c2a5f76ec8b09c3a9a30992d41b0e2e38ed0c0106a6b6c86d670c4235f3", + "0xa99253d45d7863db1d27c0ab561fb85da8c025ba578b4b165528d0f20c511a9ca9aff722f4ff7004843f618eb8fced95", + "0x89a3cacb15b84b20e95cd6135550146bbe6c47632cc6d6e14d825a0c79b1e02b66f05d57d1260cb947dc4ae5b0283882", + "0x8385b1555e794801226c44bd5e878cbe68aeac0a19315625a8e5ea0c3526b58cdd4f53f9a14a167a5e8a293b530d615a", + "0xb68c729e9df66c5cd22af4909fb3b0057b6a231c4a31cd6bf0fa0e53c5809419d15feb483de6e9408b052458e819b097", + "0x924f56eda269ec7ec2fc20c5731bf7f521546ddf573ccbe145592f1c9fee5134747eb648d9335119a8066ca50a1f7e50", + "0xb2100a26b9c3bec7ec5a53f0febbf56303f199be2f26b2d564cfee2adc65483b84192354f2865c2f4c035fa16252ae55", + "0x8f64dbed62e638563967ec1605a83216aed17eb99aa618c0543d74771ea8f60bbb850c88608d4f8584f922e30a8a0a72", + "0xb31b9e1ffe8d7260479c9413f8e680f3fe391ae8fcf44fcca3000d9b2473a40c1d32299f8f63865a57579a2d6c7e9f08", + "0xa5b1d136142eb23e322c6c07cb838a3f58ab6925472352ebd0bb47041a0d8729e1074ca223922f3a7a672ced7a1e562d", + "0x8d9470a5a15d833a447b5f108333d50f30aa7659e331c3f8080b1e928a99922edc650466a2f54f3d48afdb34bff42142", + "0x866368f5891564e5b2de37ad21ff0345c01129a14ea5667f9b64aad12d13ec034622872e414743af0bf20adb2041b497", + "0x88ef9c2ebf25fd0c04b7cfa35fbac2e4156d2f1043fa9f98998b2aa402c8f9a4f1039e782451a46840f3e0e4b3fa47d3", + "0x94ba04a4859273697e264a2d238dc5c9ff573ebc91e4796ea58eebe4080c1bf991255ab2ad8fb1e0301ce7b79cc6e69b", + "0x86b6bd0953309a086e526211bf1a99327269304aa74d8cdc994cee63c3a2d4b883e832b0635888dff2a13f1b02eb8df4", + "0x843ea6ea5f2c7a1fd50be56a5765dcce3ea61c99b77c1a729ee0cd8ec706385ac7062e603479d4c8d3527f030762d049", + "0x8d3675195a3b06f2d935d45becc59f9fa8fa440c8df80c029775e47fe9c90e20f7c8e4cc9a2542dd6bfe87536c428f0d", + "0x8978580b0c9b0aa3ab2d47e3cfd92fa891d3ddee57829ee4f9780e8e651900457d8e759d1a9b3e8f6ae366e4b57f2865", + "0x890112ec81d0f24b0dfbb4d228e418eff02ae63dc691caf59c1d103e1d194e6e2550e1bec41c0bfdb74fed454f621d0c", + "0x97da00bd4b19d1e88caff7f95b8b9a7d29bc0afe85d0c6a163b4b9ef336f0e90e2c49ce6777024bb08df908cc04ea1ca", + "0xb458268d275a5211106ccaa8333ce796ef2939b1c4517e502b6462e1f904b41184a89c3954e7c4f933d68b87427a7bfd", + "0xaac9c043ba8ba9283e8428044e6459f982413380ee7005a996dc3cc468f6a21001ecaa3b845ce2e73644c2e721940033", + "0x82145013c2155a1200246a1e8720adf8a1d1436b10d0854369d5b1b6208353e484dd16ce59280c6be84a223f2d45e5e2", + "0xb301bafa041f9b203a46beab5f16160d463aa92117c77a3dc6a9261a35645991b9bafcc186c8891ca95021bd35f7f971", + "0xa531b8d2ac3de09b92080a8d8857efa48fb6a048595279110e5104fee7db1dd7f3cfb8a9c45c0ed981cbad101082e335", + "0xa22ac1d627d08a32a8abd41504b5222047c87d558ffae4232cefdeb6a3dc2a8671a4d8ddfba2ff9068a9a3ffb0fe99b1", + "0xb8d9f0e383c35afb6d69be7ff04f31e25c74dd5751f0e51290c18814fbb49ee1486649e64355c80e93a3d9278bd21229", + "0x8165babccd13033a3614c878be749dfa1087ecbeee8e95abcfffe3aa06695711122cb94477a4d55cffd2febf0c1173de", + "0xa4c1bc84ecb9d995d1d21c2804adf25621676d60334bd359dac3a2ec5dc8de567aa2831c10147034025fb3e3afb33c4b", + "0xb77307cab8e7cb21e4038493058fb6db9e2ec91dda9d7f96f25acbc90309daf7b6d8a205682143ee35d675e9800c3b08", + "0xaaf7466083cd1f325ba860efe3faf4cebe6a5eecf52c3e8375d72043a5cfc8e6cb4b40f8e48f97266e84f0d488e8badf", + "0x9264a05a3abc2a5b4958f957f3a486a5eb3ddd10ff57aa6943c9430d0cfa01d63b72695b1ade50ac1b302d312175e702", + "0xb3f9e4c589ad28b1eceed99dc9980fac832524cfcbe4a486dfeedb4b97c080e24bdb3967e9ca63d2240e77f9addfaefd", + "0xb2c1e253a78e7179e5d67204422e0debfa09c231970b1bfb70f31a8d77c7f5059a095ca79d2e9830f12c4a8f88881516", + "0x81865a8a25913d1072cb5fd9505c73e0fde45e4c781ddd20fb0a7560d8b1cd5e1f63881c6efc05360e9204dfa6c3ce16", + "0xab71c2ea7fa7853469a2236dedb344a19a6130dc96d5fd6d87d42d3fffda172557d203b7688ce0f86acd913ce362e6cd", + "0x8aa2051bc3926c7bd63565f3782e6f77da824cb3b22bb056aa1c5bccfa274c0d9e49a91df62d0e88876e2bd7776e44b9", + "0xb94e7074167745323d1d353efe7cfb71f40a390e0232354d5dfd041ef523ac8f118fb6dcc42bf16c796e3f61258f36f8", + "0x8210fcf01267300cb1ccf650679cf6e1ee46df24ae4be5364c5ff715332746c113d680c9a8be3f17cacaeb3a7ba226ce", + "0x905ac223568eedc5acd8b54e892be05a21abbb4083c5dbec919129f9d9ffa2c4661d78d43bf5656d8d7aafa06f89d647", + "0xa6e93da7e0c998e6ce2592d1aa87d12bf44e71bec12b825139d56682cdce8f0ba6dbfe9441a9989e10578479351a3d9d", + "0xacde928a5e2df0d65de595288f2b81838155d5673013100a49b0cb0eb3d633237af1378148539e33ccd1b9a897f0fec3", + "0xa6e1a47e77f0114be6ae7acd2a51e6a9e38415cce7726373988153cdd5d4f86ef58f3309adc5681af4a159300ed4e5b5", + "0xad2b6a0d72f454054cb0c2ebc42cd59ff2da7990526bd4c9886003ba63b1302a8343628b8fe3295d3a15aa85150e0969", + "0xb0bc3aea89428d7918c2ee0cc57f159fba134dad224d0e72d21a359ca75b08fbb4373542f57a6408352033e1769f72c6", + "0xaad0497525163b572f135fad23fdd8763631f11deeaf61dea5c423f784fe1449c866040f303555920dc25e39cdb2e9b4", + "0x8ce5d8310d2e17342bf881d517c9afc484d12e1f4b4b08ad026b023d98cba410cd9a7cc8e2c3c63456652a19278b6960", + "0x8d9d57dbb24d68b6152337872bd5d422198da773174ade94b633f7c7f27670ff91969579583532ae7d8fe662c6d8a3b0", + "0x855a1c2d83becb3f02a8f9a83519d1cb112102b61d4cdd396844b5206e606b3fefdbcc5aa8751da2b256d987d74d9506", + "0x90eb7e6f938651f733cf81fcd2e7e8f611b627f8d94d4ac17ac00de6c2b841e4f80cada07f4063a13ae87b4a7736ca28", + "0x8161459a21d55e7f5f1cecfc1595c7f468406a82080bfa46d7fb1af4b5ec0cd2064c2c851949483db2aa376e9df418e6", + "0x8344ccd322b2072479f8db2ab3e46df89f536408cba0596f1e4ec6c1957ff0c73f3840990f9028ae0f21c1e9a729d7df", + "0x929be2190ddd54a5afe98c3b77591d1eae0ab2c9816dc6fe47508d9863d58f1ea029d503938c8d9e387c5e80047d6f1e", + "0x856e3d1f701688c650c258fecd78139ce68e19de5198cf1cd7bb11eba9d0f1c5af958884f58df10e3f9a08d8843f3406", + "0x8490ae5221e27a45a37ca97d99a19a8867bcc026a94f08bdccfbb4b6fa09b83c96b37ec7e0fd6ee05f4ae6141b6b64a8", + "0xb02dbd4d647a05ac248fda13708bba0d6a9cd00cae5634c1938b4c0abbb3a1e4f00f47aa416dcd00ffcdf166330bff9a", + "0x9076164bb99ca7b1a98d1e11cb2f965f5c22866658e8259445589b80e3cb3119c8710ede18f396ba902696785619079c", + "0xaacf016920936dae63778ad171386f996f65fe98e83cfcdd75e23774f189303e65cc8ad334a7a62f9230ed2c6b7f6fa4", + "0xa8031d46c7f2474789123469ef42e81c9c35eb245d38d8f4796bba406c02b57053f5ec554d45373ab437869a0b1af3f0", + "0xa4b76cd82dc1f305a0ee053e9a4212b67f5acc5e69962a8640d190a176b73fbc2b0644f896ff3927cd708d524668ed09", + "0xb00b029c74e6fdf7fb94df95ef1ccad025c452c19cddb5dccfb91efdcb8a9a1c17847cfa4486eae4f510e8a6c1f0791a", + "0x9455e5235f29a73e9f1a707a97ddb104c55b9d6a92cc9952600d49f0447d38ea073ee5cf0d13f7f55f12b4a5132f4b10", + "0xae118847542ed1084d269e8f3b503d0b6571a2c077def116ad685dcca2fca3dcb3f86e3f244284bdcd5ae7ac968d08a5", + "0x8dcb4965cd57e8b89cd71d6fc700d66caa805bfd29ab71357961527a7894e082d49145c2614b670dcb231ab9050d0663", + "0xadd6ed14f3183f4acc73feea19b22c9a330e431c674e5034924da31b69e8c02d79b570d12ef771a04215c4809e0f8a80", + "0x96ae7e110412ee87d0478fdbdbaab290eb0b6edd741bb864961845e87fd44bcbe630371060b8104d8bf17c41f2e3fca0", + "0xa20db17f384e9573ca0928af61affab6ff9dd244296b69b026d737f0c6cd28568846eca8dadf903ee0eecbb47368351d", + "0x937bfdf5feb0797863bc7c1be4dcc4f2423787952a3c77dfa3bfe7356f5dbcc4daebde976b84fc6bd97d5124fb8f85c9", + "0xa7050cc780445c124e46bba1acc0347ddcfa09a85b35a52cc5808bf412c859c0c680c0a82218f15a6daeefe73f0d0309", + "0xa9d9b93450e7630f1c018ea4e6a5ca4c19baa4b662eadfbe5c798fe798d8a3775ed1eb12bd96a458806b37ab82bdc10a", + "0xa52a4d5639e718380915daaefad7de60764d2d795443a3db7aeab5e16a1b8faa9441a4ccc6e809d8f78b0ac13eef3409", + "0x8e6f72b6664a8433b032849b03af68f9376b3c16c0bc86842c43fc7bf31e40bc9fc105952d5c5780c4afa19d7b802caa", + "0xa107ae72f037000c6ee14093de8e9f2c92aa5f89a0a20007f4126419e5cb982469c32187e51a820f94805c9fccd51365", + "0x9708218f9a984fe03abc4e699a4f3378a06530414a2e95e12ca657f031ef2e839c23fd83f96a4ba72f8203d54a1a1e82", + "0xb9129770f4c5fcac999e98c171d67e148abd145e0bf2a36848eb18783bb98dff2c5cef8b7407f2af188de1fae9571b1c", + "0x88cc9db8ff27eb583871eeeb517db83039b85404d735517c0c850bdfa99ae1b57fd24cf661ab60b4726878c17e047f37", + "0xa358c9aadc705a11722df49f90b17a2a6ba057b2e652246dc6131aaf23af66c1ca4ac0d5f11073a304f1a1b006bc0aa5", + "0xac79f25af6364a013ba9b82175ccee143309832df8f9c3f62c193660253679284624e38196733fb2af733488ab1a556e", + "0x82338e3ed162274d41a1783f44ae53329610134e6c62565353fbcc81131e88ce9f8a729d01e59e6d73695a378315111b", + "0xaa5ddcabf580fd43b6b0c3c8be45ffd26c9de8fa8d4546bb92d34f05469642b92a237d0806a1ad354f3046a4fcf14a92", + "0xb308d2c292052a8e17862c52710140ffafa0b3dbedd6a1b6334934b059fe03e49883529d6baf8b361c6e67b3fbf70100", + "0x96d870a15c833dddd8545b695139733d4a4c07d6206771a1524500c12607048731c49ec4ac26f5acc92dd9b974b2172c", + "0x8e99ee9ed51956d05faaf5038bffd48a2957917a76d9974a78df6c1ff3c5423c5d346778f55de07098b578ad623a390e", + "0xa19052d0b4b89b26172c292bbf6fd73e7486e7fd3a63c7a501bbd5cf7244e8e8ce3c1113624086b7cdf1a7693fdad8b5", + "0x958957caf99dc4bb6d3c0bc4821be10e3a816bd0ba18094603b56d9d2d1383ccc3ee8bc36d2d0aea90c8a119d4457eb4", + "0x8482589af6c3fc4aa0a07db201d8c0d750dd21ae5446ff7a2f44decf5bff50965fd6338745d179c67ea54095ecd3add4", + "0x8a088cc12cf618761eaa93da12c9158b050c86f10cd9f865b451c69e076c7e5b5a023e2f91c2e1eed2b40746ca06a643", + "0x85e81101590597d7671f606bd1d7d6220c80d3c62e9f20423e734482c94547714a6ac0307e86847cce91de46503c6a8a", + "0xb1bd39b481fc452d9abf0fcb73b48c501aaae1414c1c073499e079f719c4e034da1118da4ff5e0ce1c5a71d8af3f4279", + "0x942ae5f64ac7a5353e1deb2213f68aa39daa16bff63eb5c69fc8d9260e59178c0452227b982005f720a3c858542246c8", + "0x99fea18230e39df925f98e26ff03ab959cae7044d773de84647d105dfa75fd602b4f519c8e9d9f226ec0e0de0140e168", + "0x97b9841af4efd2bfd56b9e7cd2275bc1b4ff5606728f1f2b6e24630dbe44bc96f4f2132f7103bca6c37057fc792aeaab", + "0x94cdad044a6ab29e646ed30022c6f9a30d259f38043afcea0feceef0edc5f45297770a30718cbfec5ae7d6137f55fe08", + "0xa533a5efa74e67e429b736bb60f2ccab74d3919214351fe01f40a191e3ec321c61f54dd236f2d606c623ad556d9a8b63", + "0xb7bd0bb72cd537660e081f420545f50a6751bb4dd25fde25e8218cab2885dd81ffe3b888d608a396dfcb78d75ba03f3f", + "0xb1479e7aa34594ec8a45a97611d377206597149ece991a8cef1399738e99c3fa124a40396a356ab2ea135550a9f6a89f", + "0xb75570fc94b491aef11f70ef82aeb00b351c17d216770f9f3bd87f3b5ac90893d70f319b8e0d2450dc8e21b57e26df94", + "0xa5e3f3ab112530fe5c3b41167f7db5708e65479b765b941ce137d647adb4f03781f7821bb4de80c5dc282c6d2680a13d", + "0xb9b9c81b4cac7aca7e7c7baac2369d763dd9846c9821536d7467b1a7ec2e2a87b22637ab8bbeddb61879a64d111aa345", + "0xb1e3ee2c4dd03a60b2991d116c372de18f18fe279f712829b61c904103a2bd66202083925bc816d07884982e52a03212", + "0xa13f0593791dbbd360b4f34af42d5cc275816a8db4b82503fe7c2ff6acc22ae4bd9581a1c8c236f682d5c4c02cc274cc", + "0x86ba8238d3ed490abcc3f9ecc541305876315fb71bca8aaf87538012daab019992753bf1e10f8670e33bff0d36db0bf0", + "0xb65fbb89fafb0e2a66fe547a60246d00b98fe2cb65db4922d9cef6668de7b2f4bb6c25970f1e112df06b4d1d953d3f34", + "0xabb2d413e6f9e3c5f582e6020f879104473a829380b96a28123eb2bdd41a7a195f769b6ac70b35ba52a9fee9d6a289c3", + "0x88ec764573e501c9d69098a11ea1ad20cdc171362f76eb215129cfcca43460140741ea06cee65a1f21b708afb6f9d5b0", + "0xa7aaec27246a3337911b0201f4c5b746e45780598004dac15d9d15e5682b4c688158adffdef7179abb654f686e4c6adc", + "0xa1128589258f1fbfa33341604c3cb07f2a30c651086f90dce63ae48b4f01782e27c3829de5102f847cde140374567c58", + "0xaaf2b149c1ca9352c94cc201125452b1ed7ca7c361ed022d626899426cb2d4cc915d76c58fa58b3ad4a6284a9ae1bc45", + "0xaaf5c71b18b27cd8fe1a9028027f2293f0753d400481655c0d88b081f150d0292fb9bd3e6acabb343a6afb4afdb103b5", + "0x947c0257d1fb29ecc26c4dc5eab977ebb47d698b48f9357ce8ff2d2ed461c5725228cc354a285d2331a60d20de09ff67", + "0xb73e996fa30f581699052ed06054c474ebdf3ae662c4dc6f889e827b8b6263df67aeff7f2c7f2919df319a99bdfdceb1", + "0xb696355d3f742dd1bf5f6fbb8eee234e74653131278861bf5a76db85768f0988a73084e1ae03c2100644a1fa86a49688", + "0xb0abca296a8898ac5897f61c50402bd96b59a7932de61b6e3c073d880d39fc8e109998c9dba666b774415edddcff1997", + "0xb7abe07643a82a7cb409ee4177616e4f91ec1cf733699bf24dec90da0617fe3b52622edec6e12f54897c4b288278e4f3", + "0x8a3fae76993edbc81d7b47f049279f4dd5c408133436605d934dee0eadde187d03e6483409713db122a2a412cd631647", + "0x82eb8e48becfdf06b2d1b93bf072c35df210cf64ed6086267033ad219bf130c55ee60718f28a0e1cad7bc0a39d940260", + "0xa88f783e32944a82ea1ea4206e52c4bcf9962b4232e3c3b45bd72932ee1082527bf80864ce82497e5a8e40f2a60962d0", + "0x830cf6b1e99430ae93a3f26fbfb92c741c895b017924dcd9e418c3dc4a5b21105850a8dd2536fa052667e508b90738f2", + "0x990dce4c2c6f44bb6870328fba6aa2a26b0b8b2d57bfb24acf398b1edc0f3790665275f650884bd438d5403973469fa2", + "0xa2e5b6232d81c94bcb7fed782e2d00ff70fc86a3abddbe4332cb0544b4e109ae9639a180ae4c1f416752ed668d918420", + "0xb4cdf7c2b3753c8d96d92eb3d5fa984fef5d346a76dc5016552069e3f110356b82e9585b9c2f5313c76ffaecef3d6fd8", + "0x83b23b87f91d8d602bff3a4aa1ead39fcc04b26cf113a9da6d2bd08ba7ea827f10b69a699c16911605b0126a9132140f", + "0x8aae7a2d9daa8a2b14f9168fe82933b35587a3e9ebf0f9c37bf1f8aa015f18fb116b7fba85a25c0b5e9f4b91ba1d350b", + "0x80d1163675145cc1fab9203d5581e4cd2bed26ad49f077a7927dec88814e0bed7912e6bbe6507613b8e393d5ee3be9be", + "0x93ddeb77b6a4c62f69b11cf36646ed089dcaa491590450456a525faf5659d810323b3effa0b908000887c20ac6b12c80", + "0x9406360a2b105c44c45ba440055e40da5c41f64057e6b35a3786526869b853472e615e6beb957b62698a2e8a93608e13", + "0x93bfc435ab9183d11e9ad17dac977a5b7e518db720e79a99072ce7e1b8fcb13a738806f414df5a3caa3e0b8a6ce38625", + "0x8a12402c2509053500e8456d8b77470f1bbb9785dd7995ebbbe32fd7171406c7ce7bd89a96d0f41dbc6194e8f7442f42", + "0xaab901e35bf17e6422722c52a9da8b7062d065169bf446ef0cbf8d68167a8b92dab57320c1470fee1f4fc6100269c6e2", + "0x8cad277d9e2ba086378190d33f1116ba40071d2cb78d41012ec605c23f13009e187d094d785012b9c55038ec96324001", + "0x85511c72e2894e75075436a163418279f660c417e1d7792edce5f95f2a52024d1b5677e2e150bf4339ad064f70420c60", + "0x85549ca8dcbe49d16d4b3e2b8a30495f16c0de35711978ada1e2d88ad28e80872fca3fb02deb951b8bcb01b6555492e4", + "0x8d379ab35194fe5edf98045a088db240a643509ddc2794c9900aa6b50535476daa92fd2b0a3d3d638c2069e535cd783b", + "0xb45cfebe529556b110392cb64059f4eb4d88aaf10f1000fdd986f7f140fdd878ce529c3c69dfd2c9d06f7b1e426e38f3", + "0xac009efd11f0c4cdd07dd4283a8181420a2ba6a4155b32c2fed6b9f913d98e057d0f5f85e6af82efc19eb4e2a97a82df", + "0xb2c2cdffa82f614e9cb5769b7c33c7d555e264e604e9b6138e19bcfc49284721180b0781ecbf321d7e60259174da9c3c", + "0x95789960f848797abbe1c66ef05d01d920228ca1f698130c7b1e6ca73bfda82cee672d30a9787688620554e8886554ee", + "0x98444018fa01b7273d3370eeb01adc8db902d5a69b9afc0aa9eadfeb43c4356863f19078d3c0d74e80f06ecf5a5223f4", + "0x87d20b058050542f497c6645de59b8310f6eeec53acbc084e38b85414c3ea3016da3da690853498bde1c14de1db6f391", + "0xa5c12b3a40e54bee82a315c503c1ce431309a862458030dde02376745ec1d6b9c1dbeea481ae6883425e9dae608e444e", + "0xb9daa3bf33f0a2979785067dcece83250e7bf6deb75bb1dbbab4af9e95ddfb3d38c288cbef3f80519a8916a77a43b56c", + "0xb682ec3118f71bde6c08f06ea53378ea404f8a1c4c273dd08989f2df39d6634f6463be1d172ac0e06f0fa19ac4a62366", + "0xa4f94fd51ecf9d2065177593970854d3dce745eebb2a6d49c573cbf64a586ae949ddfa60466aaef0c0afb22bd92e0b57", + "0x86cd5609efd570c51adbc606c1c63759c5f4f025fcbefab6bc3045b6ad2423628c68f5931ff56fdda985168ce993cc24", + "0x981192e31e62e45572f933e86cdd5b1d28b1790b255c491c79bd9bb4964359b0e5f94f2ae0e00ef7fe7891b5c3904932", + "0x9898f52b57472ebc7053f7bf7ab6695ce8df6213fc7f2d6f6ea68b5baad86ec1371a29304cae1baadf15083296958d27", + "0xb676c4a8a791ae00a2405a0c88b9544878749a7235d3a5a9f53a3f822e0c5c1b147a7f3f0fc228049dc46e87aa6b6368", + "0x9976e10beff544e5c1645c81a807739eff90449df58ffdd8d1aa45dd50b4c62f9370538b9855a00dd596480f38ebe7a5", + "0xa0e91404894187ec23c16d39d647ada912a2c4febfd050a1ea433c4bfdc1568b4e97a78a89ba643aca3e2782033c3c58", + "0x91a6ea9a80476ed137eb81558ff1d55b8581663cccd41db4fc286876226b6515fd38661557419e1e46b6a3bc9cda3741", + "0xb9e8a1e23c60335a37a16f8085f80178a17d5e055d87ffe8cf63c532af923e5a5a2d76cf078164fb577996683796caa6", + "0xad8e151d87a37e8df438d0a6a7c02c3f511143efb93fde8aef334d218cb25932baf9e97c2f36c633620a024a5626af3d", + "0x978f942f210e8a482015e6fdc35a4c967c67b66e6e2a17a05cc7a0f2163aed227b775d4352b0c3cca6cbf4bd5bafaf75", + "0xb5e2e3d8b2e871c07f5899e108e133f87479959b80cb8a103fbecde00ccdbfbd997540eef33079c5cc14b1c00c009fd1", + "0x88a164b3fefd36857f429ab10002243b053f5d386466dbb9e5135ed3c72dd369a5a25e5e2aaa11f25488535e044e2f12", + "0xa66091c0db4e7cf05a089ec2b9ff74744354d0196968201f5e201699144b52bb13b4e68e12502727163e6db96e3565f2", + "0x8e65aff8e37240461b7374c20bfd1d58b73a525c28994a98f723daed9486130b3189f8efe5c5efcd7f5390cc366038da", + "0x8b37c21dd7304c3aa366959ba8c77ea8b22164a67e136808b6f8e48604297f7429a6c6ecf67b1d09b8b7ec083eacd7e0", + "0xb689b1277ad050f53da91a702516a06d7406ff33a4714ea859b3b2b69f8d0aa8f983c7e039b19c0759a3815d841fa409", + "0xb17f7a0a182ed4937f88489e4c4e6163dcf49fd2ea4d9efbba8126c743bea951cd769752acd02e921774dc8ebcfae33b", + "0x8b7fab4f90be825ac5d782a438e55c0a86be1c314a5dbc3cc6ed60760a8a94ef296391f1f6363652200cce4c188dae67", + "0xab8410c4eaa2bb43b0dd271aa2836061bc95cb600b0be331dada76ddb46711ff7a4ad8c466cc1078b9f9131f0dc9d879", + "0x9194bd7b3cc218624459d51c4d6dbc13da5d3de313448f8175650fa4cfab7cc4afcda5427b6676c3c13897dc638b401e", + "0x980f61a0f01349acd8fc9fdc88fc2c5813610c07eecb6ab14af0845a980792a60dadf13bb4437b0169ae3eff8f5984ce", + "0xb783bee24acea9c99d16434195c6940cf01fc2db135e21f16acae45a509eca3af6b9232a8aa3a86f9715c5f6a85cb1c3", + "0xa3079931c4b90966d1faa948db847741878b5828bc60325f5ebe554dcab4adcc19ee8bce645e48a8f4a9413bb3c6a093", + "0x801f61ac9318f6e033a99071a46ae06ed249394638c19720831fff850226363a4ae8486dd00967746298ee9f1d65462f", + "0xb34dbbed4f3bb91f28285c40f64ce60c691737cc2b2d2be5c7d0210611cd58341bb5bda51bb642d3ee2d80882e642a13", + "0x8750af19abfb915e63c81542b13d84526a0c809179bbcc1cd8a52b29f3aba3ae0f7cf6f4f01790bf64ef7db01d8ee887", + "0xa6ea10000eb2dd4efc242ac95bc3b3873cdd882fbeb7c9538c87e3143a263ca3a2e192b2159316a625cfb5fb0b6cdcb3", + "0xaa40ca54bc758a6c64cb932924917581062e088b3ad43976b28f2e11d8a7dea73f1fb50aeaa0e70182bb2dc07d805bb9", + "0xa4779dfd25b5ec9d75dfb54a4bb030364899a5e75c1492403acb19f2adc782c7ac4daeb66d2f5aeb74135afe9f318e3f", + "0xb4551e2805d63ca453f4f38b1921ac87ff687e1d70575ad38f3469d6f0608ef76b7b1b98ae1e6b1e7d928773aaab6e3b", + "0x99490ee722f96aad2743b08dd37bfeb75a8c59efaee4c9b694eaa05eb8a6bb23861a4480544c7617d04d23fd5e2543b4", + "0x8a7050d964d295fff98ae30d77ce730a055719313457e773fcce94c4d71a9b7cf63db67e54a8aab20fb1335b0130b5d5", + "0x903144e6bbee0a4fec17ff80fef0d2103981140c3d41776cfb184ced17f480a687dd093f6b538584327e6142812e3cd5", + "0xa5b30f7c6939bdc24a84ae784add927fec798b5a5ee3dd156c652df020728dd6d43898be364cf5ee181725fbcffc0964", + "0xb43d97ec2bc66af92d921a5c5c20a03ef2be2bc2c9b345f46d8287409fcbfd88ebc49d4509d64468222cd1d2021bf236", + "0x82dc23c7f5086c9ac6b4566359bfb830d203544b0d8332a210775670f899cd9ff48b94bfeba40040c25664ebdd5cfad8", + "0x9294cd017fea581dabb73dcc8c619904d7e022b664b0a8502c9d30f3807668af279948e7e41030ae296d492225297e95", + "0x8d6c9dc636c8e884f9a4299e5cff06d044ebc94ad783a4b71788347ea4a336d4d048b8a9ecabae789e8fcdc459723dfb", + "0x801a80bc49e882ec81b04e37407713f033f7bdac79252dfa3dc8c5bd0229fcbd4019890e402cf843b9378df08f72ab84", + "0xb4313ca32569d973900f6196363c0b280ddfa1b47c88d019e5f399b805b444a777950fc21ae198fc23ece52674b94abf", + "0x96f06056fd255fdabf78986e315e7c4fdf5495cf850536b7976baa97a994cc6a99c34609c33a0f2facba5e6f1026dce6", + "0x983ed80220a5545ffd70ef5e6ac10217d82ec9cd8f9a27ee77a5ff4074092308c0e6396fc4e9932a77ddd474e61f8b55", + "0x872a059aa630af73c4abbd076e8b333a973ffc5bdecf5dcc0600b00162184213cb19d4f601795030033beb808d5810ce", + "0xb040f318d9d3b8833da854014a44296dbd6762dd17cab13f91987256c54353b7f0800547cb645a7cc231997454209fdd", + "0xa8c4731a555308e8ce0b8325eb7a4cbf6113d07e9f41932df04480b72628d313b941c7055f1cc2ac45c7353b56e96ca9", + "0x8c24031440b77637e045a52e5ea3f488926ab0b426148975edf066c40a4581beecc1bfb18fc4cf5f9f96dc6681b4bd28", + "0xb39254b475abf342f301298feaa17a4b3051f30ea23a18acf59e003e2704ac96fe40691f1da387913bdf7aee6389f9a8", + "0xa1dbf938b604ccc6d60881cc71f38df568aa02752aa44d123514154017503f6c1c335ae43e359f1487bc8934073cd9c1", + "0x8d52aa1be9f429ece0580498d8fe9fef46d4a11f49436a82b8927f9503dacc41245907f126594c1cd30701286f8c092c", + "0xb826f396486942c0326d16f30a01b00a682c30a75553dc6ac34fd5b3e96b13c33b94738f522eebaffb59ff8c571c76e9", + "0xaa89f51cbf6e6c3e2aa2806187b69ab3361c84e89f393f3ed284fe84db46fc3944aa44f8928e3964f9c1a1ec27048f68", + "0xa254df0efa4203fb92b42a1cd81ca955922e14bf408262c8f7cb7dc703da0ca2c71556bd2d05b22ce9a90ad77309833d", + "0x93263c507e4d5f4e5df88e85b3d85c46ea729fb542a718b196333e2d9fb8a2e62dc1347cf146466a54ba12d200ef09d9", + "0x922e3c4a84246d89a07aa3e90f02e04b2cea9bebc0e68b742156f702aed31b28c6dfa7ac936ea2fc2e029adf68361f98", + "0x9a00628eeeda4ccbed3ef7834149aec4c77aac1a14bc2491ba5d1a4a2c5d29afb82ceaa5aac1c5ce1e42cdcaf53e30ba", + "0xab3a88df36d703920f6648a295a70ffa5316c96044f39ff132937bfda768937cb6a479e9ba4a4e66b377f3a9996a88c4", + "0x966b11526ab099d550ab33c6a9667e5cfdedf255da17a80a519d09acd78d2ea24ec18bd1ea7d8d63cf0a408f1c1fe0b3", + "0xb5c21b9817dc32f3df9d9988aa3560e1e840d586d01cd596bc0f850ab416b6013cbf7dbfd05ac981f26014c74bd2d2b2", + "0x9040abef5e2523e7f139c9f744a64b98fea3a57952059ffe4d5ed77fa87068203c090ef4e7f52c88fb82ea8a6fdca33e", + "0xa0dcdaeb7d3f5d30d49c004c5f478818c470187f4b0b4856812dcd1b3a86de58a99acb8ceb44c6b80c3060cf967c43a4", + "0xb5f4be9a69e4a6719ea91104820df8623b6d1073e8ee4168de10a7e49c8babea772bcbc6b0908185e98d607e49cd3609", + "0x8634020a5a78650015763c06121c606d2dd7b324aa17387910513dd6480fb797df541fc15b70d269b2794ad190595084", + "0x9504d1d0fb31ff1926c89040c04d51fd1f5cddf9d7ca3d036e7fd17e7a0f767ef33cee1d8bf7e17e2bc40949e7630417", + "0x812c72846ef6d692cf11d8f8c3de8fa78cc287303315114492667b19c702cd24d462020f1276895df26e937c38f361f8", + "0x8c97aa5e9ef2aa9a1435ef9ddfe62e850f0360864ed5fb82bf9fef4ef04d8fb4f827dc078bc911ee275e4501edd6617c", + "0xac5f7af5e23c8e429aaa6b6825129922b59d25b4608f07b65f21388a9ac3aa89096712f320afe6d56e44e1f0d51a4eb9", + "0xa8c84d9a8593a0cb5be1e450960f59878a4e6b70da54a7613dfc25911b7cc9e6d789d39401b0a0d6471ab9dcdc707976", + "0x8c9d5fd89611392c0f085ffa4fa642a181f0b9b23593deb5e10fdd1642722ca75ef34a037e88a8d03f2888fe7461f27c", + "0x8c74b05f91fb95c85e7bd41f6d9a1e41e667e68f3d19b325c1f25df1767019919edab89b92af237896cbc4e6d6dc1854", + "0xa3caecb91640821f0b2c4981b23f2069df8d2b98ce026c1538bc096b292f5f956a5d52c1c8d6a8165a1608083ba6494b", + "0x8ae8e0c36f8b79a69176ff29855df45d0fcd9e4d1dbaed8899f8fcdece676e418ec034a6c161e2a894f0c834aaecbfd1", + "0xb88d18c67dc3b1b6ed60ee437c441c1ed14ecddebccf43683605716f30058b1aa4ba05ff10cd8171ee97d8f58d70c094", + "0x94f43d84dcdfd9cd19115c7d8e9c1e856828eafbfdec93b876cf0007e317e30b2ad951dbabc186aa6ef90fdee4d91990", + "0xb44e4723f41fc1d5b0057f371e3381ae02566590b3f964b6eb07b2104f66ff78410c407235fa98d04f635694f3baca09", + "0xaddd8390173d29ca0811534d389253831fed75fed135398617836b6e70767269eacb1560b39a58f02042ca3b97fe59c4", + "0x80bdbdacc0c358c7ea52aeacdc5f9ceb6928bcf6e7dee7c17d8ae3bf7c2372aa7a0372363888968fc0921aaf4776d5d0", + "0xa486e2b6f04f403f9e609d69dfb3cfb992af56ecad1683271df3e3faa3b86638b81e73b39978fb829ee7133d72901f2d", + "0xa19472da57457e10c6a6307895393ddaec8f523760d66937fe26a025817319e234eaf69756ffdf1b84c81733424a96d7", + "0xad6a195397cbc2d75171f5e82090441eed60bd1ba42c39ef565b8b5a8281b04400678625b1dc46d617f694a7652a8e5d", + "0x8f98e721c06cec432e2221f2e1b06bb1469d916a8d88d6973acf68d1e003441d00390dafcead8ecdbf9eae4509baf5aa", + "0x91d62a0f9d13c59adfe1376ed6d057eae244d13c6b3d99be49a49e0075cf20f4085cf127774644ac93615be9ac9e5db6", + "0xaf45dec199245e2b326a0d79c4899ed44b1c0219db42602a4a6184ace0ff831a3276297af28f92e8b008ba412318e33e", + "0x8754bde54e8d2d169e6a7d6f0eae6097bc0461c395192bd00dd6f105677ea56ab384c02553ea5eeac0a65adcb0df77ee", + "0xb676afd2f5afc37a314c943d496e31b4885efcbcc2061036e370a74cfde5642bb035622d78d693bfc3136fc036c7edb4", + "0xaab6ffe6cc234397cf1822e02912bc282dfb314e92fb5a9e10d0c34ee9b5856d4b76e166bc2bb6fcdd66aabea35ec4ef", + "0xada6e62f90ee6b852ec4b72b22367acac2896f0df2c105beda27096583ddbedddc710d171330569f111c6e44a5b57ae7", + "0x802139dd15241a6de663d9b810121bdd9cf11f7f8c8ca6de63f4f8e731409e40d1fd3558b4f619ed42ee54929dff1c7e", + "0xad8e70531cec21b4e6f55be1751c2d025bd2d7d8158269b054cfe57fa29252d052ce4478ec7db6ec705789e2118d63b3", + "0xa8e4a4271769480e1b33a28c87a150ecc0b48bfe8a15ae04152197881de4ce4b03453aefe574842424edbbe4173e1a3a", + "0xb98c65726296610cef16c5b58da5491acd33bd5c5c5af4d934a9840649ef85730fbce8018dee09ded14e278009ed094a", + "0x8e213a7861223287b860f040e5caaa563daa0b681e4e09ec79ad00cc459238e70bbeaf7486bbe182fc12650700034ec5", + "0xa2879f9e1a556cf89b9b5b3bd8646a8cce6b60bcbc8095df44637f66a2da5858eee2dc9091475a8f64bb5aff849389cd", + "0x8a17cdb4077b9b0bcf28b93294ac5ae4c8bba8839fce0f1012b53187ac008f9858b02925fbfc421f1123afcdbd8b7753", + "0x86fd9c11528aa43946e4415ff64a3ca6409ee6f807368c68997b18605da65e415ccd85ad913820d450cb386593de666d", + "0x8ed55923b963c3d85a91aca11c40ff9c6c7f1e2b9bc199d1a270e5fb16aa62dec0136e97866145ae9d58a493e8b1cbbb", + "0xae32af5b5d418668ae123c639b149e5eed602404e8516da4a61db944b537a3620545e8e3d38cf10cdaea980ab2f80973", + "0x95cb8d9e9d6762d78dde0ad73869ffaca904a7d763a378b8cc11a7933d3e7d1c8aec4271a079b1b00f8887ee5b1ea21f", + "0xb5ea20b42a3ca247f00ab5328c05f0cf194973d5f7271c66c41c5055b1ffdca136be179709e0c1de209fbe07b9820bf3", + "0x98682f7cce471c92a8d6d15fee4ddf4d43dd97c3e3811d2913618ecacc6440b737717c07736ae4558c910e11ee98104e", + "0xa67da2c7cbba48e929ca4e4b9a6299fe01ef79eff8cc5cd3fdbdc0721a68130e4079f30ae151a573a7dcca8ecf2e684e", + "0xa9981c9f9dcbb3b0f6996f664fb2acd7573189f203be37b2b714662aa273551396abfb1f612ccde4e4c8127a050dbe4b", + "0x92d55eff8da600f886da9bf68e8eecf482faa4b268f3f286b3b3e5cc91b19604081498d4905b201bb4ec68e32b5591d9", + "0x963e3f1728de9d719c86d390f3eb9c3f99d1928347fab0abf10dbb37d76b59ddb64d4734c977863a6cd03ffece5ca895", + "0x93480e2de83c921056b6d8628ac37cd5ef7555ba43b0308fc13386cb0515d42c12ecd06057137aa71a7931beaf90b9ce", + "0x8feae57ff0e6a162cc81c99f45c6187d268fc0bee8c2bffc92142ef76c253d201f0e932943cf2fa312982b281ce1066b", + "0x8f8f4bd4200fb87afcd743274480220d77571928000d4197410dbb75439d368df6a06d941a6152206371d2ca9cac99e4", + "0x8ee7f11e79af4478e0a70eb424fe8078237ad99ba6d7e6bf1a8d5e44e40abd22d404bd39b718ad6fdf4c6601f2a47665", + "0xa98acfcec612b574943195b9ba95bebcc9c0b945c9f6b3e8760b2a4635909246a9d73b0b095c27b4ecb3339704e389b7", + "0xb520efd19f65e81dc285031ea3593f8c5dad793e4426beb9196ab46e45346f265fd71e50adb0da657977c60ed5724128", + "0xa3d9d0b7415280ce4dfa2429d47b2b8e37604a5157280a72cc81d541ffe44612dbb3ef7d03693fc42a569169d5842dc3", + "0x8c29e2d0b33801f6d9a9c065a76c5cad1fb0a001506b970307e21765ee97c732a4cbf1d7c1b72d95e0ad340b3b075224", + "0x839e21f292892a6eb596b9b1e9c4bd7c22a6fe71d3d04487c77840028d48392c5cbe73140a4e742338e0c8475cd0c1ad", + "0x8bea5c68e7743998619185bb662e958f1b4d3ca81019d84ac43c88911aab3abe4ee9bcc73cb95aa3ae87c0138801bde3", + "0xb8f262d21a94604049e008ce03dc857848168e1efca4522acb0ccc827ffb37f545e1947843a356563a76bc6489605b66", + "0xa7bd0842b0bb38d9943b82aa883f36f4eb8a6e8a7790d4f87faf306608f51d250a19b73984f1156cef5dd2581664614b", + "0xa993e649bd953627a88a2539dac3a12ec7f37a4c65b01425d9d34edf7ee10a71aa98f65c9e013107f824faf8aee041a9", + "0x8e07eced75c67cb4d2ec01857f6ac1408482e6b31cb2faa249e8cf99f180575587df530c7782a7539b5221121ef48aa0", + "0xb2f4578f26c05ecb9e2669ca744eb19d4f737321ac7d04fafd18beb7866e0fec9dd063953ae1f077b44b9c6f54db1279", + "0xb6b3788a6c7bcaf467d19daf6ab884d549aa866970c05a9181f544ff190d043192c84fe437a75a30b78b425461cca062", + "0xa270684903c61544b85a7041e81f65e787e1c1e23e57538fa8a69836bed0ca1673861dd29f743a1280f2f38eddd3aa83", + "0xa9c2397c4773dcad2821266dadfd2401d013d9f35de6744f2ec201f3507700adb1e6ec4f5a453be4764da8bf68543f26", + "0x83a3025ed6fd5df9d98be32a74e10a0d9728b560942d33ba028536fb148fc34ae87e92be2df3e420a8dfec08da495982", + "0x90dc70c183a90bab988b4a85b7b921c8070af0e5f220364fe11afa0722990b2c971e1e98eef62d3287fedfd9411f1df7", + "0x82d940937a6c636224d04f8e2536f93dcf20dc97a5f188875ad76c21b804aef9af10839419b61143c1f88a695959a6b4", + "0x8017f9473ce49d498d6f168137e77e62fe553e5a51e75b519cf2cbd1ab9afdafad80fd5e6fd0860e640b0d78ca8ed947", + "0x80573a0ec049fe1f7b3013b2839e145cd87e07c0e43826a29ef8c92516f9a30896c2ffcf3ed77ed22a6cf3101b1789d5", + "0x953349abd2559f9824db07cec857ad54f1a05018f3076425f8dbae37f8d92a46af2c04ab7c8ec0250449541187696e98", + "0xab7bd2c4f05ee9a9f252c4e16a20993a12c535c3809d124bae24642616521a9768d3f19eceaf8524583f47ae1f527684", + "0x9883b77ee834ee0112ca2f366d2a6fc213e0cf454e061438c2901a5ba35b7378f64da8adf6a476eb1562991ef5b4a5bc", + "0x89291811db308637356dbf7ed22cf07bfce33eb977734ee346e8c15a231b35d8b4443574f3fa97a40867b3e23b0bbfa4", + "0x93d753849d7d9588d39e38217500b123a6b628a873876612d9f98b5d611f52c89c573432d2176752b5d1cc2d94899b8b", + "0xa45add3c4844db3b7a237295fc85fddc788ac1ec395a0524d2fc90a539571a247146aea4aa10eec30a95e9617c85b98d", + "0x90f94578842db7a4de672da1e483858ece5e466c73c12f725a0fc71f42ff880c9447a33fa9096839bee817536f2591e2", + "0xb2c1b6fb031bb30460f157356562b44b4de096a0a112eab4fb3cc500aad38bc770da1fc2e73caf687a0da5e8537049c0", + "0xafb15e15fd930929c0e3c66482068a5afe0c7b7f82e216a76c5eb1113625bfa0b045a52259d472284cfbaf4796c71456", + "0xad222a9a3d907713418c151b8793d5e37634354322068f8206b9d0da1a3f53b0004193713d23ec35990639a1b6c2e075", + "0xb44a128dce97e8c4b178cdbca0a5c1b3f6e164490fac0fd68dbfe0aafa89920bb4ea420a8527e06c80dd19c2f135e3ef", + "0x8596e993ef18b8d94e9c42a90cb7060affc586b8e9b526820d25124285de5590134e2e86592e9dc4dd45ccf5d578fa60", + "0xb71bb0ad138141ed506b2253e84110d2db97cc2d24a3fd0d096b0022d9f38f87aa74e2f505074632d64e90bcc491aa30", + "0x84841eafd357309de47b92ca5ec163dec094a2e5271bc65898c31932e0160bee165e4decb23af339cfe09c83e1cc5441", + "0x8a2915ee39a6fd4a240b98533d7690ef1773ce578ed1fb05ed414ebe36f7ef289fa46f41768df57190438c356331e329", + "0x90bb337165386f1990cbd8ed2e8321ef21bc18125b015b4da0c37e5fcc446b26005379ee4fad8ce9348ceb4ab49e82e2", + "0xb707b50ea2ab05c6d183671587f25fe29eef23fe569d731459a1ac111a0b83a2cd65b88242876b34aeead3b05a15d745", + "0xae1f159f79b7996315c4f9acce7e21a6ed59d4ef76331196fc86911fda3035edd5c11d568b105175a36c948d0263b382", + "0x922bc525bace05e5dff6b5cabde5469ddd2c1c601f7131abc04ecefdd35095e6ac015b1aec3c3b25c5dee8d139baf60d", + "0xa7b060405b2740f82db64683187b1bb89e5f40c8438663c7cbc8ef2513929fe5f92625667a7f2f599a72a96b1fc8f08a", + "0xb9dfe94a08651db5efefbb813269bce80d814e3089b80c0654491e438d820bf521f8a4a4477909344ba88f7683eebb43", + "0x841817a9729465743576950b6e8eea32ebf39cca99ace86c4792f9f35926e2d6830c52854a3b2eaeb61694e6845008bd", + "0x934128034bde8fc7b93b952aa56e0ed28b36cfa04cfa1f0d5b38266dd40beedff5e0bab86e4717b0fb56c56be2eae26b", + "0xaee9d64caf28596308782cd8f3cf819506daf3378f86157ff775e618596411adf94efd0e9542787ca942066f02cbd332", + "0x85871184db314411a49575fee088c52ed5dba4e916ee001ec24d90898a0154d9790a06aa8a707ca7a8b986c0293b8d89", + "0x8d3d87edcc0187a099c97b581a598d357a41ac152303bb27c849eb78e72e15cb97cf9a0468fc36f245c3e152c76bb7dd", + "0x900475d165dec18b99eb7b5f9e9ad1d2d4f632e55fdcc4c5ecd7775fed462990e6aaafe9c669f40508f9b15f00bda31f", + "0xa25b5954edd57e7811a0d18532043d975c7b44b80f65cd630935d7b16ada05f30fe2b7be7ae8a2f54c25957faf3f1950", + "0xa089019afa3a7a15f7e7874e73b6773c0a824e6d3379b4c928e173321fb165ad979a6be004d394c28d19d410b2655d3e", + "0xb28f46797dee0c538bd3de815df641a0ef718ad3e52b2764aec380d6905b38b50ad6f60d0f68e096ca39960ba7734355", + "0xb0ac155d3d05851b04104e6b459f1a68e9e155437c92421a7c0e4dd511ef89cf71dfa3cc920769492ee283a65ebf029e", + "0x813c69a810745580d43d5b5480f0ba81000fbef0071e6b655c7346bef5ed774e9214a7816d40eb1774a5bd033767a046", + "0xb176345ca75c64f10ec33daa0dcf1f282b66a862fcd3d8d66c913f9a02db4c9d283dadc02eff13aaab94bc932a42234e", + "0x92560f67e5b995db4a489bb86ee78b4aee0800143b3535ad557a53e9e08716bd0202d9f5714722c2a5e8310046e3f5b3", + "0x8adb427bad9cc15fc6c457a96a6750dda8c46d859c5f69bf0e7ab8fc0964430b33967fd47cf0675b6ba1757f91255e6e", + "0xb120f723b80389a025b2daa891b140b3d7b8d520ae2a6a313f6e3d365a217af73292dcb249dca1f414ec05e865e3cdc7", + "0xa61a5d261a8dfe5996c42ea0a5ae703a2adcfda80e86837074d868eee16f87d38da19596c48b55dbd7a7cbec1a9b4996", + "0x99dc921eacc6bb867c5825ad4c83bc4af9dd78a18b3d0e1a60ad493e3805b8fb9b7922b577da1adb3d805edfc128d51d", + "0x85455fa165a07282aaab4a5bfb88027f47b9532e4af8195c048515f88b0db7e80f42e7a385fd4944faaa7f2a6544ad17", + "0x96dff2d1c8a879d443fe576d46bcceaf5f4551d2e8aad9c1a30883637c91090de99ad5eec228eb5febf93911502d3cbb", + "0xa87eb7f439377fb26c6bfe779701f4aea78dd7980b452a386afec62905e75217a1996c5234853432a62ef8bab21c31c3", + "0xb598278293823e9ccb638232a799211173b906444376337fdf044d0227d28fcc4c5867e6ecb3200e59ca0b139e71cac9", + "0xaa6fe147edc95027654d68140f428ec53cede3552c5f49c09d18bc6f6ae8c739a63042eb7291d14d717a4e1f0778abcb", + "0xae8ee18913d328b2fba71efe65526d3ee9c81beda53cf776baec4019ea30212010758cbb5dc85ed6620ce04b189f01f2", + "0xae9fb686777e88dffdd42805fe4114aa0da1b350d92a27ff3f8a817fb25af1fcfc9a06155affe0273bf13caad16a5351", + "0x95d372ba3a2ee38371538f34aae91b4844488e273f70c02f1992370f89fc2343eff95692d52ce9f21206abbee4959958", + "0xb15260376f0a34ca2827ff53acd7eaaef94c9acc2f244b36500423069cb1cdaa57ac8dd74adb5b53d0fd4265fcbb28ea", + "0xb0ffce6a8059537ef6affdbbc300547ef86e00109289239b0c6930456c562b4ed97f2e523963af17736dd71b46c44ac7", + "0xb5499a1277d34f9892f7579731ff53f423f2ffffa9ea43a6e929df8c525e301396249a2324818a6a03daa0e71fcd47b3", + "0x98dbfb8e97a377a25605a7665d4d53e66146204d8953afda661ae506858c5cd77ff7f21f5f10232e06dbc37378638948", + "0x84177e27e6da0e900c51f17077f5991e0e61bff00ca62c1623e627c5aea1b743f86eef6d55b13219a1947515150bade6", + "0xb50407bb5c61b057ab8935df94fd43ca04870015705b4f30ceac85c1035db0eb8293babc3d40e513b6fb6792ecbc27a9", + "0x988699a16917514e37f41ab5c24f4835ed8a2ca85d99972646fcc47c7e2a83c2816011144a8968a119657c4cda78d517", + "0x920c43fdcb738239ad542cb6504ab34498bce892311c781971d7db4dec70e288676de4d8697024b108cfa8757fa74035", + "0xaaa106329aac882e8d46b523f126a86d3cee2d888035ce65c0be4eaae3e92fd862f6ac2da458a835539cccafaba9e626", + "0x96e4c1562d14b7556f3d3e8a1b34ea4addc5a8170e1df541dc344728bcb74cd1630eb7ba4c70e9c68fd23c5c5d5a729b", + "0xa616ac5016d4e68e03074273cd3df9693ee0ce3458e8758b117a5c1bc6306dd2c7fad96b1bb37219c57ac62c78ad7a3e", + "0x8db7d9b20abfb1445babd484ae9e38ff9153ac8492230d7591e14e3fca7388a5ca6ef7d92ed445c8943cf5263e4a6ad7", + "0x88464134221aa7134878eb10928f31c8bd752ab68c27c9061c1de3f145c85731a4b76acdc7e939b399b6e497f9e6c136", + "0xa5f7c794f70b7c191c835dded21d442b6514bab5e4d19b56f630b6a2f1a84a1d69102d7a0dcca256aab5882d3f30f3ca", + "0xb96b6f98b6817b5fa6b1b1044e2411bdf08bf3ffaa9f38915d59e1d2b9bed8b3d645eee322ee611102ce308be19dbc15", + "0x92c26ade2e57257f498ac4ff0672d60b7ea26dad3eb39ed9a265162ccd205c36b882dba3689758c675f29e20836b62d9", + "0x8379a0299e75774930577071d258e89e471951642b98e5e664c148af584d80df4caa4bd370174dae258848c306f44be5", + "0xa0e53beda02bd82bf3d24bd1b65b656238128e734b6c7a65e3e45d3658d934f909c86ca4c3f2d19e0ac3c7aae58b342e", + "0x8ca5ceaeaf139188afd48f9bf034d8baf77bbf9669791c7e56ebf783394d7fcdf2a25fa4bdfcddfde649aa0dc67ccccd", + "0xa8060e6448844e9db4e9fb4da1c04bcf88fda4542def5d223f62c161490cf1408a85b7c484341929c0f9ce2a1d63e84b", + "0xaf6e1a5ecf50b754bb9eb2723096c9e9a8e82c29e9dcaa8856ab70074430534c5395534e1c0ed9ce98f4b84d4082fa67", + "0x81c8dbbef98f1b561e531683d5ae0f9b27b7f45dc6b2f6d61119ca0d559bf4ceb676d320afc5aba1811eeef7547a59d8", + "0x85b46cd64d605c7090a2faf1a2aadf22403b3692b3de1d83e38b2de0108d90ac56be35b0dca92c7a41c4b179a3567268", + "0x8dd3cc3062ddbe17fd962c2452c2968c73739608f007ad81fa1788931c0e0dda65032f344a12249d743852eb1a6d52a9", + "0x8630f1707aea9c90937b915f1f3d9d7ba6bda6d7fdef7a40877a40c1ee52471fd888f84c2b2c30b125451b2834f90d3b", + "0xb4a747e0bd4e1e0357861184dacec6714b2b7e4ee52fa227724369334cf54861d2f61724a4666dae249aa967d8e3972f", + "0xa72de682e6f9490b808d58f34a0d67f25db393c6941f9342a375de9ca560e4c5825c83797d7df6ed812b71a25e582fff", + "0x8d5ea7d5c01f1f41fffe282a334262cc4c31b5dcf31f42cc31d6c8e37c9bd2f1620a45519dab71e108fe21211c275b6c", + "0x8ccdc7e3642c2894acbf9367f3e99c85963cea46dc5473d175339a2391be57dd8815feacadec766e13645971213b9eb8", + "0x858e9b5fc8c13b651ff8eb92324bdda281db4cf39f7e7bd0472908b3e50b761fa06687f3d46f4047643029dc3e0ceeaa", + "0xae20d36c70cd754128c07cbc18dcb8d58b17d7e83416e84964b71ccff9701f63d93b2b44ec3fddc13bbe42ebdd66221e", + "0x860dbf7013da7709e24b491de198cb2fa2ffd49a392a7714ad2ab69a656ca23f6eafa90d6fdc2aa04a70f2c056af2703", + "0x8f809e5119429840cb464ed0a1428762ba5e177a16c92581679d7a63f59e510fdc651c6cc84d11e3f663834fcafeafdd", + "0x8d8a8dce82c3c8ea7d1cb771865c618d1e3da2348e5d216c4cbbd0ac541107e19b8f8c826220ca631d6f0a329215a8d6", + "0x86e3115c895ae965b819e9161511540445e887815502562930cedc040b162ecb1e8bdc1b6705f74d52bf3e927bc6b057", + "0xb9833b81a14115865ca48c9c6a3855f985228e04cbc285f59bf163dca5e966d69579ea4dba530b1e53f20bd4dccdc919", + "0xa71f5801838a6dbb162aa6f0be7beea56fadac1a4bcd8113a0a74ab14fc470a03775908c76822d64eb52a79b35530c05", + "0xa77ab73ae94b6d3378884f57eee400eff4a2969aa26e76281f577a61257347de704794761ea1465dd22a6cc6304fbc4a", + "0xacd1c5df3c487c04cf27f002e81f2348a0119349b3691012526a7b0d3bf911cdd3accbc9883112ed2ba852145e57fe68", + "0x8a28515a48832ac9eaf8a3fb3ad0829c46c944b4cb28acbcdbca1d0d4c3c623a36cda53a29291b8f2e0ea8ee056b1dee", + "0x846bafca11a7f45b674237359b2966b7bf5161916a18cf69f3ec42c855792d967d3bf3f3799b72d008766206bb7a1aa3", + "0xb24b341675b1db9a72c3405bbe4a95ccdfd18fa96f876ec946ccb5108f73e8816019998218a036b005ef9a458e75aeb3", + "0xb99c267b4a09193f3448bc8c323e91ef5b97e23aeff227033fe5f00e19bab5583f6e5fcb472ec84f12b13a54d5c0e286", + "0xa088aa478dbe45973b04ecafbcbd7ee85c9a77f594046545cdb83697a0c2b01b22b1af0b97dd75d387bb889e17f17aa7", + "0xa0c6b0cdff2d69964134a014e36c3709d9e63f6463c5cd7b01b6f0be673731b202d577539d89dd57a888326da1df95af", + "0xb4e6dc4ef11b2b41794ece70a8968e56705199d183366759568b6fa845d2cae127486e926b5b27ae9118bb21d1682c1d", + "0xa007804353f174098f02540a57e96227232444d5ae0a24232c244647148b6c049848cbd2b50d0a25af3ca9164bfff8ee", + "0x873fb034cc39c9cee553ece908fbf315f62efbc412b9afdde6a1889326b7f6f813e050b0601ba9921688e958cb75942e", + "0xb5676c90f0106c40d8683299e59d564f505ec990230cb076caef3ae33f2021e6aa5c9b27bb8fead05fc076df034c28f5", + "0xb5a67fc4c5539ad1ddf946a063110f824f7f08d2e4d30762c9d437748c96c9147a88efc22260573803ab545c18b108f2", + "0x817ff2b748a949973a91b69b0ec38efbd945aeb26a176d19f0fb76e261c7526c759e6f5516f9ed34de6eb1ac7838c9cb", + "0x99b76bda3526a5d841e059010fdb14eb2fa035a7d10463373a062a98c3c1a123e2da0848421dd7546d776438fd05e304", + "0xaa0d363270f90d56bbee7ea577b0c358532bda36d9247af6c57d000044a97ba41e35bb0db438f4c94551c6350e4e0674", + "0xacdae205d05f54b9544be96c9032350511895ccf413dbbc56d1f03053185df22a6d5b7ffcc3fbe96c3e2ce898ccfa73e", + "0xb091c220a1de18d384f50dd071dca4648ca4e708162c52a60e2cedc0188e77c54639f75bce9a468a64b2549119c07ded", + "0x878676133e5c700b1d4844564fa92a9930badb5293d882aa25ee6721a9f2cfab02088c31d62cf1342ae3edaea99a1ea0", + "0x9756d0793e6aba3b4dff48100bb49a5ec08ec733f966cb438379b91caf52fc2a5930830ec3f49aa15a02c82c1914dc7a", + "0x9722f760184d3b2d67cb2cea7fa41b1ff920a63446006bd98c6347c03d224d2d8328fa20ccd057690093d284b9a80360", + "0xb5a68489de4f253715a67f0879437bfe8f4dfc4e655ca344848980e6153b1d728acde028bb66fd626fa72eedd46ff683", + "0xa8cfc900b34835d9fd3add08044636f69614eff9ae929eac616c39bd760fd275ee89bf24b0f275dd77a66e54fd6b94e5", + "0x89967479bebf70b2893cad993bf7236a9efe4042d4408022fdbb47788fabedcec27d3bba99db778fcde41e43887e45af", + "0x889235938fcec60275c2cf0f19d73a44d03877d817b60bb26f4cbce09db0afae86d42d6847b21f07b650af9b9381fa82", + "0xb7fc321fa94557d8fbdd9fff55ab5c8788764614c1300d5ef1024290b2dbb9216bce15cb125da541f47b411a2e7e3c2d", + "0xb11b0c4dc9477176b3cda6b17858dbd8c35a933ed31364801093f310af082cb5a61700f36851e94835c5d4625bf89e32", + "0x9874e54d2939ee0600f4194f183877c30da26d7515e9e268fea8d24a675dd2945d1565d9016b62b1baab875ac892f4d2", + "0x90df3a77280d6f1fa25a986309bba9d5b89c3cf13656c933069bc78e6c314058716b62eacfa7ab4aff43518b8b815698", + "0x962b08299a287d77f28d3609f39fd31bc0069f7d478de17539e61fcc517045050644b0307c917208b300ce5d32affcca", + "0xb30eedca41afb6f083442aaa00f2e4d5dc0fda58e66aaf0f44e93d4af5c4bf8ea22afec888cacbf3fae26d88e8d344cc", + "0x847747a22fab3fe3c8cd67f3f1d54440f0b34ce7b513225dc8eb4fa789d7d9f3577631c0890a3d251e782a78418fecfa", + "0x8d1ef3cb5836e4039b34ee4e1b4820128eb1e8540e350309e4b8fea80f3ae803d1f25f4b9c115482b324adf7c8178bc7", + "0x8f8a2b0b0f24f09920b58c76f7d99ec2eb2e780b5a66f2f30a9ed267dcaea0ec63b472282076c7bf8548211376c72f6e", + "0x831ee6dc8889bbf4d345eaeb2f425959c112d2190764abbbe33bc44e1d9698af87ff5a54d01fac00cfee5878dee7c0f6", + "0xa7eb2479ac80d0ee23f2648fd46c5e819ad3a1f4752b613607ae712961b300e37f98704880ac0a75f700f87d67853c7a", + "0xaa4d1b9cec62db549833000d51e83b930db21af1d37c250fdc15d97bc98de7a5af60dbf7268c8ec9c194d5d5ccda3c1d", + "0x87396fd7e78c4bcf270369c23bc533b7fb363ca50d67262937dab40c7f15bd8448a8ba42e93cf35fb8b22af76740d5e1", + "0xa958b2a9ffccbca13c0c408f41afcfc14d3c7a4d30ea496ce786927399baaf3514ff70970ef4b2a72740105b8a304509", + "0xa5963a9dd3fe5507e3453b3b8ed4b593a4d2ced75293aee21bfed7280283348d9e08bf8244c1fce459aa2470211d41ea", + "0x8b06ddc3359827558b2bb57caf78b3e5a319504f8047735fcc8ec0becf099c0104a60d4d86773e7b841eb5b6b3c0cc03", + "0x9437e7278283f6d4d1a53d976c3c2c85c5fe9b5aec7e29d54a5423e425b4be15400ed314f72e22e7c44ee4bacf0e681c", + "0xb56067ee26a485ed532c16ec622bb09135a36c29b0451949aa36fee0b0954d4bf012e30d7e3fc56e9f153616b19349bc", + "0xa5c72f7f5d9f5b35e789830a064a59c10175093a0ce17654da7048827d0b9709b443a947346b0e5d96b5ea89b8d7c575", + "0xa8318d01182d4c9af2847a29a6b947feef5795fc12e487a30001cc1ec482b48450c77af4837edfa1aedf69f0642c7e5e", + "0x82ea421c091552d3dafa7da161420cb5601b819e861dd2ba1a788c3d1b5e8fa75cc3f2b0db125dde8742eb45b335efa2", + "0x8679fd1c7771ea3b12006d4a972f4f2892e61f108107d4586f58ee7f2533d95d89b9695d369cdace665f19c6bc3bc85e", + "0xb5ab3e8adee4c950fce4d33a0e2f85d3d886e60a6e2f4454b57bc68725f0cf246372d863167482cce1ea10a7c67c3af2", + "0xa85696927075ec188979180326c689016a0dc7a2f14ae02ea27c39ef91418cd44177d3fca5752cf6b298fd75fa012e26", + "0xa44f87b7232f102cd092f86c952a88afb635484a984da90a41a57a3d883c9469064bf105b9026024090486b6c6baa939", + "0x866ac91a437db945bbfdc11fcee583f3669fa0a78a7cecf50fbfa6ed1026d63ad6125deba8291452bf0c04f2a50e5981", + "0xb780d5a1e278fd4eef6139982e093ceafea16cb71d930768dea07c9689369ff589d0c7f47d5821d75fe93b28c5f41575", + "0xb025d0046e643506e66642c2c6a5397a8117bbfe086cee4175ff8b7120e4f1e6794e1e3f6ec11390993cca26d207ae43", + "0xa04a22b6e28c959ab265c7f48cde42bb6a00832c6beb2595b5df2879080a9424890960417d7d7ceb013d697d0ebf7267", + "0x81de9c656ac27f54d60d0252e33aff4e9e9e9c3363a50740baf15a2b9061f730a51ae1704e8c4a626153cf66d47f19b1", + "0xa15fab90599df889df11fa60c752948b68fba54005491180dafb66c5775547976d0eef33945e55d4818653e0818c6f92", + "0xb06f9be44ddb103a72fa4ebc242c8ee1975fe9bf9ef7124afeda9967ff3db644dbf31440151b824869406851a90984a2", + "0x99abdfe6806ae5efa2d11577da17bd874d847c5f810460148bc045bcf38c4fd564917eacb6ed61bb9164ed58055cd684", + "0xac53231077f83f0ae5f25e52b70bb6105d561c0ba178040c11c3df8450c508ed5df34f067fdaacf716f90b4926f36df5", + "0x99e3f509af44fc8d4ebc693d3682db45fd282971659f142c1b9c61592573a008fc00502c6af296c59c2e3e43ed31ec7a", + "0x98f2f5819670aff9a344e1c401f9faf5db83f5c0953d3244cfa760762560e1c3a3c7692bb7107ea6eaf5247ac6fd7cc8", + "0xb5b9f90391cec935db8d2b142571650fcbb6f6eb65b89c9329e84b10bfa1c656026674d70280ade4ba87eeaf9333714d", + "0xb0696b77ca8a0cdbe86cad12f358880926906fb50e14f55b1afc1e08478ae6376215cbb79bc9035de2808c7cd2b13b85", + "0xa51d746833062a65fd458a48a390631d5d59e98e2230b80d8f852cfc57d77f05eefcfd3c395ade1e86d4a39c2141365c", + "0x812d67654319f4ef3c9e4a2d4f027a4cb7768f1ea3f5fdde8d1b79187a4b874ff9a5c70f15b7efa079c2dc69d1b9b1fe", + "0x968978b653c6416bf810f6c2ffa3d1abbefbd06f66b6686e9a4fdce3f869e0ab1e43cce14dc83786596761c100ae17e1", + "0x98e1e6ab562ca7743783b802faeb0a24f1341abfb9655f106920aef08964a3c0e8083e1acda7ae28fed7cdd5478decb6", + "0xa91c0b982a0a7085a103600edf99e9d0bee4c4e7db6d9f8f376c215c7d42476218462a3765f2928e12c3dd49d688e4fd", + "0x8a43395b3124fab9e2438635bf88952e8e3084dad7ecb3a9927f9af0e0887bce4707084043671fc98ad03621e40a149e", + "0xb0b37626143d4a8c6f5693d5f1fe871525b4dd946c4239cde032b91f60a4d7a930d7ba28959737550d71c4a870a3a3be", + "0xb01c74acae1715c19df08d5f4a10e0c19d1356264eb17938d97127bf57e09ced05ba30d0fc1a9f32d6cff8b0d5f91c9a", + "0xb4c2328eb8a5a673406faed8f0aebb8540d2791646e37ce46e0e382506570ca276eb6f8e166dbbf9e0a84064873473b9", + "0x85cb9f769a185e3538e4a4beda9a008694e1bf8dfeea9dc07c5c40a9ceb1d31fcb13cacfaa52849ba1894b5027cb8c30", + "0x8742f91cddc9a115ddc73982f980f750d82d3760f2d46ee4490d5b17c6c3bb57c7d4c7b8d6311b7b41e59464c009b6a5", + "0x948ef86d17128a061e1bdd3ea7fcc7348e3ec87ec35dc20a58dd757d5d18037fe5e052bb359e27ab4c2320d9a52a6a0b", + "0xa70f6a214097c271e0d2d95e30fce72d38c30a2f186271fdff0e38e005aff5baed53739b8c4f9501aa7f529c5cb2da59", + "0x892a7574cf6704ad75b346c95ae6f2668904f1218c35b89b07a0c2dbf3c62173c348f6fd9473926eef56a37c0f635c04", + "0x837e85a41f39b4ded1420aa8fc3be46a7adb99305e0928c6d7643b7c44434b72984cea08eb68f5f803661df0db78c87d", + "0x94e495329f2aab3eeb68f347961d1006e69d990095877a4dcc376546233adf29a14bf6b16a0c39aa477e15368e87014c", + "0x851860a8fdf76a97048396553262637dade27f1f63f926997e74c7c72b14b10293eae7824e8dedffad1aead57c124f79", + "0x90481017a250972055ab1cf45ff17d2469517f10f18c9d4ef79a9bdc97a49093289bbacfefa8a1e491bbb75388b34ac0", + "0x983db15f7463df28091c691608ca9c51095530fa6b1b7b5b099c612e673d29e16787cc9ae1c64370ba6560582ce623c0", + "0xa477dab41014c778a1b78a7ce5936b7b842124509424e3bfc02cc58878c841c45f9e04ccc58b4f2ff8231488fff0b627", + "0x868ebba1c85d1f2a3bf34c0ab18721ea725378b24f6b6785637ee4019e65d4850e051c8408fe94a995cc918c7b193089", + "0x93cbf4238a37ccd4c8654f01a96af809a7d5b81b9e1eab04be2f861d9d2470996fb67367e5bf9dcd602dc11a3e4cf185", + "0x83113f4e696030cca9fdc2efc96ba179cf26887c677f76cde13820940ad6891cb106bb5b436d6b0f8867f2fd03933f7d", + "0x90c709f4e3359a6d215d03f45ad5cf8067aedd4aab03512dd62229696485a41dcd64e2acce327fda390e0352152fce13", + "0x9945cfced107a36f3cf028ba04c653360afc5013858b9a12fac48802efcbc198c9baf3a7f9b23dfdd5036e88bc7274c8", + "0x832ae60192b47fc735a8ddeaf68314b16256c90ab68099f58e43073e249c6939895c544a02fa34e40805bc6b5db33461", + "0x8b12c335818b643c1d22cbc2869606cf64e7ae54a7713617fc4dd3b2f052ebd6b920ca59ba2e9c7aa8cf71bb4f40f9e8", + "0xa2033eb7a373931c65d66989644aa0892ac3778b9a811b2f413d8bf534e282c339717979f9aa742162abb3468c195f87", + "0xaba2b4c37dea36bed6d39323e5f628ab607699c66767f9bf24ef5df1bfcad00c2664123c0d8d5bd782f1e14a06f4c769", + "0xb71963777535b4d407286d08f6f55da8f50418486392a0018ee10f9ae007a377b8b8336f33386b0eb01c45695c3ed2da", + "0x88dc87826941340913b564a4f9b74985a311371c8e7b47881235d81c081f1682bef313c2f86561a038757fb7d6a1a8dc", + "0x869e13e3fcf91396750150f9dc9307460494c1d365f57893fd06fb8acf87ac7dddc24e4320d9cad0414119013ea739b8", + "0x92194e292303d32b91ae9cecb8d6367c8799c2d928b2e2846dab1b901371a4e522fc4089aad8f4ee676f0614ff8b19d7", + "0xaa589a3e512cb4f8589bc61e826a06d9f9cb9fdfd57cf5c8a5a63841435b0548e30a424ca3d9ef52bf82cc83c6cb1134", + "0x81802e0194bc351b9a5e7a0a47911d3a0a331b280cf1936c6cf86b839d3a4ab64e800a3fe80ea6c72c3751356005a38b", + "0x88e5e9e3c802314ddd21cb86f2014948b7618502a70321c1caf72401654e361aac6990a674239afa1f46698545614c93", + "0xabac1e0f85d5c3ff6d54ed94930c81716d0ac92be49e3d393bed858833f4796c2b80bf7c943e7110de7b2d148463bfbf", + "0xb7eb416004febd574aef281745464f93ef835fd65b77d460b6ad5d5a85a24b536b4dec800cfe80ae98489e54447e8bb6", + "0xb3fd8ed1c30e7c15b0bc0baf0d9d1ecad266bafb281cd4e37c55edc76c202fb1e4ea315a91a2848f40f481793ae35058", + "0x86ef674ddf4b7d303c68bbfb53db00b925ccbf11d7d775ca09e458f4ecd868ca828103e8e7cd9d99672a193e81b83923", + "0x95ef414e9f7e93f0aaaeb63cd84eb37fc059eb8b6eced2f01b24835b043b1afb3458069c45218da790c44de7246860c9", + "0x93ec8f84c20b7752bfc84bb88c11d5f76456136377272b9ac95d46c34fce6dcfc54c0e4f45186dd8df6e2f924f7726ab", + "0x95df5f3f677c03a238a76582d7cb22ed998b9f89aecf701475467616335c18e435283764fb733fb7099810fec35932ae", + "0x8cda640695c6bc1497d19b9edc5ff4ea94c1c135d86f573d744358758f6066c1458901f9367190dcd24432ae41684cf0", + "0xb19aedf5569435ff62019d71baa5e0a970c6d95fe4758081604f16b8e6120e6b557209cdea0ccd2efec6ff9e902d6ce6", + "0xb3041f21f07d52e6bd723068df610aa894dfdde88094897593e50c5694c23025e412ef87a9d16cadd1adbb1c6e89ced4", + "0xa7f8d6ab0a7beb4f8d1cfef6960ebdaa364239eca949b535607dee5caeff8e5dfc2a9cfb880cc4466780c696cff2c3a6", + "0x99a565b4796e2b990bfcb234772d93c5ffdbe10453b5aa94662272009a606ba6ea30cc0c3c26aa22982c1e90738418a5", + "0x90c54b55ff19157c1e679d8d4f7f0687a70a27d88f123179a973c62565adfcc9347cfe31f54539038cf2f34556c86870", + "0x8612f34bcd018d742202d77d7ce26cf9bc4e0d78e50ddf75250b9944583b2c6648f992b635ea13fdaae119764e7c28d5", + "0xa04fb38e5529bf9c76ec2b5e3a1ef3c6f9effb6246c7f67301cfed707356ba1bf774f2867c77a5805933f0c8ad0ec644", + "0xb4800e7b503da0164885d253135c3b989690794d145182572181995e6fa1989f3d0324993e871bbd5f48fadd869d8a18", + "0x9981cd4f28ae7b7dadf454fb3aec29746dc2e0ca3bd371b2a57cd2135a7d93559e02132528ccd2d305b639d7ac51613d", + "0xa3ceec012dd1fbad3ef9f9f1d6fe7618e13d4d59e3f50540d2a57010d651092979c75442ec8b38a1ab678505e30b710d", + "0x8b97b8654d067fb4319a6e4ee439fb8de0f22fd9db5569ba0935a02235cb4edd40a4740836c303ec2394c59a0b96308b", + "0xb3d1bf4410fec669a269622c3ce63282c9ac864620d7b46c9dfcec52d8e79b90c4c90a69c32763136a7f2d148493524e", + "0x93174eba1e03f879e44921084aa0ee3562e48c2be49085de96ed7621c768ff52324d14c8cc81f17d7ed50c38ffb2c964", + "0xaa2194cd0fb7aec3dac9a1bd8ea08be785926ed6812538be6d3c54218ea4b563646af1f5c5f95cb914f37edfae55137d", + "0x93f2c0dd59364f6061d3da189e04d6c64389a3563b062e8f969a982cd68cc55b4f38b21546c8a67c8df466ff4f61f9c5", + "0xaa7dd497cc949c10209c7010ba4ce8a1efd3cd806a849971e3e01716ea06a62e9d5e122ad1d2b8e5a535fae0a01a7761", + "0xad402424b2a32bca775a66aa087580d7a81f0867f293f1c35580b9e87ccc5a2bab00c29a50fd0d7bd711085ae2248965", + "0x96237843d8e29ac77fc6ebf4acc12946ad11697de8e5f152fe5776f2475b790226a7d156ac48968dd68b89512dc55943", + "0xa45c25cdbb9fc327cc49a1666988af9ab4c5f79cea751437d576793a01c3eeea4c962c05c0947852fe0e4c63e1c84771", + "0x93dcf834a614a6f5484cc4ba059e733ab5dcc54253229df65ff5ad57b447353ebbc930736a4c96322e264e65736948dc", + "0xb9a94f82a82c0c5a26f2c1d5381afec3645e8ee04c947dc3b7ad59a73018db1e9965ab3642f2bbf60f32c430b074fb22", + "0x94eab29b3524ccbe0c4b928e5fa5dd8f684074b332fcf301c634d11083653ffee4f7e92ddbcb87ed038024954ad1747b", + "0xb8dca5f679931d6abef0674bad0639aefad64c2b80572d646aaab17adf5ca1ab2ebeecd5a526cadc230bec92ed933fc2", + "0x944d394958e539251b475c4304f103a09f62448b7d8a8eaef2f58e7de4f6e2e657d58d5b38e8513474115f323f6ec601", + "0x8a5ae1f13d433962d05df79d049b28e63fe72688fc3e6660aa28e0876a860c3dbc5fc889d79f5c4dec4b3a34cdf89277", + "0xafa5278724998eced338bb5932ecf1043d2be5dd93f4d231d05d2ea05b4455f2ffdc0eadcb335dcace96dd8b2b4926fb", + "0xb91153a2f4647ae82fc4ee7396d2ca23270ec7f8884ce9eead7e9376270678edd42dd3d4d6c003dfc2dde9fd88cc6e7c", + "0xadc932f1c679bf7889cb1ff4a2d2897d7973483fa283979a0ea3640c80ed106ea0934c1961dd42d74b22504be49851f2", + "0xa82e90761fae684d1415cee0649bb031bcb325ae0b28f128ab8e3650bccedd302a70de1a341ca8decfdda76f3349cad0", + "0x8ae353188b4b98835f4ef0333cccb9e29e1ac3ec11d554bc96f5880c101cb3c84b8eefe72f2287b0812735339fe66cfa", + "0xb8b41135bb1a1ffb64afbd83e2189e755f2c350e1273cf47c38ae9b8c4800d831436a69458b8ef9fa8b95a148d8ec9fd", + "0x96f75a04d8752fa93dc1eaf85ad333cff4eeec902a345576139e16de3a88eeb71b6726224349bb9844065cc454d959e9", + "0xab82b05e3923ad4c26f5727c60dc0d23063c03f5a4fd8077da66aa87042cad1bd99586d4ab35aa5e4ce6f4da6fecf3c1", + "0xa50c83db91c26ef7bf1720d8815b41bd056b49fd99710943679a162ccf46097a7a24585750ece886e38eb4fdb866fa37", + "0xa719f667914a84f62350dcc6f4f30b9ab428eac6837b70318c3ac491c1e69d48af5e1656c021818f377d911fe947c113", + "0xa148807aafddfa0a5624c7cb9e42468219e4bdb9994ec36bc19b6e6d7c4a54d3a0763d13ca80624af48bbd96d73afca5", + "0xaa012f205daf22a03e9fb13a63783dda7666f788a237232598d02a4d4becec7a699ab493f78d722ce68519262924c708", + "0x97fc15fab5952c5a2d698fd6f7ad48aff1c8aa589f7d3b14285fea5e858c471cf72f09a892e814104fa2b27eb9771e73", + "0x8da8840236812667c4c51c8fc8ab96d20dae8e2025290b9cde0147570a03384370b0fcbe20339c6aff09cca5d63e726f", + "0xb477d85359a8e423fed73409f61417a806cb89c9a401967622aba32bf85b569e82bca1b3394c79e180114a0d60b97316", + "0xb3d6ee2ed1e4c5cf8ba2c3a4f329832e41c7fdcbcda8a3fcbe8f60967fdb1717665610b7c1ac65582534d269d762aa09", + "0xa0b3b30b1b830b8331ee19f96b4a4321a6b93a3395b95d3a895682c65ec6ea64774b878b93514eaf353f2e4be28617b8", + "0xa2b88e9617f4d30ef4e686d1932ad43cd555fadcb5102e51bea19e6fca649284ccf4debb37b5cb2090ef386fa5bf5327", + "0x8a4446f7e8463ea977a68d6217a9046ad4356d6fc1c18d46c5d2ab681ea977b8faff136d65abea6bbf8936369cb33117", + "0x91e7464bc56e03f436228104939ddd50caace5a38f68817bb2991e193b57adf6835152bbf3dbcdebf0382ac9823f60c9", + "0x961a441e6cdf8106c4f45e5b47190d35644faec701c9cfc41ced40cfdd1fa83752fd56c1ac49131a47f1970a8f825904", + "0x94b7b165cc71c2ae82976b8f03c035fb70e90028992b853aa902c0467b384c7bcf01d56166bec5def4453e4d0c907e52", + "0xa5d32cffabbf547f900026b34ef46f08075b7a244565f615370d2f04edf50b094c95088a4a139ce07caf55bcd99afa07", + "0xb4e06e73660745f75ab2f34d9f6d2675b58f80f911ab6dd4c5a6ce1095f9a2b50d86f6ff9a05394190bdf96af0827920", + "0xad3fd8f83c0103b29d41319209dffca201d2b98094362da08da3fd6ff0ba96796b49d6bed525c9adb96c2954858e7f48", + "0xb0c27430695f0fd20ae31e1ec621da090094f2203e17411db9384695ffcf5c7c6badf461ba49ba70164aacebd6f278ee", + "0xb9bc6e972fc3b532fd2b1eeafc4bceb77604885f32132af6a9a842fa2440df452f49ec0cd9d86da1180e8deb0723b260", + "0x9729e22d6104b0174c136a854920f542b384d375040adcebe36acc253bdb55845eb43e34dc5a7cc27d22c417973c24d0", + "0xa8b420b36d48786c9231d454468a6e855dd7f71dcfd095efc9855ee70dbece0f06ad277f7829c5813fc30524c3e40308", + "0x8757dff5499668c93fc5d9cea0a8db61817b8ed407200d623030b5849a913d12f8371b667cfde8d8082026eda7407e8c", + "0xb859ad747ca5af661fbd03a1a282df6e84c224ecea645bc2d4ba5e35fa06cbf047387319fca0cbc76b712398c0798968", + "0x8e3173c27875f1460297af0fa736c945dc842ec3e476a973d3d5f790bf183ad3ffe96ac13868c5101d8e299890791864", + "0xa9d725e2b92c878be42b5eecc2c3081c63c7231ccc7e2dee17ca6a4caaeae22788fab1f1465fcbd7fc236613fc2bae4c", + "0x86f6c4f04a354cb2470ef91914816fd740f8d5795ce7ff981f55a2634695fde5951bbae7a4bbc4c63747040f8644170a", + "0x851773cb26f320f0c3f252d95ea7e058ffcc795dd0dc35e459aa1b6b448238909230d809e82022e64b7fca5d40b8324c", + "0x8962641e0306220d9892fe2d452caa286301a3c465185757be7bce2d9b2c9beb3040280099606cc86773e43941fd3439", + "0x8beb6e08c440b0de5fb85251d39d9e72db4e556a2dfe3dae59efd8b359d08492064cebd8d8993254b43bde8bd67d969a", + "0xa7e047894466ffe3dec4ab8d5462f2b1d8ac0df006b1d2dd26caf499ea857d93a811cf42233f9e948c9cb903beec004c", + "0x92eedd95557a91691a5e2835170390ce2401e223da43b78615a804c49566f9d31cbb7f10c8a8390c4bdcf691544fdba9", + "0xa5e5b5d8fa65824e958bbae98d146b4b332f97ed50e0bc2c58851dc2c174ab71bcbb1ae015cd2955c26b368487dd862f", + "0x853a494eafb308175629d581ed04bed71bbc3af9ca4c0dc483d03d27c993a2bbd88cea47c2085a6928d166fe6938fb77", + "0x83f06b88d29afbfbe8f61811690322ac4fdd6abb9a23612162e7a2dd6bcbb5f14cee298ebebc1a382484f7346dc51e60", + "0x8c9cf05735ea5a0e563490bdc7ed29a4426643711c651e35c8551ca6f855c8458ae8f0933a022d0bb9a952edfed411f6", + "0xb906b48d807748a26cc2a8848455a76ce502261afe31f61777b71917bdf7de2fece419db636439478c7582058f626c29", + "0x97efe1fa7c9b25d8bea79d74b6cdcf88f63f1e865f54b58512a2e60428630b0b40b8b6af1b5f71df47520507548c3cad", + "0x8ef5ca6e753818906bb3fc71405928d8e4108854ef0ef01c1009071b353bc2852e771fcb619d5fea45590e8f61003d7f", + "0x8e4d901661e2913740d70ba4d0745df5e8c9c0a260149d9362beadc7e669630ba909ff0e8a6cc85c54d6b7435d0d351e", + "0xb7c6ba3bebbd9592967954e3a480ee8df1d9f5965f04e7d78a5415b645128deae7ddaf6ed507c8877bfca91ce078e529", + "0x840bedb0ad4e25acf6cd25dee4f98fea495b2312dc5cb7a8388c5ab00b2acb9cd25da08e9fbead145a3107972b1ccd5d", + "0xa8d4578dbafdb27f3911af59962d89e75dea74db55346720357790da677312c203107d9c7911535aa563446fde7d4c47", + "0x86d3b77f231bfa09251b7fd2ce09c27ac520ec35d783e912476f9a4863f83d269eb175790d6e735da9260293d707f8ee", + "0xb34909f1cc033232652da0c34051a769dc76adb1aee00674a59dc1b860f6e610974c3b4bb69a69ccc73e01f042431242", + "0x90799854d0cf34e1d91ff8e101bc7c5007423d34d2f3bd9adea2ecac57e83f3a65a506bb93d4caea49b29f6d18149957", + "0x8ef94cde29b037e19a1ce7bf4418ad3c95cd9457412796ea385750c19a6690f13a3bb5bb6a9ee81e7a40face1e0a8bca", + "0x97053d21ae8d75972fb37f6fe516c38c32ab162fb56b9f510f954858f4e3ef6ac8c3a9557ed3f41b7b6aef05fe97f931", + "0x90a9f9f0f40991f3bddc58b92d40382147db22cce50d092d4a05aad251b46b94e71ec9f7107a180243288059fcc5ce29", + "0xa14265b1344ac2921b0f890d13bcfc432e4f648ce403e261fce4d3bb32ffee9e2794c02830346054f998e82784c77040", + "0x91928402ae121e56a3e64cd6f390127e6e92fbfb1967ec6efa4f52f3e8058f1f41a0f4fe96b5bcc11641c1139e790b2b", + "0x921c8c92b6d40da6c5a7b592acc74fc0f577d93767b9aa4a1cd302a72dbf503a1ea5b2c29fa0d0359bff3b8f252246d1", + "0x93ae0ebe0e8e133fd80cf67a499047e30ec4c4660ccec9d49098717ef57721a030f423e00c5e74af4ff4acf014a10497", + "0x82c865e21905aebfe0496af1c6ac7e342b5f446a9edb4f7da0f2fb0340abfd8e6fc545da874459d9aabe6bce0dd9bfcb", + "0xaee3961d8d2687c0f134b9c28b920bdc4021d925fbe14323c84224a9fe161248789249fb85436a5891d0bbff42c2a3e9", + "0x91aee420b98b6949482b8ff4be996b97245b4e8f583a6e085226539074f42aa89818395efd1a6699735a569bfe19d623", + "0xa48eec22c192e495b01722d0016a54acc45ff837e2a95c4294ce81d5a4e43e0053a6f0ead8a4fb3ddd35faf6607275b0", + "0xa26e15937c11faa30ffa64817f035e294cab0e839f73d29de8a244ad039be4e221eb47ea08d9a4658b0152fc3caf6110", + "0xb84450f948aa7c8682fccb9cae84d8e3558adf2d0ca5fb81eb200415291158720f8f3470542ab5b88c6873ad08e7fa9a", + "0xa8e8ec27d0608d020169a85d6ecdb40eb402f006a3b97afe32cc01987721b3a68a92ec693aeb4d357e189e05fadf699e", + "0xac87cd535ef5699312cc26f86adb71baa0be42e858bd5a2d94ac05737dac63430691e29b9a30d2559ad581a172519b2c", + "0xa4481e67b524f8cddf2046625efd3d75efee6aab87ddd2c1b22835647e918157e5e924ac760db2195c86d326f3db1615", + "0x891f29ded231486ee826840c8895cb325f7e84a5a6d2eac246cb3573612cde274720233b1978318a57ed337a046330a6", + "0x906b6e750e6178289012769807d2598925d7e51c260c14497d8af978b1695990e3352e6e809a752f376597a68083870c", + "0xb7a056898ee1e46f7f29702fb39232f678ec173eccd170303b3b0a30c8d8cf1a5321384e3513e3b03bb742c238deaa54", + "0x8f2f035fd96c3a336354c89ec9b8222803bf42e95fb2412c28d4e75eec99c1d4d402501ccae17357b757db8bdb0bfeab", + "0x81228625ffcedf977fba9cfa13f6edead3985e2651d5974789c394a69401cd7face9e20ae6694be4c0d4bab5e99c61a8", + "0x885a83eae25e61439ad809567a2ab148583402e01cfdd77b0e37ab4038935425c64b4e0886949bf06438c35e80aa13f4", + "0x8926387f48752f6933899c48e038cf14e7941ec6a58bcc0a436614b396296a17aa53e6873803dd3041dae470bd493fcb", + "0x95d0d3fa061f4d856eca78a569aa132db14cede7646f97e2aceb6da0c8ea53195d3b7a566fe5ec8c41b95ecdd89a1c6b", + "0xa3c817f4062ed6aa94064ea695d76c1825f3bf77b310fe1db28b8bedc9aaacbf1019dbd128adfd53042fb943d863a2b7", + "0xaf1208417aa584052da309169854149ede38a3ad63c76cad6e43afb6f1a7b854edf8310a0b00088c039259cedf0f859b", + "0x8b713fc3196bad35dbf364089049ada5477e540d78d76a5f0a9df98f7ba4a0e65dd0644509c149f9b07887298bf74b04", + "0x89c09c43c5b733c4a417cd9ebc0795cc3348b72778d31828a9171427779a82ef023c1a4fcfcdc919ae25056f9c826fde", + "0xa0759c850ed320c8c874435e90ace6edfb8e7b3f2a09d942b8ad8339c508044ee2ee26c70f1b626ec49a77971433b6a8", + "0xb85cbc58d4fd52286e714ac4eaaa0b2743a1de06fa03ddf8f6668ec6f1d204acccce93b10620272afb8c0b49bc4b0a43", + "0x814e0a87384e159892a8d23036985fa3f489c53bce192e107bd2d64f57b1bf5ea0acc1ef46c7a42bbc5cd0924d92b4a0", + "0xaa6821da96ad89d7881b878e141076522f104ea9a5bbdd1fce9f641898f7d6232c518a87a0f666871d7e3165c26081e4", + "0xa9041d714bfc067b5427252186fa3557bad598fc0067dc8521aa9bc1ae298f6e96113db5ac9f6bade9a85d5a950c9755", + "0xb8669340f3064692625e1bf682d34fbe69a61689e3aa6d6a3e822c781d406b0300dba9c3f7b8152a8c2513f1310d4291", + "0xa78c53316ce768a1dc5968030bf4fc885f4029b1ddb6a5d84a61c85af686c73727f62823891edfcb6ccf4545de366cff", + "0xad1d3aa29ea28292ddd438c865e2b5d93f32cdf009e6d5f5dc726de996583925727e6348bf1c28c22dec0bd86aaf867f", + "0xae1447a2062e9e28af5f38aecc60fe150cd10c2edeaf2110034aa144f6235ed7fbce432a58805d4fe1f6b12652d6e1cd", + "0xa32146634332d3303934550705353c6d4fae5fa5985105bba35041e74cd71e2aad67b45da171221f6ed80f36bf6dffa3", + "0xa232e8286184196ea77427b53d8b52c44d758ecc42d22556529db3136379b4989dec61cff610cc6cf6700a450a847a94", + "0x8a72c7255125a736da52dff5f77e44c3de29f88fc05f5ff9227c69df296930caaa11446595e6bea3bd946baac5ef957c", + "0x9688a981a9457678067f629f8efa6b522e7318b529f88d37ef56c5bf8f1c34fb9bb3a918ab73caab82bf5abb0c03518b", + "0x88286f3eabd71115fc3b17a6bf6981340a81cf7e5f96b0a1a016d4ec8c18fb486d46c70919123d0c189a6f5d6ff29a1e", + "0xb535e701b40d793c02ac0d625ca91620d3f4a512aa9741f71389e58381008b2f93d597586d06213c4e103d67d0ddf6c5", + "0x80d0c9dd941e8d8d3700cc51a434a5aaa3308cf8ebfd14128ccfd258f826b27cc3cf5c3ad7851340393abb1eeab3a157", + "0x87049225fa2380d93f18d3d90cb0697a56b373b66d7f24ab209966aed8b55a2790194d5885399db29dd5b1f189eda64f", + "0xa52df158ce8670e0290551e8878d63dd33b4759d6f50e448e63fc7fe6ea99dddb6f180be5fc0fc3918ce54c05f80b356", + "0x8b2a728b39c465fb0f60b0c486e5dc8d5845ccec03d3dd93b393cedeeb3fe1b44518359f1ed55fc770a8f74bfeb9923d", + "0x91fc05419dba718fa4a910dcf256ebea356bbea00522d8d5ec3e7ba4271a26035aac15e8d9f707969df1d655d92dac55", + "0x97c8779ae80c24c1f82d5a714762d6ee81069224e39515e41d8a71c9310dc5d1c55cc92bc5c6a4bd391ae4c321d1d4d2", + "0xb5e5aedba378c4484e3a7a4ed41b75b0844f674261c2501497de6f91f7274b5a4c1be0e055f2e0c0cab843d891169fbf", + "0x8a26212f27211b295beea500abc8e9d430a8500d3a350cc62f895d39e8b4668aa638c17633804ba353010000165637ae", + "0x864a95118e5d394e00e99efebd505df0125525c9ebe165764c453b80ad3edc730feebde3d93850745dfd88a27bb8f20b", + "0xa092e0b78290e826cc1ae56afffdd08f7c10954f549a3ea6666f3db1b6cdaeb7df53db28dd2a92446342930fe60a27ce", + "0xa1720224c0626a081b6c637b2a6d37da85d9a82241e5efef3bc15699b02a69f6304e43d8ff3144d60c16e00225d6b39e", + "0xa7b3d098cebea9cf32e19c5195608182b6afe9d4af6b9df532c047eb7a941a971279b2ae6a4b80f2f9d9313a6d788ce3", + "0xa3d2451e6788944802c5077a778d7b7299dbb9d1612676bb6baae78f39976e0fd879493cc4a4d737b8174b472a456850", + "0x930121b73da844571b1411d56760e80923a4ee09917b3e9cff4d3dcb0bc27026ff2c4e2c44e7aca7d3f8383f129c7f9b", + "0xb4b0119d163ee00a2b74bdf188a5cdcf054daaa48c483b94bbb4d09ff615afb4a91347db6363bc7535e2af9054ec2214", + "0xa5846decee706780201095a8cdd48fbf3d3a2eac8d089a818e5e22c29457494bbfb4399323b067f3d2be2197c33dbd98", + "0x96ba600df10ee7af5a9df29c0ca31dbed275d647faf9c66c7342de927ceb25b5bdd852dd7aae0228b27897f90fdd5d62", + "0xb6ac51ddc98edd9fb9f54ef84bf372a041d58dfdf0dfdbdc4b08ddc1a7ba93ddbb1413dda3c1545a3fd7386c6b85975c", + "0xb35f3efd91a0723e0d486188ea9675a3462106470455118392d7610470b623caca2fa33829721c05fbeb0fabcf570bfc", + "0x87f49e85df5f8055714a8ce7adf37f6a278e64e76ed74c60abe3edfc3611ef5b0426d4c6da45e5f3b74d30be1dc6f539", + "0x8ff8bb06902a71b1e9177a77367318b2e3e0a88f5d74d6907ca9943f4f9f1ceb5f297132c2a025259d17a67e880d1bad", + "0x85eb6de6c70fe5c53ab0ab27aa0fec439f136c979c557d317337cafa6e6c5cb3169679c9169567dec5f6c72b3c057d83", + "0xac18715ed1080771d760cb7066c6328faf65d9b30517903f8a5cad8d66d5c6381156b521107d7cd75ebb8c30e250706c", + "0xb95b9eae4703727e4ac9ddf2ae675906487bb78905a5f9cba74a4cbfd118d96b7afb6ef3ed5edf14fd963b830d71338c", + "0xa3b47b52fda16b62b11c8aa4daa56b0b669c4d5c56a3059b7d063284d8a91f6fff9ccccab23d6ceb9650483b2d353039", + "0x96a95b3f327df94c85e92f2e406f1649ac621533c256b062738f3c3ee137059a735a3e6072247acf57b1b0d8c219bd7f", + "0xb19b33cc04570be94eae8e943d5bb17bb0c96e9de4ca84f9f41b37320a1a03d397d53747dc13275fef1b356de557214f", + "0xa1faa3dcb931dd91507f3f12a17c43f6627fa2bc5c71fbdd27548e091eaaaba262477949cd51290e81196bffb954a492", + "0xb060a16079dca1d28a1fb33cbc26f368630ee042d980ce305230005d5b9ab533a7a695281ab76e9214458303932d8bbc", + "0xb303783196a858fe45d67e0520c30576da605fd69964449c20009fbd5099cf1de52a32d326d7c3b864de07440195ef40", + "0xaa550a4c20d1003d137ffd8fbdc1196d09ad53cfa0e202302093a80fa3bbc4c9aff83f34f2151785cc1ce5f30255693b", + "0xa7f8585f45566a351058e10c6f1ff4a7ba24811f1482a47202f581525615ca770da93f2f58878788b45b92cb446ef4ec", + "0x8206f63a9a5b59bd68e64a843e68fcdf706f4c13bbfcdfa9928298e5b9251006ae0bbd80c715aa3c9957d2c0148b5059", + "0xac9490abe1241319658f1c2c645cfa01296f5d4106020c7894b7ba4a65cdd52f6c5401bd3b3cf1c9863e088cd8c9a16f", + "0x85dd6d9c80a1b58c24c4d2cb7590d33d2454f381f58e820979948e5831972360cde67bbd56e1860077ef5192fcacb904", + "0x8b0285944c676fe2519cb68da0973275fa29c0718d838d363ce46651b068d29f867cf9fe579ff8da0bb8b37d202bb23c", + "0x95147275da658d43a758b203b9ca1f1c1478853e9bf77b5218593142e2bd9c0bf46d2206ab64cef99295de6e9a268edc", + "0xb8efa187fdd3e1f46c15cd596e9567690c10e253b5beaa5be8074b6ea4e6d3d06e0f2b05323453239e419ae1e7128521", + "0x8340464f52c92e31806fd3e8e65f56e27194d1f6daa4a0f0b3831e8102aba16f88bb5a621633ddb7dd0342e1d2d12343", + "0x8615d87dcab85a78dc052f05a01e751176b756b5dc9985014347454ce5752f459dd6464e1c5aff36cb6c51b783fa2692", + "0x80c6e35c0d3defbe4d3968792724a23f0b8830dd2fac58663583a49339ea20f1812cc4140e3ee867c7e716177319bbbe", + "0xa7aa63dbfc201dde8f29bb6e23d7aa5020dd35bd18a0cc93c8a10c35d695913fe25b9e8cf9b5fd1899e9657b22bc8863", + "0x97c2a4ba80c4caba2e729a603d2faa0120915e3fe64cbb065f7ff33de5f877f1ec9461cf455e88ec9e9ded9393939dba", + "0xa54bd1419f0e2d2d87757870f37c476c7e3a13502f1ada82fd7394fd29f8a00c4986473d753034d0954a2550badbac0b", + "0x8d3e2bf900d0d2b9b46e6e2f37620f0cc90526dbbcfaad4e4a37ed53f39fdd23bd3a6f21aa7e800eaec937d9710dd6e3", + "0xa88d2b1c7802b2dc216c2b6532406c091bfb12f29121b9a82c1154470e250188413ddd3e79f7e009ea987a4c45b332e5", + "0x8c552c2101dfdc3f99c2da436115452e4d364eefe029b12946f05673c5ce1cfb48d39a579625849236dc6c8e7277dd30", + "0x8415c252d52a26a6400c3189c928a98559bf24162ecf3eef1d10e439269c31d854b0b4f6ec7a2430e3f11b5d77de78d6", + "0x8b38905bad93a8d42339dbdb5e510003c51fcaf05e04f88fd7083753353bc1c4c00a5dd4a67431cd4456d0669c7040e2", + "0xb1d0ed8862250d0f0d9ef9dcf0cd16d84313d1a795dc0c08e0b150dadf9ce73d32d735e04632b289cafa69a6ee75dc89", + "0x9434e18a5fb631b10edb02057f2d1fe16000ee55ada3c26a079c9fc3943e29d6de99e52829fe7b333e962270c712e51e", + "0xb1b9f3914007e6fca8ad3e7e848a1108988cb2318da36df24767d804e95d1272943fda948451135cc1b5052a3953b081", + "0x8c02947a76d7b6c0a700a83dfb971dc105bfe996e18c521445f036310914b349ab28e57571e36ae08d13a46fb01c2f43", + "0x893472fbc225f973a0ac6a0a0130b9cfb7ab6869dff80df71a62b1f6beb4afd069bbf35b4f327165bc31dff39e4fcaa4", + "0xa7c176c0903175f3540d62f9afee994d5d9bf37081e094644b22f017e94c515afefde7bb07f638342abef7de657f8848", + "0x860186c2b1d3b1e657729bc804275fb5f5ee89eaa60848fcabd3871289665ea9f0efc8a95792d884972bcfa2de96223b", + "0x865b38aea6386d0ac8f501a7d934e23d01dc50105324e354d4c4fa3cb1d4c29c26f4566df7b1a728e10cfaa9d24552e6", + "0xb4eea5548de6969dada658df604b5d9c49002e2258352838003e0fdf7b299d81fb025807a7f37cf5b547cebd7f2c1f93", + "0x8982de11ba68d63a649a3b296d4d56c71e3c3eec016db250d733ab7c3b9a620c09c5a5d0b64fd30d3bc03037ca4b17c9", + "0x84d8b8a10d67eda4716673167c360fc9b95717cf36ef1d5bc6f2ef5b9d2624f0e76c2a704d016adf03e775ea8e28d83a", + "0x834d03ebd51aff4d777714783e750b84c16cb6627f8311bd8ff17c3b97fc4a5bba57d6c8f6d74f195d3030bcb5f07612", + "0xaaf49e0def0c4d5f2c1e9c17b51e931d2f754b19e80070954980b6c160178349f6d3c8d4808801d362e77f41a0008918", + "0x8ef4115edec841854e89f2bbd11498dac7396bca35dda554290d3db1c459ffc17be671f4a46d29fa78cbd6064cc2da20", + "0x9641dc8a64f4acd38e343a3062787c48c312f1382f7e310ccea3e95e066ab6dc980f6ed90a633236a435e68bf6b3c625", + "0x8a84cfc2cbeb18a11dd6c2a0aebb3f6fd58a33bb4b26101e826add03748595022e816afac79a4e7c20b3805252839dca", + "0x9770782d729017659844421e1639ffcda66a2044df9e19769b90292df87dcb146b20c6b9141bb2302029d84a5310665d", + "0x98c7ec9696454868ac52799d1c098c15ec4e08b34884dda186ebfe87d32840b81fd3282295df141c91137faf4cc02da8", + "0xa3f6eb921247617292162dfc8eec5b830ddc294a0fb92f5b4828a541091ffdaff34c392c1d7168259d6204405d90ec72", + "0xb185f77a468f07a54222d968a95635234e74fc942485604909308a9028ed2753b15902b9134749f381f7cd6b89cc8c3d", + "0x867608a682d53bd691dbc92eeb460d1c300b362ca49c11a280f6768ccec217f1145f9d59fe50d994f715ce89d38a74e1", + "0xafaad630ad8827cd71aade80edf3d7aeb65a344878db12fa848759e6233f6fceca563aa437e506ea9e0f1e47b126d45b", + "0xa12afbc84e3441594aecf85d089423dd3bb8bb33a1a384ddf7cc14caa72284caaa56aa179c15e3140fd56bb532491a67", + "0x98757b0b5e5837ddc156a4a01ce78f33bb1fce51e0c1254ee9b6d3942268d0feb50b93edbf6aa88f9ea7b3c0309830d8", + "0x89573f4a4ae752e9f964e42bec77d28a41840c28e4bcdf86a98a131d0b85367b885077823a6f916972de6ac110821bd2", + "0xa17f2745052de5de9c059307308fc49f56cb5230e7a41cb7e14a61c9efa742ee14c41023ce90c7f2261adc71e31045f8", + "0x914b07c53a41c0d480083f41a61c10429ea42dafea9a0db93862d2269ff69c41db8b110b4768687b88089b5e095523cf", + "0xb380cc3e0d26370976fe891d24ea4eeb1b6be8cfce01f47fd68838a27190e644fd57b049d3aa0a9589370de20e276944", + "0x906385fdfad60feec79eb1c303e750c659ceb22d9c16a95faaae093daadd53e7aa039a45d57e20951d6e1ca0dc899ef2", + "0xb5211ceee31b194dba60b616bfd91536e71b9213a3aaaf5aaf9b2f4cbdeb05191861d78b97eec58e3c81abe4f0488c04", + "0x97878e9e38c2f69d697800e7a2f132fc4babaacf471c79c26a757f771606e55fe696ece68a3163a0ffeb2f72274cf214", + "0x959431c1f54c46500c05aaa9a2bc4230531dad97ae768fa92bb85436c0ecc6374cf20fb0ef82d122db116820a943b401", + "0xb69e5a1c6798f30d33e42cb8d124f025d2c77c993c4c7107a539aacddf44d8d4d2239e802ece32e60ee4dbfdce201bdb", + "0xa8b09e5e9f802ad273b2efa02bcbc3d4a65ac68510510b9400a08d75b47b31c6f61ffdb3704abf535a3d6d9362fc6244", + "0xa41ace7f1efa930564544af9aa7d42a9f50f8ba834badcaf64b0801aaed0f1616b295284e74ca00c29a1e10c3de68996", + "0xa8f2aa0bbbc19420a7c7cec3e8d4229129b4eb08fff814d959300cd7a017ddb6548c9a6efebad567d5a6fde679a6ac6a", + "0x9683da74490a2161252d671d0bc16eb07110f7af171a1080dc4d9e4684854336a44c022efe3074eb29958ae8a1a14ace", + "0x8ef44d78d10795050c161b36afa9ab2f2f004ccf50fdeef42fe9cdc72ebb15a09389ca72a00001cd6d9b1d7b3bb766c3", + "0xadca54f3b14fb18298098970b0267301b7312afb75894deea1b2afa3e85b7a3b4efac9971ab54c5cbecba2da9f18507e", + "0xac5d4528f06fdccfc1370d5c3d03ed982fed0861a93a3f6453aa64e99360b124926d1892faaf72d89459e663721dfa99", + "0x98aa1c801bd615b8cba728fa993021e181e0ad717ba01c0290e7355694155407083eb53cb70819c4775da39d33224db7", + "0x8b3aea4c7c2bfe1020de3261ec085d79c7bf8a7903b825d2c70ebbb84af197bcc54e3653c5373a2045c3021526b63b66", + "0xa29f3de4cb3d99afff1daf7d431b38a33a9804fedc41626618928ed059df6f6fe9f298a046b594ffee951ed4d4e1400f", + "0x803fd346be540c5242667c18ee41b26bc812456ab13ff117196ed69b90ee608c8cb6554396b64066a546ec87a71ed6a9", + "0xa9c18d81ffd029c0339c72c499bb51685392253b996b6eabd8b76f05c6191ed8444a1397d63b9923743661a319517f7e", + "0xa048d5c390d08f07161faac71c5994baf152c883b205f3bb10d3501709d6516ae54d491b486303a11b751857a31f0052", + "0x9156fb4803e40e28d8d57d928481a8de4373687288da44fe88c5676a8ae013ed1fcc09d56a31140bf74e7f767253810e", + "0x98e289c725b18e0085afdfaf2acbc674dae7b0a2ecc2537a7d0b87e20eb785404ab05973a787f0495d2adb3e5565c09b", + "0x8a7237b249325bd67cdc1f9fb278710069033c304afbf270b7ea24dbc10c8eabe559a484d3edc733c77b4384932deb41", + "0x9056f2e5b02e5c2e04a69fa1323bbf1859d143761268d18e74632e43800a2a9c76fd681e924a19bc141de0e128d3e462", + "0xb9f2bf9e4e7263014296a82b9ecbb05d3f1efa4b2e675e3b38d3eace59da06a89c859256e1b77847886d6aa15f98f649", + "0x83b22949cca19030289bbf7cd2a0d8b84e1d468e78bc85271a6753241b89122627632723bc293cf904a5eb2b5dc6c3ae", + "0xa919aaf35dd0116168d2ee845122026416bec9633df113fbd913d8db5996221e234f98470d029a8ff182825b59fda20a", + "0x91726901f49d32b41afa15219073842278f60dcee223640903d871e318a1c2b541136b7b38a7b2ab7d31e4242fc29674", + "0x942b77666545bc9a858d36cfe857ab1a787c9528f4a0b87918a06bf510793264dcafd12ae6bd3ee300179dab7f40aed0", + "0x80adc1f2f9c47a96d416e44fcba41628abc0fae1f88f6a26aea4648419ab726f7fcc2187c7d5145e3d8f5a75c03937f4", + "0x8041e0f66ba9dcee01e336dd4d16ae5e4e1618512fc147cc8230003aa2940848162dc2187d4130bf550dc1f3559849d4", + "0x999e8adc51bab54386af1c5e8822986ad1b7ecaf1f8a4c2baa5bb2fe9d10710e49545c5a8bd89ed0e61a3d73a908e5ef", + "0x89272ffd39b6e9f99fafdd58bd9dc00f66f26a1d36b38a1ac6215e3546d966739eecda7fc236335479207cef95cce484", + "0xb8e0b7532af13f15dc04a0eb4ea8abd67e58f1b1c6ad2e70c0ffa04a5c18ec2018b5d7f4be2f9f86db5e0b3986f639d9", + "0xb96bd11b0f6ead4abd5fe1e4c6e995da7583b901afd01cc05e87d04663fb997997d6d39dd9fb067c62cb1b1cbb67516f", + "0x94ab08914088b973e8dbd5685decb95f3bf9e7e4700d50a05dbf5aaac9aea4be2c10c83096c02252e9238ceea1351d05", + "0xa188de419b062af21275d976494c131ba18d2b2ead8bdbfa38a777832448e64d4d9725c6a1d530ffb6513f18d5b68d9d", + "0x8f73c8c118fa25c76a4ec5611351953c491452743056a819c8c82ba4737a37d88da0b55f837e7239a5f46d2c05a1bbba", + "0x894a44769e0be1c26648b0d89c4c9f46dbdeb3a71b90c493093bee372bb9f2d3f319850fd886d51f4f58db0de5641742", + "0x87d239923b0db024a8d9b0281111d47b0761d81c50652268b074efa3ea70d793e30f874a91ce33a4acecd0cf38c01951", + "0xb1b48b75a97f9fc2dc9530dc69f6268829dd0ddd574516e7eb1b9f5c3a90058889a7bcf3d378738e6d4b02f5fbfa44db", + "0x83e3ee9526ffcb60c6e75b75550fc017912ec0daf96d0a0d5f58c1b229cce90c684ac7c3e17fb998def8e7e2e155d750", + "0xb9b7bba579e474b0abdc7775ff5f84c9f117c6ca17788cf5a5f01b2c35a14aa39036031c8d799fec2cfb371d9f7471fd", + "0x90d7faf4891fbc368a32f575dfb69f13e37161ab4f63a7139be103285a49490c2851a907f8d36e09e7d1a190dddbc6cd", + "0x968c8b9affe18fc34a4e21f0d8c5518341c566099e6b45b8721c9912bab3693c9cc343406fe90279692a1eef2a3f7311", + "0x8735baaf4704207550f77df73fb701d9a63329993a8cb355ccc0d80daf950145f37e9b4b22be2aba29898e974f9fd552", + "0x90f52b2dccf525b9191d836b205ffe966d9a94f6c5800f8f51f51f6c822619e5abdf1257ee523597858032d2e21014ec", + "0x831209f8f5257bb3eb452d3ee643d5f063299f8e4bfea91b47fc27453ac49fd0ba3cf9d493c24f2ca10d3c06d7c51cd6", + "0xa5a4db4571f69b0f60fb3e63af37c3c2f99b2add4fc0e5baf1a22de24f456e6146c8dc66a2ecaafeb71dce970083cd68", + "0xb63da69108fad437e48bd5c4fc6f7a06c4274afc904b77e3993db4575d3275fce6cffa1246de1346c10a617074b57c07", + "0xa449448d4156b6b701b1fa6e0fe334d7d5dd758432a0f91d785b4d45fb8a78e29d42631bc22aaa4ea26f8669e531fed7", + "0xaabe43de1350b6831ef03b0eef52c49ffb0ccd6189cce6f87f97c57a510ac0440806700ce2902e2e0b7a57b851405845", + "0x91015f144fe12d5d0b0808c61fa03efe0249058e1829bb18770242f5fb3811e4c8b57ff9cb43deccfc70552e4993892f", + "0x8e9c570811ce44133ce3e0a208053acb2493ef18aade57c319276ad532578a60d939ed0bde92f98b0e6a8d8aabd60111", + "0x8b21839b5dc1c9a38515c1076b45cedec245d1c185c0faac1d3d317f71f1bfebba57c2559bcdb413d9d7f0a2b07f3563", + "0x90413bbd162be1b711e9355d83769e6aac52fdfa74802d628ff009325aa174c68f5329ddd552ef93e8fdcb9b03b34af3", + "0x8b6b02e3f9dd1031ebd3df9a30432a3c86e64306062ef00a6d1243620d0cb66dc76f8d0d412eceff877ff8768c2696ce", + "0x9894b41d9fc715f8f6addace65451f41dc5ce7b983dd8cb33757b4d7259bef12f144e0077d0b662aa847d5a45f33c563", + "0xa353a9740f6188d73aa4175a6c5f97898a05ed7aae9d2a365f15b91dfa7c28b921fdef0a32d90b6fb82718b33d3ddb8d", + "0x984eab8faed87c403c9979f2d2340fb090cc26d00cb4092aeb187c3f4ee1df3f57cb8363f7764073188790b16dfc464b", + "0xa5c5ae0ba435fb7f3ddd5ad962358da326239ff236fc3b51bd22e88296236b109951cee1b98f444302badc58d1b5bfbe", + "0x880be1006b0156f2788813432f450f613d235f41aba52a6000d2ad310408ad73d86b79f6081aef1e8c51010d404ba670", + "0x937da751aae68f865c7a33fa38d718f20e2a1c65cb18c8e08f8441f0cdc77662789d2793794dd0a427cad30cd0b33f42", + "0x9496fde66c834ff86f205897db12bbf9a9bb78d9ba8b5fb539cd0a2c927cc6b4120c017b0a652750b45edbe5f650e5dd", + "0x97a6f409ffeb593e149307a14bc47befb632412d70565c5f13d6b7d032acd2e3ed0f7b6af701b387f11d69ee4a8094d7", + "0x97ed94934263dc0260f4f7513745ed3483cdddb9adb85dc33193c3a8b4d52affaf1ded23b59c34651afbffe80d40dc36", + "0xb2b26378d44f916bcf999db218b9892e06de8075f205c7dafd6d37a252185c2d1b58e2e809c717963d25627e31f068e4", + "0xb8f9fa1fb45fb19a45223f7be06c37d3a3501dd227c3e15999d1c34b605f888123026590697d0ae24d6c421df8112520", + "0x997aa71e3b2e8c780f6855e94453c682bee1356b5ce804619ef14834475511105b1e4d01470fe4e2215dc72182d9909c", + "0xac2cb2a7cf55aaf990cfada0218453853047e813d3f51f5a623d09f4714da79de6592671358a5edf938a67f905b6cb5b", + "0x8d8340d0c3081cd30d34f3ff6191e1ff6ad7994b4ebac19e5936f1157ca84e1813228b7605ee226366d6bab1e2bf62a2", + "0x9693b17669086003cb46c75fed26ea83914a54901a145e18c799a777db1df9c9ca6b2ea3ee91e7b0ab848dc89cf77f19", + "0xa6b6b2a6cd8c4922d78c8ba379373b375d66ac6ea04b830a23d5a496cf714a9439d81c865da92d52600aa4e2e43afcf1", + "0x89cb665020abc3f5e11a03c7ba5ec9d890fa9ed2630f1443a8e45a28c32786ed980b5343ffffaea60eeff5b313bc0d66", + "0xb37b989106594221bc6cf33a1a83c3e65ecdef279e90333a9e105b8139dc28384bb2277edd4b77c9e59d15e6afe074c5", + "0x98ce5aee5918d18b2326b30c1ba41669cce20bc7a1d1b585363305fbdea66055164a7ac398ca0f0e670291a3061022eb", + "0xb57f472d5f34beb4cf430d7c0f8ac5bd1c0621a284633ed36e6f7804bc2b7847f54b469c7ea163a436510d9e3b32f97e", + "0xae673a6579dbf0504c8fd0c8fc0252d2f7ae8da615a06f4d215c2f8a8f516201f24e5cc42967630c252905e5dbbd6377", + "0x97c1501835a31091a5a83f0546e01c85ee847a0ca52fb3cc0653f6a826e13d25ddc623a5dea139108f7270a1fd7043ea", + "0x9376ee667f3834f6c0da4324fdcca5c04712e0649877ee19da79a2d23be24640c38758fce562470ce2134ca34148ffe3", + "0x818af89c40379a10074cfaba6d5968ecf667f1a68a7edaa18e8977ccb34e0829f237c5634fbd079e7f22928b277f1096", + "0xb8e0af0be0a252b28df25d4a509f31878bcddf702af0e5553393c3dfd4a1f1247ad8dc2668bc8dedc9b41f6ad8e71b15", + "0x811667ffb60bc4316e44bd04573503f5b4dc44d1ec824393a699c950e5fa085b146537ddd6a08a3fede7700396a0df7d", + "0xad834cbf850b2f61ce799c4a0f8ab0c57039d4e1113933c50b0c00175171aadee84894d1376cf325bfd434c3deb44315", + "0xa8b7dfcdb40373ba4d55e751ccfb9070554434df9e359fc165284ee3dc35db6fb6055657ecf5a9e9b7b8e2e1abea4375", + "0xb56a5b9fd41c9d3f65532aa58bf71a38fcf07782e1ae0084dc537862fa02e6d66658b19d6f71c39cd5dbfac418da1837", + "0xa935af5ed224b9533b41a7e79f872f6851591da9e9d906050ccd1b2c772a1d6d010c5fc7160c4f8cd7d3aa14c3bcdc26", + "0xa81e580fc98692567b28323fc746f70c3139d989fb6aabf3529504d42d0620f05327e3385c2bd5faea010d60dd5c8bdf", + "0xa8b352054cdcde8ddb24989329a249b71498a5593a13edad1e913c795dcad3d24789abca9c7ed1d57efcc9e3156da479", + "0xb0de8a2bd7f93284b2bc700e442f52ada16a22ad8d86329591547411c23fff0333b2ab0c9edf82bf7903ebf69916eed1", + "0x843e9781b653d1a427f3534b2e86add49d308ca247546f9fcf565f9e08df921e4d969e1b8ed83f3f849e98c0f63e39be", + "0x84a4098c5dca9f73e827d44025473096101affd7193c40a0307e3215e850e753e9a08e6e74a442d57626ff26df77faac", + "0xb463eaaa2f3315b511c22a97fad353014d840a6a95fe0d457d0677e63e571407d7f5268f8775381a5e7adc3b4163eb88", + "0xad0417edaa16cfddc288eef4173aa7057ca4f81e815541ac588ef5f24b98d56fed6845deb6ae1a9740a28bb1cd8780a7", + "0x9271963b8fb2288a96e07eac13c0543ec41abdc6d978bd7c44ae08251ea49994412b542c77c8208cd71fd8e7852d4a70", + "0x8b68b6db9044d8bafc155d69e0daba95cd59d6afebb085791e999afed4f33a2479c633d31d534ff767b8cd433d591a23", + "0xa6a06a0e433e385437d9996ce823abda9848754aa9cdd25ec8701af35c9ec15df999825669bbc2e17cedb597a96e8eeb", + "0x94d414bff8b6b8597634b77a77d1060db8e1af0d0ddfb737a9bf1c66c8430e93a425510af2464bce4a7b29bc66cf325b", + "0xb6514049562af1c6fb7d0e8df6987b020f0b7a6e721f4862e36b1ba0e19af19414ede04b346be22d348b50875803d1bf", + "0xa42c7fb34f2fbee8aaccd1d86672d0acdf4e6bb083ff0456512d7e1e43be041cc0924322fcd986e6e1bce5d5ecce6f92", + "0x867cbdd169a52440ae0a75d33a28c7d00aa92b4b65aaac5e62aa53a8fc367c08ab8828cc8fa18b6e7d1f908d158e3382", + "0xa6fe0b768fff3e4a6153e59a7b7508eb2ee8165eaf5274d41ac2812bd4563c4ca2b132f0e27ea2f1c98759cc3589b61c", + "0xb3eb1dba43d10b9e17ffec8def053fc96f9883bacb49330a089a0ca5b9ab0182e8b5111ad4aa55c1ce1b6f4afa5c70a3", + "0xa1531351098bdfcda566ff4d811301c0305626c77f954a38420c490e7c684f517eb1a4e4bd2c3904a10bac889cba314a", + "0x92278d106ad2f27eacdb86bdb1faa0a07a93765bb79dcff191873c52253af83480114b2299ffe5324f9c31d0abbdbbd1", + "0x8900ba95a90c447fb6fa1f528af3d7a378aec25feb0620516b6b97e54b328fc31af42e46a8ad5e6e3029d83a6f2bbe5f", + "0x86053d481179c1ac910d5e7b9a5de82794b442f20e854583512ce1f9c3f09e71d1bf97d6700fe776debfe1527ab97a82", + "0xa32a60de492fc4340336416bccbd2591b5e414fca0aead82281212e24490acc01747537b3da783684e27aeb987245cc8", + "0x9820fe8e0338f21797143f368177e3669a1f3894b40ae9fa3b353125f7c8e85cc424dcf89878f2c7667f65db3b1e4165", + "0x934d64711b4348ac5e1395cc6a3215e5643b540f591380d254165486b0ec2a1d0d21c7d2c6310f9e0eed3d08ecf4b57c", + "0xb9fd32d589432eddcb66dc30ad78981360915854cc44b2afeb826b5d48a08e377dc91be66f5bf1e783d1a8bb320f7ccb", + "0x98c972cf01efff4fc2e485b47572e2d8dde22461d127ef401b71a111b0603203971e3cde40912643affd7341cd27e57a", + "0x8db6c1620760063edabd376f4399b6e1355462e04f5c81cdcb3989fdc00f9a466bc85ed899e886c89c149adad69edbad", + "0xad7b7fda0aa6e2aa66a27235ac5cc680aa04b85dce329fc4be84f75c9c961120a3d9e446aa44539aaac8ea203eecb4eb", + "0x8ccb01eaf41d816ce69ebd57754859e263530915e775c4e7d9dac37b2457a9099b9ae9b4c6cb09eb5ff246e3c9320c59", + "0xb895b83b5f7ca46e02697dbaa6157df6c7571864c83e504a8c77d965bc2ba97bf9353a71c56a020df64498bd40e30b21", + "0x8018c07a81c522fbc25f2cb14f2321c61b98bd8962ed8eb7d5823dbe5d1958a5ec2fb5622fd0868e991bcb6cae016ea1", + "0x95b16364e94d01b3664812264d7185032722a4afc23bdd33bc16ae87ee61816c741657c37138d9312cebfb5fcfbb3b2d", + "0x94a709209990a8b09bfb4b9581ab471aae3a29526eae861108b28edb84aab6d28f1d7a25dddd8150b70af34bee4ca2e4", + "0xae06c80839c5a13269b984ff4d8a5938c6f4d8d647b1b1daa8cf7f6145340b76a286cd615ec251a65501e6290162da50", + "0x875cbd0694eeb90d3567da9dc7f570d97b02bd9cf17bfa011efdd48f1d580608a3213bff4006603b8b4079fa66bded10", + "0xb27f88c455f025e1cd902097d6a224d76bdf9c9195adee30bef4a0b0411fff980787285896e1943a62271d0aca531446", + "0x8024880cde783cdb2b863e3dd856be92bacc5b2a1347e96e039fe34279ce528560d2df7d4d1624a4595dbafb40529697", + "0x8883d02c2a5c0e026d941c785128d4ac6f7a9de625ea735b7d6ff27a5ba10fa4d6370d450d99a855d919f40d64f86afc", + "0xa1beb985c45fdc30ac536f1c385b40b6113ef6fabc2f76d255490fe529468847a776efa674ba8fed72180f07d3f701f1", + "0xab83bd9b007561695210e3276fde72e507456ba277ad4c348a2aec7a6e9ebdc2277cb4bd0bca73bd79bd2240a1fc4456", + "0x8db27f516153812149854fd6bb1250e843a3ae1c9637df818b08bd016a769d0497ab6087fe3b2fd4080882713607bf46", + "0xb3891dde4e00d60386aeff161b4a0fbc30bb31ee7918ce5fc0b49aac3238a000ced192c9c4c08d90de3a0ba973d7cfd6", + "0x90a2049a15c02e59024a7a1cb0adea97501c60b1c7442fbbe560054c3d69264e69627ac57b7d9be01bef498bb2a60198", + "0x87df67a4bd72444b5faa4f3b067204c4927c869dd3b29ad192d859589a9b2c1d6d35ed68310081e140add254a9463092", + "0x8f80986a8dc8a0d6408ebbcb4f234e76413c11cb0d66067f9436bb232373100f20a4fded60f08dec3525315abfaa8523", + "0xb061e10beb12ba3683688a4ae3a91600d14878ef78a308d01b93e4918efc666450e3f7b0e56283468e218934231df98c", + "0x86b9e55f3783d62e381659d3e06699d788b88aab1ff99848db328a83c97d223f602201bf2127c5ecf419752fed0a224d", + "0x858d878e29925c87243e010020007f96fa33264e89c8693af12857b362aee3fac2244057e159651c476ebe1dfbd67bcb", + "0x8fd47cdef87d7a569ffce806d2c2dad100692d6c53e5f5dfc6e274f897dccadcee30fc6c6e61373961bbc1f3ecbfa698", + "0x892f2822daf3df3a759bef03168c1cb07408df62e024747a788e94d2da325f880bb9c6e136c7f6643f45b021c6ccb654", + "0x8714e37ac24f5a198f219e7c88a92172fc3db129e044e914663ac708d8101851e7c53fce79d32d0e6da74f2ccd1d30ff", + "0xae95e1dbba8b9e2c8dfbe1c202e9ccfd04fa396470035a699b902fbd86d5e6a31732a7c8cae00b9a4f6e51c8d560c7c3", + "0xb0cd058e77498e860fa20c5f8d9bd09bb249add1badf84ba8d1bd49e704b9b4bcd67a5c3d211840a2c8fefab3fea639b", + "0xb78e468d3a7da0dd481f333ae56534e2ef97587be2e259a458e25aa37952aed1cc5f835640f812d8052f5bada8f57b12", + "0x835de7965c6b26e7ad1b92eb6f0261d1f376fa12d61eb618d9b342b597c9c117a5a8f6a36269aeea88072b4641e6b5bf", + "0xb4d0eb99136b3643468c9c48a20fad62785a60fbdd3c054efac4bd1fa7979b4c9ca6c2c0b18069c0912bea2f19832790", + "0xa00c47315dc0700a850966836a95f3cebfde04dd094bde0742dee77b89a05b5ad655921f86fafd1e902938ff34d4c58d", + "0xab13fa0afaa92229a71ee91efae6d1b15f14b6eacefffb7401d41d0d6db24e24a8dbe8ee19b4680ecb69d2a0cb4e84e7", + "0xaa56c0fb18401210062dbc653df8e3732aa8921a1280e9737e99b26a0100a13a9cba8ad0317a69bba16193362ee0f030", + "0x8b410324a6406b345df0fa25f541ac20b7313fa55832752f70cf4c79f43b0bd3d5b4cdc447e6ba7bca08d0edffa8e29c", + "0x893362241ae412d9e5df46506407595c58ffbd7fb1fdaf0694c3432470599291238997abe118bf7737e56a4f5c9dc292", + "0x921618194a756be81cb49d6357cb392b32cc62d96c8ffb7e16d9659a0f226a0436bd378da7b835054dbe0de2c6372ef2", + "0x94a2904f10994928ff5367b777e1430047736fbece33442cf452018bfdeae62e84cd75cf80f8468285e347d504c94111", + "0xb4b81545b767f380bfe10e0fea9c3cc62ca8db40b43c83ffb245259378731298e3eb6c3bdc3a16932f88f5d8a86edc4d", + "0x936203c2453ff01c6fc635e4d54320d69e60047d805daae3b75633c2259108497b778f011e5a057249f11b2b888ea76c", + "0xb90bf6378d29339443c3f2008b1e2b5f0345f86e393027f14a295e583bf6e6c2b10f54b6dcc42079ff0d356c405b03bb", + "0x916913f550d327de2d8d6c7723dcef2e3869efaf95fd963d95c8980b97748c61ad8e2e629cead8577266d93fe39203bd", + "0xa033c6f3d5ecbabeb83eb363e54e5faa7ed2d7f4fb771b161762c4f003eac4e1afb236806b784baf2222cad54e2d3cd9", + "0xab289d4a5771147e6c29ff9ac2bf65d70081ea6c6af2d9b728c3c144574a31b5fd8632af57c18c389aa2cd994938bb0b", + "0x9488da2019ff13e290eeac132b491df58b5b7b23c2898ff1a67bffd7e9c9464c39bc8177a57950fd28589e3d9ff9c6c4", + "0xa5abe42b2e0891851440fb2aa6c1d8a86b571bce8b80c8e9e2692e5cb6d45a1b2f055c9fc4c74a7cd292871604129ea9", + "0x90bfef698e83c2ba4dc9304aa01edd274169a978b7154bca518daef394f55857d0d1922ebef3d91fc5ecb3b895d9e0ec", + "0x92328f1372b6406ec80786041b6d57018b8507e3881a08727aadfecfdfcfb0824394cbb1150117ac5da5d71b89e895ae", + "0x9719751c5f7a65ae2bed8aff7b4b8c34539ff011b259b7ff54f63f9d987b3fbdce5c99534ed561aadaf07bb6e939e208", + "0xa151816774aa9379fccec21cf212429a1c68cf91b055cbb9d931f461a8d5616c693331a11ac5c6fcfbd17d84ee0b44e4", + "0xa72977b1285618a45943ad00f33f37102e2885eccd2f76785254eeca495068fb1d8d49865343e9e8313c6c2c3b2024da", + "0xa6f5ad2e023a1585d90625c9f7094f0e8851c79f0eede8ec582ee8e063407cc5b8298e5fdc4c786e4fbbcecaf33e787e", + "0x82901e008febcea0c0a14ae21d985a397630e18ee6e346f4a449f23be228e8f338df567d30211a11180b94fbc5204bec", + "0xb9b57fdb8d14d1be87a25f89553b3966eb7869e0519ffdf4cc4d51f4cec90d68f7b81cdc0450e04207276e9c63ace721", + "0xa06eabcf43585a001448f3dc30411f3d5b74fd0a695c81eda9981842ba2bb0081d3f5a8360aa18b6d43ef13ea78b293d", + "0x926fe48a7e8f07559b7237beff9504476dd97b5b4d67acd01a3633358a6ba4c7abed5c87683a11209aa2ee759888e00e", + "0xa716cd3a84a963e2a5a46145b6ef4ebce705de52bf2945c374152a1e41c228a9c4eae0b6d1e222c1eea8b9c13c002177", + "0x8a9b5985df6fb32cdb06ba1591a977545444478f2fe985ed1b10de61c630f0a4693c2185d63f0dc0256b208072c43b17", + "0xa8eab26ae0ebcdf96a59fad1dc2d5e83b94abb2ea1774b607023f9d9e0fe065853b1e2242e794f989a80a47f550c0bd9", + "0x84adbf38164cd04f3d770a7f4b8eae7a5d25b4a803fb63c02b95b71b33e454319c44e07a760d22bf5f58e7e372d09a16", + "0x90f443a3ba1b9129a0bee400b5b29d42e50bb2aa56b0022bbfc3c6f8d69db40299871ec7c1b68421cc89e1af6b13a39a", + "0x81c5a94b379eb98c494a8d0067c748ba47e87a2ada0105202ed7651eb4e5111a0cd8569b06ae68d392c4fd74a37833d2", + "0x8f92324b14a1549ee0b186073a26691088e41556d33b54258fc6e0b000e9624156db4e97861a0ec22960e6c47ca8a1dd", + "0x8b021cd0fffe055068cc460aec3cc455952e2ac32be5fa060e0d1b6cf30ed15381618f801249e893b1b9f10dd82077b0", + "0xb3e9f0dcb3d6f0b138f589fa54dfb01f849890ab97016372d004aac55103f363a64bc0e606ddf75430f1534a30fc522d", + "0x8fdfe64af891db89b25daa859864d479cb7599486bd6f36e593f8f2f839f942261ffc3eed5001a93fde44cbcdc24c583", + "0xa9e4554373c5073e135874e2bacbee69c65308eb0785532fec6a37834e8d0b437b77a2f11cc63c87d7183b82cd9b6bc9", + "0xb4c47daca723ad7193ac5098cad4dcab654186ec5ea5c0fd014a3ac39726be954565a901694ba211820c011fa1c59e18", + "0x8835427e86cdceb4c11cbea331ed724e4e78af15e3bab5be54f6b926bf66b5d99bcc40dbc456d86342c9fa83a033c2d5", + "0x8ea84590a400cedba047c2661378921a42f5ca0421da58c1bcb37bc686a2aed98afab3fa5e6ba3a51029390ef3cdf4d4", + "0xb48551170fc479d69fffb00fae4fba301e92e37cae08f596db6f6489c3b7020edc074f9e8d7465b84e9dcef1b6b3aecc", + "0xa6f318b1eaab00836a330710e88bfe400395b3081485f6a212e3cba9463f6fe7864ba4f71e57a411ecdf2bcb4d189f96", + "0x848d5137a39999141a79f4bdf91150796ba36352d8525821bf3bd6e070b352792d79147341b8254dd60fa8c36e9e2618", + "0xa8526f8904b1eac4ae2a25534aa91e8031e9aac7b8f58d8f49897e920c36c0232f4a30aa6eed305deb0f7793c115b267", + "0xb8b6a727c44c37a8388383e959d195d1d0e51a657d4ba360633d219d43c5df645383e2406c25f1d418e72b862c3a6e9b", + "0x92e64adf65b42c978f36dd03ab22ba983bfbb61944efccdb45b337ceb486beda99818bf20d32a545503c4572bb0a4983", + "0x9653bb83df66260a0bd059cd4244ef7c661b089e403d26ba777d2090783ff31f963f5d3a9c125b1ad1a1d19134f3fc8d", + "0xa74e72355e71ae5eb36dc75191643500ca3e67f18833ee981010e7e7e60a68e1b01b05901eff05014b9ef29aa4829f45", + "0x8b2139a5da14524cf6acc593144db23db424b95b8c7041d8f6c7a14a6725dda1cd09c42bb3ae26a5a3650affaa742800", + "0xa60ddff4300ca44a7c7a00a1f98441ad1438e07c30275bc46551cee1b681926d2c825cc8f90399ee5f36bb9fbd07d3dd", + "0xa04e5e9958867a5acc15fdea0d88951cfebd37c657102f6ba1dcdaa5e46cf1c823ad0d98718e88e436f260b770599102", + "0x95e977abeb70d46fe8d7584204770f14c856a77680607304ce58077550152733758e7a8b98b11b378540542b1175fecd", + "0x8c9ec93ed35a25ce00d61609e92d567459a45e39922ccd1c64ab512e292787125bd4164c00af4cf89fd3cf9deddcd8bb", + "0x819819ad0338250d9c89aceda9e217df12ac54e940c77fb8420575caa3fa78930689d0377ba88f16d38179a807135dc6", + "0x8baafb379d4150ac382b14a64788d819146480d7a1dccd3deef6889686ded375900f5df069843ef14d754ad3d7540401", + "0xab827236996bb79b447714c6993af941c5ae66248df4d9a6f3650d44b853badb5c0cb67804210e07a7b9d66ca43092f6", + "0x927656c3eac8d2eb575e3daeb77f9605771170c325bee6aeade10c083d42bd8dcbf3bcc3d929ea437001c7cf9a95e2da", + "0xaf22b212d5ee44fd4197966b9690487c38a119cd6536cfb8c181f38a94610dd9e057f95774047a446504dd96dd11e326", + "0xa44bd94b9e01e3ba36340f2ac2201ecb477495d4f1fb6726a6b439302deabb5a35d237c6a6aeb7e3b0a65649f8656716", + "0xaf367aeeae3bba14fbdb05bcc1a521000dd9d37f5c34ae56fb306d3dfda201d0329a8b6e89d98e15825cb3c6bfdb1194", + "0xabcc4fbdea43e50ded9e2fb01464f4e87fb136e960141e8d39214f92794cfab5634f22cd40b18d8c0e501f2307aad23e", + "0x920786cbd674348b9853689915dfcab02cce2a4596d117962bce36aadddf4bdd143891e22f2c8015517039a64e8aede3", + "0x8cde63b9bd57cb3ef743f1f3e8250669eed739e5fbd68c500a3cc0c12f93862a69aebcdbc69dd8f476c2eb307f572a53", + "0xb967e65a5f1cd8d5d570f5e87e7e186fba51b9504f8e466392a76d8a971fb91fd9b7565bcc1647f50d7d15e48b93bc95", + "0x8d5a87b25fedf5edd57d870304bfd9081dc78c3e3e3b38b997260a92edac7feccdaf24feb51822d2edc223b70bb4ed5f", + "0xb6cd5d340a57f8ec73723c4f3ecd6601620dc8137a3e75a5d3c578bc79a9cae86b379950c644dee2ff99dad780d025c1", + "0xb6f0a8e754b7f52a85a2a2e6512cfd017f7fb0418d19bb318308951c4e242d3c65bbcb9748da9cbc91a738f9ca577332", + "0xa89dcf7d410bccec385400dd96b1cc6af89026a431d0f531aa992cbd7bc8bfd7c5f360bcb665bda1d72efa17bb982551", + "0x97788e7522427a46c4b6258d15623ef7a565712812fa80d001e1de8dc1791392702f3fa3cce5a8cd1c5755625a0ad10a", + "0xb5338fb5e137ff625b27c5148298f27ce8f493e2527c5d0facaa49f29cae34580d0d6c3c1074a2e46cd8db3f56004ea9", + "0x8962f006d7b1095dd0dd132ffe7e87e328510c95ad893cf3b2ab21c177c5cf2c27f47d8856f87e9762c547be009d25c0", + "0x87fee9ce9c26aa476e67e0791a809e0a06a8a98facf3faea730d438d3e516cdf75d645fa75c906e4e44ab9237a22c016", + "0xb75ab972e1a1214bab0b38cc3e973d44bb233acda5b4291f5e110b6fb78fdcab93dc63f01168debd898e165f615be1f7", + "0xb5a0fb52bca279d3853761a94b206acaf313df33ae6303d9b71edae90b66fc507adbc60fb11e758888736c81d5d80c0a", + "0x849b8f0005010e684701cd3a4e59e8c89e5fec59af6d2de5b6332cde03b865ea84f07f0b80ec3404380b0e148fbd2c24", + "0x96e2b0b6fe78408f9208f809f5c40398100b2dac202c8c5c33c2189560dea868270a598c419871a5a2b67783354f6014", + "0xb234b81f996142d0df2c719760bf996544820a03195a6dc0ff6a72543692f5a369bf63d1f0b477ef2fe7b3234e41f685", + "0xb85e39bcf40da1a12a535740176f4de749a93824079deb5fdaa004f3282fdefaf5275e3418c88c419bd42a3dd2ed2b3b", + "0xa27279304b89a18a4e2b443246f2368fb8b15f46a34533179b6bd2ef683f6e98e222b7a32880b39b8fac1afa90133803", + "0x8923c22cf15c9c1964213d725b337ece9ea854775a06f75f232c4859c7142a3942f418354e33066298aedfba3cb27e62", + "0xb109f714311fb9bc431ef57911e2cad6a3949455b9f23255cd7edea35be629e07f845fe53e2b12a32305ee2f4f264f27", + "0xb51e82ae5c7d48050e405897d0053e9ea4b2714d002e88f78c9a307cd50b9c6b3ee7cb86f86527be9d964b01895fab20", + "0x90db256931c7f98bcf3bffff4d496739185e7a20f329ee7bffd4e0850a37739948ec745285703967f4ca50ec370cf68b", + "0xa0485ac0445d88dafac56bfba2563b020cfc370f54c1606c89d12cfd8a4d1336d2ba50306e476155a6f5b0e0a1f2d092", + "0xa00754c3462e74bda928da855bbf90f9077db395e32f03cce9b2955546d900b72330d247b7d607b65e130f5b0d883de0", + "0x8547d56727c3ad8b5c8ce622ed9ad86fe8cd78e6e4848c9845914b5063b17330bd10b46d8d3f18f83ca09ecb28d1afb2", + "0x95b937b2a979bce0e159ac75c7d5d659be8599c92305e73e942aab414793364a3ec28c7c1c8491a5750ba84a29828d8d", + "0xb011e150f0294e45a0f4c69409999d0c2e602449dbd67ab95e8258466687cd733a0329083a31b03722f4e2580ddc95e9", + "0x924651a733ad5e5d9adadad3ea6a6babb8e455c8d5f2cb5bdc83fa422e7752592190ccedaa827b866861e73506a6968e", + "0xa4d5180122f8e31503ae027e54da50f72f5cfb910a6f7309bd882b5cd666f454672591f1f20e461e182a47d03b47052a", + "0xab19ae659c4f73ea3d21895269dbec583c7029955a36469124ebe295027010faab56c4a475973497f28e9a77c03b8fd0", + "0xae7ea1a803d0f439e91494f8f35fc1167dae23834c0c699ffe65d3da8b09f8df5a53195a99ca7b8558242279e69578fa", + "0xb9d63cf0e30f9800101b43b980bcd2f229758e74b21ad5354866b4e684791c08a184330dc316228a0d67fe0210f2bc4d", + "0x8c41629744391ddb96dcbbf9cd99b13d36e57d65962e0aeb92ebccf1c4cc769626feb3ec0363def08eceb102b3dd4ad6", + "0xb2848ff24faf9e667a8c19d050a93896e9e75b86595f7b762c7c74ccdfb9db126ae094961fee7f5d1192776c1ac1a524", + "0xaf013bc29206743ce934d5887b8d0fb3667c89bda465d2321835a3618513fba6a459dd7566268220ffce7e0c97e22b2c", + "0x8bb799e36db1132da8e8b028ea8487dd3266b4628c56dfae4ea275f3c47c78e3d7445ab8d0aaee4cbf42148b3a148175", + "0xae2b81fd47c038b5195a52ab8431f0d3cab4cf24c4237252d955aad2156adc16dda9d3270157e0bfe5a44022e5c051ef", + "0x8e0129213b1698d2ec6df132356805a8633ba79e672e586dfef664ffccca71834253ba14f296da962651fcba2c002622", + "0xa1ae30b500ae77cd9bbb803d737b4a5991cc780618ac22b5cc179efd8fe10afb8c135457f2e7b86ded485ea12eae70e5", + "0x8a39723077b7c0df6e3bf6548afa3910c214ee275951fbe5155a39473be98099626ea14d844630a6fa90292b9594665d", + "0xa628386c79b61aa7314b01d9814aeec20c2a66e3deda322a39957e7135c2e52b1da486d1b9cd61c87afb22c1d10f6462", + "0x97867f469b01249820aadd9a54e12d4fdadd4555f2d530450e1f8f6d2dae57360578e2c2c8ba41e3b5950df596537a98", + "0x97f192d0457c217affa5a24267dd16cb4c01de8fefde9df4884e1906d2f22e73382dcee6c7d910bf6430bb03f4a4f1e1", + "0x86d5b5739de8442dc74d0d8dc78e49210fe11bf8c6ff0f0faecbc47b64812d6b28c8afddf6d9c0212f1988451d6ccb1c", + "0x8ff3312ce9693cd4a9f4b8e75bd805f65b0790ee43fd9e075fe4cebc87185bdf161335049819f22530f54fed2779a5b9", + "0x8dc41d85548bee5d51941d55752a500bde3c5a8f3b362da4eec307a963968e26605048a111c9166d448b8dddf6f53892", + "0x996bdfd004b534151e309ac925fa5ee7801c9da4f6b4c43e156d1158b134535a2a3956e1255e0dd72ac2af6bddaebcaf", + "0xaead652704b788bf4983c8f725c644c327a6e9f6683215f5c826c09f82fd2e40631791f51d14e6aded91fdc018d45501", + "0x991ffab58a82b98ed8fc7b00c3faca153589fe09cebf6a137ad506387a1ca4dba475b0e4a1b9bdad829f1422facaec39", + "0x9652e6c4ae084221d6bad855ec0bc11b5f855c6efba67f644e0902ab790a98861cecc6ce047c68273c3aa7eeb2f4c7d9", + "0xb88b816507aaeea6dc92b861eabdc96988b74d7883f20a4b30ba249158acaff3c50d261742fc9ad2e9eba888a8d59065", + "0xacd028a51e16c07a10d2073b9d03070457ac5f1246365295a1359d015c460b92b4861125fabe6f114de8197045df408d", + "0x806d3cd9d02d41c49179fe7dac5b05dcfc9a205a283135d4f008d0771c58e6f963d7ad0f6798606edda718eb5c7ff3ed", + "0xb9b71f1657a6b206fc40159a941e127f252a7b324dea864ecd804f48c0ed86da9778a925fb65491204a92bc2a26fef32", + "0x80ed67bd0e74350c875abedc0e07fd42ce7cb926f0f3fb1949c6ac73f2300b5a14a5c6f6ff8aed99d5ea5029bb8e7ae6", + "0x9875f67a7a473714e4dd75ee0c763ddf88101532d9680724b3848fef69e218b04a96b90f88e0f4409aa40b9a21507ecc", + "0xb4a2bb1b421e5243e5e7576a0672dc19f9f70315a03f6411c19f76616ffbb70fc5dc0e57fd4ab85e24ea2261b7ce38ab", + "0x879723002ce43e6c75ba2246f51436efe3376242beff987d025c3c4476495af32d52a54fad5d9ec329a442b93bcff1ce", + "0xa4121efbefd9c3eb143619afa52a916f199c75024908047763b29466cdfc837c2fcc894aca63044c33c41c777e529b5b", + "0x895f637b497a9766714a3d9e3c275a1f0c9ddab105bf4c8b7e663f36cd79492022415bb4938c1a4849bda73106ace77c", + "0xb119acb8b161ce4384a924645a248a656a831af526cd337d97e08405415b9dd22060849c76b88a4785eb5e7214961759", + "0x802e712f4c0a17009c4be6c1e5ba2ca3b82adcb68793ec81f4489b7985babd8a3873d544de63d5e5de0cb4dc5048c030", + "0xab111051e4651b910c68ecfdc33f2d99e7bf4182df68cedbdbbcac219a543e04d93ecb2763fe32b40c095c7ca193c331", + "0x855c73ef6afc6bcaab4c1e6388519fd5cbb682f91995bebd558167715db454f38012291beccea8186a3fb7045c685b67", + "0xa29d02ec6d9baf84c19dfd0eb378307703bfafc0744b73335550f3cd1b647275e70215f02d1f4ab82a5df4d4e12dd938", + "0x91510a45b8a50cac982d2db8faf8318352418c3f1c59bc6bc95eab0089d5d3a3a215533c415380e50b7928b9d388ff89", + "0x8286e7a2751ca4e23ea7a15851ad96d2cadf5b47f39f43165dde40d38ddb33f63a07bc00600c22e41d68a66fd8a0fa51", + "0xa413d4e619b63799dd0f42ac57e99628d338b676d52aec2bb0d1bb39155ad9344b50cdfe1fe643ff041f1bc9e2cec833", + "0x85524e5bb43ae58784d7e0966a664717289e541c8fcaff651541718d79a718f040a70aa8daf735f6635dabfc85c00663", + "0x97f0d48a4028ff4266faf1c6997b6ad27404daa50ca4420c00b90f0b3e2d82ef8134d0a04108a74955e61e8dfeac082c", + "0x8df6145c6cc39034c2f7331d488b8a411931c8faa25d99c5432831292637fd983d4f6b1a6f55522b4a42a462d63c6845", + "0x98c2060f67a916991b391e67fcf23e5f305112807fe95bdddb8ce6c4084126557e4c5f003afb32e30bc6808b30d4b526", + "0x8964246b3c2b8f7312f0a99647c38ef41daf70d2b99b112412356e680185da6810ab8ee0855ad7409d334173bcc4438f", + "0xb56c2c416a7069c14bdb3f2e208c5a6ad5aac1cbe5b1faf99dc89c7141d0259d1c6250be9d9195500c4a41182ad2ec3d", + "0xb7864583a4cae3b1083dcdcff7f123d24a69920a57d6594d0b7219e31bf0e236682442b6499a1f6795cfeb4f5f236695", + "0xa064f94139bf1b70d476bde97099631b1284aa6b4d87f16bfc65c075e58b2f1b3c2d057605259f806e545674a1169881", + "0x80d1bc4acf14c0f487cd57c5d6157b7f38917e93cb660f1c25e474fcdcac3c3dfda50f6bcccfd6676bae25c4b6b5014e", + "0x8ad9a4976c4e3e282843518149fcf5d454240740f4b91466f6310b7216d23d70b9b47c42870293252f29f092f330967a", + "0x914197593d2d99d784c704cad7ecd3f0b9f55dce03fc928d13e1a1034566c4de754f1c2a5ade047b0956415fe40399ec", + "0x8d77f5e29c572ec3c0ca39cbae2072ba4102403265b3d8c347a00386da9c0b8688d6e3280c96037c300d57b3545f3773", + "0xabfdf79d935fd4f06a04938d6580a8cbf9735f0d498f49677f26e73d3b34b7075d525afcb4f14ef1632cb375bef7dd55", + "0xa97a8c446e3edc86efac7bda5e2e5d0158c909552a3bf86151df20ece63b8d18b608f477286fb1c7f05605ab7e6a7c2c", + "0x8618d946c7fd62486551c35486fa466bdfcdc63c941e4cff5a01fbbe566b7ea9dc763cbe73e2acae063060b619a212a9", + "0x8d03ee468070936004b06acf64b868963f721f37faa09887f8a82c155ad5c5732572a6855b531db58af03b1afe034a18", + "0x8d3247f75966ea63935ef6049f7c889c1651374adb446f49499fc9191dbcde7ea33cbc1f1e2d3d1756b6e69870404643", + "0xafc853c3a3facb4ba0267512b8242327cd88007cef3bf549184ee891b5ddc8c27267bae7700758ad5bc32753ebf55dae", + "0x80df863eaea289de5a2101f2288046fdbfaa64f2cf1d6419a0e0eb8c93e3880d3a3fdf4940f7524ea1514eef77fb514e", + "0x8434b5888c2b51d12d57da6fb7392fff29393c2e3bfee8e3f9d395e23ddc016f10ebe3e3182d9584fddbd93a6effcefc", + "0xb78cbb4c9e80e3808c8f006dc3148a59a9cace55bcbb20dd27597557f931e5df7eb3efd18d880fe63466636701a8925e", + "0xacb140e44098414ae513b6ef38480e4f6180c6d5f9d1ca40ae7fbadb8b046829f79c97fe2cc663cbccd5ccf3994180c6", + "0x936cb8dc959e1fc574f6bb31f28b756499532ebb79b2c97ff58b720d1cd50dc24b1c17d3beb853ba76cb8334106ce807", + "0xadda2116d9fab2c214ec10c0b75f7f1d75e0dd01e9c3e295a0a126af0ea2c66373d977f0aefdda2e569c0a25f4921d0e", + "0x89a5cefb80c92dcad7653b1545f11701d6312aef392986835d048f39d5bc062cabc8a9501c5439c2b922efc5f04954d0", + "0xb9acb52747ce7f759b9cdc781f54938968c7eeacb27c1a080474e59394a55ae1d5734caf22d80289d3392aab76441e89", + "0x8564f72ce60f15a4225f1a223d757ebd19300e341fd9c1fe5a8ece8776c69c601938fa2d5c21b0935bd2bb593293272b", + "0xa5567d7b277c4ebf80e09c7e200c20d6cb27acbaa118c66ef71cbccb33ee3ddce0e0f57b77277ae1db9c66ed6e2d8f30", + "0xb82e9c2d8df1cdd3b2417bf316d53e9f3cb58473c4cb5383f521ef53e0af961ef916e4f6557a6d8b4655ec01415231cd", + "0xaa816dfd2814c8a25bd2cbaf66303ee49784df471bac4b3188074ea30816f00f425234454d40d8ad8035aa925d74da36", + "0x9919f384df20faaa2d226b521cab207dd2b62420d25ebbda28c9b2ca76a2a52203b2ad7844c1a25f5c75f005c5a83149", + "0xb24a6aa35c2d0f87e36598b36224c64427cd69642b6f9c1bd478a62c70f8ee69f85028648f6603b4f04fb21355f2afb1", + "0x892e044bdb1276b455eac2204be105e1821f987c2570494b1f32aa09506caba7ed343cd09b1bc126fed5e0fda3d0eaad", + "0xaf0e01a3ad954dc048de18bc46bb1c4971db2467e839698e4dd05cd1adcb9261013fe9fd0cafb946c0b586f6aad86d4e", + "0xac152f0a9ace425378daf02510eb7923ff1ed2c0f8d1deb918e4efb63655de1ba58c96438e9aa23abdf2431dc771370d", + "0xad8c7419c097709347e2394195924e09617b47ac5c7a84aeb9deab8975f22155de0f70cf20d8a976551b14e3a2683a2b", + "0x808f14f67ae801536fb70a5898ab86e50ad35340cffd0648daed2f2c4564c9ad538034b2a179a6a8bfa27e9d93b4cbe0", + "0x80a74ab7ce4769db93cfa695a166db95f0a9c47885ff826ad5d93310f36d6b18b5351c67c858b9837b925e85a1995b63", + "0x95b88c3cdd64401c345828f4e4754b1a88b4875a14c08a668b90acd499b3b858842669ecd73a46c5d9f1de32ec1a0120", + "0x8ddbd770b7b18a5917eb43926fa05004e819f1d1ead05b915269e4a86b53e0633a90559007e59f6705a3769e2126ac56", + "0xab6db5fc220754f19948bef98844e6e38dd623565d1695e1198040c228ac4fd863c1f168cac1d036bbfb718d9d8dd036", + "0x97bef628e977c069e60c395a17740e0e1bc1828f5607ae7f30ce5a0c95f02b53af2ad062700a75212e462aa22c3c5465", + "0xb68d465e04fd17ca98501e61eccb0ce30401855e98046e0c1debba71c2153d6a7a704aa36a6f12454696e78e87181cdc", + "0xa79cfdd048f4181e005bd0fbac0a8424495474956b58ce858d2b700fb0f931c406282bd33bfa25c8991bc528d12a69c1", + "0x843f55fa0a6a0969daf2b48080738f30b269b2e7ec123a799e5b203c0b3b4b956dc95d095bc6550b0013918cdff8a225", + "0xb683cdf2823036827e5b454bfe04af9bec1850d25a7a7a44aee7696b6ff0468b7ed6885a41dde2b8f3ecc4aec880c3d2", + "0x8b500796e82acdc89778e0c0f230f744fb05f762000fee877bcf57e8fb703d212dbc2374887bdc2e7b7a273d83a85798", + "0xac35a8ee87bafecb1a87f15abc7ccf4109aab4ac91d357821e417f9b1474d196c38cc41cd13667f68d1ffab5e79a6e92", + "0xb6e517739390cfed5b395d33b14bce7cd7aaece57fe79a7eb3cbf150dc10765c3ea9fef7976a21a2243687e6eea38ef6", + "0xb53901eeee26692273365b789f2a60afc9b5f0df229c6d21b07016cf4c0e7985beec748aeca52262f68084393ab038e1", + "0xac4804f33d8ba2b4854ca3537bd8bf2dda72d4e94ff7ecaaf9bd3b7f098343d74d765471ef80072ae34f860b052cbfb1", + "0x8c6a30a93f1dde18039bbdd1ef294552bf79856e20bce863e4b8dd72d906be3ff22468ff3610e06b5a7d1745dde7ead9", + "0x88f0607fa3b7cefe20a02115572b16fc3222be86bb19e592c86c48afbe7e0dd523492b0c29a3bceb9a20f5538bc3134c", + "0xa660b801bbddad725975ddf9a8f606f76ecef831f954be224d6178c368e1c72d346f00c4a4c95c289b62d36f2af323cf", + "0xa75b9a6aea9542b698938dcd6cc2f6fe0c43e29f64b2f54aeb05d35fac73d41aa7fd750af4fa9333644aab8db90775b9", + "0x83e1b7129d963d1cd076c3baa5fe422148e939273db173e4d59d1858a7d841eacac7fe817d15ab8f8a493bf46c2045e6", + "0x9060a2e9c24de11f9c70e039b5ffe9e6d32f1ae39f3dda263610df2265d917679e689898e4a8bd84ad34613dca5e3761", + "0xb42fc8b863a2af15e04d1fe6693c09b46007c0b8298973fb4762b45b4590ad7fe0aa758918b2fe5ed1ed0359754fd955", + "0x83e6de7860fb256ecf7b47506a5e557d0fb0aefe57fb513c7dee2bd9604712d08ca26adca7ba9a54b712372a7c585a26", + "0x90586e9cbbf71475ecd3e7b5753b286804dcce61e165502a82b960099e79272de8b7494b8877b54ae838eb5d0f71af2f", + "0xb2e4b0d21208f73b7b75e08df80cde20c4578e117d37092a490af82354e2afd3a7dbab46fa2d12fcb731cdaece69c2ba", + "0xa010961239bb8809fc7fb4aa08fa30d33a130f9f417ee9ea60f587dcc5ef4e1b7abcdcbf8e848ecdcb7972ef6af46e78", + "0x8f511fd58d1e3403a5eefdc0a4ba6b8af848c7efddbf9575ee84449facde05ae9a24aa41a5725416467f6fbd11369c52", + "0xb24ebbd2d4482eb618cea1ac4fbfd9ed8c46c0988a27259300a7ce5ce1bb256aeca0357828cbbc4cf0dfafbf586040e1", + "0xb3ea29e9cca55250e9b7b9bd854edae40f0f0cc65fe478cd468795d1288cc20d7b34ced33bd1356f1f54a4291faa877d", + "0x8a8b20f222d9e65bbde33638033972e7d44c6a310b92a9d9c5273b324c4ad1a94f2a10cbce8300c34dbd9beb618c877d", + "0xb2436a9a647dc3f12c550e4ddc5b010e6f9cb3f3504742d377384b625fc38f5b71710a49fb73ffaf95b9856047c98201", + "0xa13f8b77c70621e421be94c7412454adc1937b9e09845c2853ef72cdbe500e5c1bf08e3c8b8d6b8eff4bce5b8dec9213", + "0xb25de8780c80d779e6c2e3c4e839a5a107d55b9cccc3ad7c575f9fe37ef44b35db4c1b58f6114a5f2f9ca11e1eb9c5fa", + "0x96ba6ad4358c7a645e5edb07d23836cbd35c47d9a66937d09486570e68da3c8f72a578bd2e14188d3acc17e563a652d7", + "0xa7f55989814051fda73f83b5f1a3d5385cd31dc34baf94b37c208b3eaca008ff696fd7f41e2ecffc2dd586de905bf613", + "0x882d0c7c81e58eb9560349f35c35e4498dcde7af7be8d7974b79d262304c26ab67ffa5ed287bb193d5f0ab46b4096015", + "0xa607158f0c1fd0377a8ee5e9715ac230abf97406c19b233d22f5911ebe716967cc10425546dc44e40c38bd6c2b4bca2e", + "0x87e8cde50e5d852d3f073a43d652f7186bac7354612517cfaecd4a1b942f06fef6f14546279c0dc0262e2997b835b2a4", + "0xa1c93acc6db9d5ee426fb4a0b846bb7a7b8d5915bec777a9fe6907246b0beafb8938941c8c79ed6082155f75dbc1e332", + "0xb1e4f61457b86f76cd93eafd7536f72baf239ce5a62bd5a8085a34e90576b1e118e25002d2de49b01d6e9a245ee7d3a2", + "0xa0435fe9a4bd1031ec5973a103ec9396b2ce9fd982f6d9ed780fa80ac06a6e47a0a6eb2daf52df1dc9292db622ee9fa3", + "0xb66d8e8a1717e4bfa42083b6ef4490e090a73168b2912f2111743e089027be0a4945a229ecf5d0b5eec11b23f0e11303", + "0x8eb764f26904eea4f4169be6e75beaa6a39e4eb524625a15a78befe3d8e3cc82692d9b135590c20ed460d6e4ba630ef7", + "0xb7e4aea6bb09829e53fe83e53f49a7a331a6d7bf76e0073d758577e6d6fbe63dab642b23657355cad48896ad8715119c", + "0x8f94207982373a99ffa282673f192aa98d0c4461fb77c31dc4549628bd9687a249f1b3c66b1840929341e42516c5c64a", + "0xa9c673cb247b13e17fa5e616f0399b7f5c7ad043e143e44ae68855a840870ab3d2aad737ebcf74c2cc9688d17ef3a794", + "0xb02635104dd28c02068985256975c0af783899eb996e37d021d9a35238deeea9e836760db21869be7b6c82aa687ded29", + "0xb33bc0966389710812b5f6698afa3e9c84839a1b85492ba11e6ded26695260abf66be6fb355d12d3a8524966f0f89e0f", + "0xa79c0dd09506951c33da3cbc23843fd02d641fc24c640a205e6e8150240372847312b9381fb03c5d301fe4dbee8d0da2", + "0xb74de6f3a2c502b5b658ebe8a9b7edd78afd036f5a2736aa06502863b6865d131b9e3542e72a86fa2e1d2db4927661ed", + "0x99e365def1452ff9fb4b9eccd36ff4154d128469ba5bd73e83ae457ab53977cf6fc04a5d05bdcde357ab539e34bd9fe0", + "0xb4f2bfb95abb47c67870aa6ca38ac8f3ae1b1a2bed064b1be7ff90865ea12e4930fcf66429c7ecd1183fae4a01539386", + "0xae4bde87f36b912e92398bf72e11d5389e93b2de1b277d7ed4b6fb5a9ab9f71a959ec3bcb734c11079440fe42b86fafd", + "0xb826459e568efdeeb66688482b67ef5020787275123fd3192f979b6175e3b0ed59e17cb734a0a052bf13f0afc7bd237c", + "0xa99dd735f4a7c85cb23dcc7f4835f9ab32026886909aaa95876b98029c37dc4d621726c872d3a9e50403443c958f4029", + "0x99083545034768010988bf8a9f34486c2cd9da27a1d10db3ab86eb69a1dd9c8ee723e7da4ef2aced63c1dbd53ccc52cb", + "0x8ac3209349f0142546c714ef7e9d1b094aab5469b8f080c0a37cb0362da5349e108760f272fbba770aa468e48d9a34c4", + "0xaf5f48ed74b21e3f2c1430192adb4b804dc873cd7e8f07130c556c30e7b78df0ef5a14b205368848fa9185e5a68dee0d", + "0xb8b741b65d68df89443523ba74203226f1e0d13bab073d183662d124e83e76cd318b2bfff09879c04d81b577ac895638", + "0x914abe4282d11176d4f2f08c6f15e6c2d0cde1ab4de00bbe888015c205f51929d97296a0a8d3ca5641f085a29ea89505", + "0x83ec306b2a9a6780efafe799df90b1aebdbff7d47921a136ea8a5648b9708a97231245a1082fea38e47ecafbbe000528", + "0x95d6b58d70b388dfcee4eda0c9805362ccfb60a87603add565b175b2c14ed92999dfdb0d3724ee3e5d30535f282641e9", + "0x97eeb4de607c8306e1d4e494f0d5db126d53fd04983ab5674ec5996b971899e734fa4011f2c889da21154ea1e76dbd2f", + "0x84ff21977fbd873ea06bec444d4ec9ff0e3902edc29dfa25f3bed269b3709e3116e99dc06cc3e77f53c53b736bf8fc29", + "0x8ecf483874a040a4a1c293af145094fedf203a5eb37c3e165857e108cce3e1210e0bfc0f26f4ae5e2194024929ba034d", + "0x97d9b92b2ef34609d69402167f81bce225ed3a95718a3b403f702b93e96a121a8f7f072d0ff47e8b25164e204d1576bf", + "0xab87c39cca1803b4e84b32e40ff30289e3cbbcfbe16a70f9e025643824752359be1f10c3e5398df402b6fec64d5a3537", + "0xaf84ca57e6944332884b5c84750afe0d5950015e127acec161853d55d48fd864c7da8d59cc5aba4ceceac650b813fcc0", + "0xb1d23d98edbe7089ce0a8432e0eb3b427c350fb4bb39eb2aca3c2bef68c432078cb9b4b2c4966255e00e734fa616638b", + "0x8e2b5252e0ea96d40835ebfb5693af49946509975682d68651396d6bb1463f09e75fd0afa04ccea49893b5b9c3e77e40", + "0x8db25e762f1d4a89a9a1cbc61c01698e775906bc88a921b2905735457a35df9ab84bae12e1b1b8dafadd50212f1acda1", + "0xb5f7cd163a801770a4034e2b837e00191b0ac63a2b91032ae9a99ec182d748798df48a14644935fabdbac9a43a26749a", + "0x998e7232e5906843d6272d4e04f3f00ca41a57e6dcc393c68b5b5899e6d3f23001913a24383ed00955d5ec823dbd3844", + "0xab2110a5174ae55ebb0a788f753597bd060ee8d6beafc5f7ce25046ea036dba939d67104bba91103d7838b50e36703d1", + "0xa211972a4f6a0303bec6c86f5c23c0d25ab4df0ba25876cbaad66ae010b5a00aa0c5daded85e4326261a17a563508a25", + "0xa49f53496a4041a01e07f2c2cf1e84e2ee726917bb103fd267451b9b7bb1331c0afde85a79a55409bfde27328b2a4745", + "0x934e915c67c7fc47adeabdde49f63f04644fe234672003be2aa0a2454dc8d9288f94293478936a450f2e3f249d395b5b", + "0xb6e69e9d6808ff7f60a01b7aea6781495d7a20f5b547852d3f0af727a7434209d3015a9dd04cbe3e272918e32e345508", + "0xb348d3462092b5c6fead7e515e09611438db8d69650876dd3b56226e303252bbeb9e9f3b888fb911445b0c87132a1d0e", + "0x8d6510334a905efe5a32001e167f1ba06f9bc4af7ffbf11b7f7bf3c0076b5cca373d8c47e98c1ba8755bb22632bfe0e7", + "0xa2d5200f20985dcd473d119ee97e1c0fafafa0f191185bfed9cac429cef8198d17665dac4f70342eea66e6e4a7370d58", + "0x8dd7eb6b1841b3f33425a158d33a172b79b2dc8a01378e4174e67a1a4c8f4b887f02c7c3a8f354ed9eac718155bcdf37", + "0xb16ca19388642f71afcd9f7007b490d82f83210ac1a989da9d4bf4c419de07af8c048cd301ec7e01b9d06abda7c169d5", + "0x93cb2d847d1a88de8c1c9d5b3c83efd0b7afb3682942bd2c8ab5ef35b33dc31a097a3e181daab8630d4e840b677216dc", + "0xa8b648c769e77a7b41c0c689fe2fba9bc585067e004bcb1732cb7b1618e97b317781c36c23a00680fc780b58c301a789", + "0x918c321100d57712866bdae84edf7e42df30a32853af257e0cb4da028842a43b49e775f3cecb85cd817269c728de7319", + "0xa7b0f6ce42e00c519e69b2c78fd9b75a2e7103e5892d3c1afd70c9b5b9e706180a4bf73dbb2d3eed52bfd521103ec5b3", + "0x90041994af3322b010891356afd8115340bd7fd7ba328716fbc4fe458236c8cad8c7564ae473d6091ec3a54bdab524c0", + "0xacb1ac83809573846231f9be2dc5f3e986cc36dd9574a620b1cced45bad0b11ea957ce8c6cbf964a0af916781c574f05", + "0xac54677dc002698fc4d454c7beb862ad085d0514f92576f3485a44c0cb47afb9db2c085058918a3508f9b3de0137d97c", + "0x8dea56e1bfa150e442f8484b2952b116781d08cfa3072d08657cc09b0217276efc4ab6f5fd726bfd826f6976ced8da29", + "0xa2b09e25baf01d4364b5205fa0c4dea84ef8fe03709113b034f88a0f0a502a81bf92c1d4641e2ac9f3a6f4203d3645ee", + "0xb95fe37aa351b4292691a9c2e547224c37ec2751a31ecce59810cb2ae0993da6fbe5efe0ab82f164462fa3764b6eb20f", + "0xa3498947e91a3a540e86940be664fc82f1e83ff41a0d95eb84b925e820602a41b7393c8b458bd4ebbe574a754586787a", + "0xaa2516d3620c832e5728fefdb1af0be30c871cbad4b166a7a4565af676e73bddc2f2f51acc603b3a022056daad2b330e", + "0xa9251b56467fb55f64c70729e2ec77a59d7eac79cc0b4b25ee405ac02aea46bf1cbc858bc773934a6d9bea57cb528185", + "0xae8c0a4ca7ba6bdca8764bac98df0581f00358db904e57867e6ffdf15542e55f7bad2dedac152ef88038b466ed901934", + "0xb0881e27e52cc6a57c4f3f278dffc7f63a9174b68bc867c16d8a151d9cc4d0aeb703d1074d1927faa9ffb43e10912c9a", + "0xb67138465d6654ded486d18e682f11a238d6a65d90f23d6b13eb6a1b7471efbac9ada6345dfb13e5432196d2a256829a", + "0x944c69a6f1126edd38f6eef60b8a5bd17147ab511e44e8e0a442e87244d8f35236ee0b8d3dac0631f8598f16486a5f74", + "0x995679dbe03dec775da26708cb9200dabcad983825f1ba601eb9395f9da350ca71e8af61dbff4c668fd0eebac7e4e356", + "0x89de362f02dc14de6995d43cdea3c854a0986c605ba5eb5dacf24e3a85983229bc99a2fcf50aba3df59f0fb20daffe29", + "0x84607f0e2d078df22d0866285614f5d78cf7697c94a7d1b5e02b770101ceecbfd53806b377b124a7320d9fed65000b97", + "0x93e3faab60050dac76ab44a29bcd521813e76ec8e4ae22712d77bb489bb49f98f9087acfd6a77016a09a42ddedab2d73", + "0xb7d64a7a35f21747b8e6a874be31ba770c0d13cbd41448411994e8cebb59591295a26bacbf74ee91e248a5b111aacca0", + "0x8dcad429a2b0d66b9eb8c1c3924d7a72979727db6a535526a3518bed2a9532d12aad1c5a778824ca4cb98e3e513f85f8", + "0x980882895faa347bd2fd1dda7b8ee7ed49e69843afe646f677b371eecc7a10e0f4e40bb55f28995a40080df471876816", + "0x89e8e7fb51df79971e2f7bf65783614abbb0d7f3f1b4a15d3f0d160deafa7ed1c446d9a5ae1a77160d4dd94ceed8af13", + "0x93fda8d350392e9c4d4ffe6534f7e7be53f32483d9319093e8436fbb8166a3c01085dc858373e65c7f4d014e0dc2bab7", + "0x897521a87b7ebf7152de5260c0875e3c7df1c53e734c672569219ee6f9bd196c5ecef159b6a1d3b7cd95e91b9b8803ff", + "0xb59affa408a0f7bd7930fa3b88750fd043ce672c10a3adeba95a12f23f0dda1793f761a86f7409ce1e6fd3b3b7195381", + "0xb4422ccc12f4fe99c530cda610053af9ffe635b633d52492fd81271d1f6f91b87171d572d5bd0e46ff63e221fb2fc4a5", + "0xa4542cdf3346ee0867c08d630c2aefc57442f1c05c0eba52d223bfdca5e9d0bb80775cff6ce2e28aa2730231fd7b1bb1", + "0xa7d297bb09118b914d286e5d1e87bdf13f7d174b988e38fb5427902e8e8c674072f36b19055a1070abcf357f8668f35b", + "0x9213b0ae24b7cb43ae95e25c09fead8bdbac55141694137d67eb5eab5e90a348a13d4d4d2cbc6436fc4f4f9f7334ced2", + "0x8aed71a0d116d832a372b42a0bb92a1980f3edf8189bdbaed7cde89fc0418b3ab21a04f5c6e1d3b8edf73f1f62bd6b15", + "0xa6c47d77d714c285c84c6b9458cbec5e3b191c0502dffd10ce049cf1ea27ddf868ef0cff13a2377289fa6c932b8e4f28", + "0x92f45622ec02483f2c1e07075a6695416d3768c8984856f284f40734346d56cb5b3322f20c2c9f0ef8e58ddc294a309a", + "0xaf6450d02b79ac9fc79f35655b58fd3619cd5d38c5317564b453f5f2d79d7a030bf767e399fe01b658a72fbd2cac2356", + "0xa3c01fed5240eb8a61ffa8ff4a120dbcebb53b8e19845949c77fb4f9b2c3dd52c7001df6219ad2f76c785a4ee0f64a2a", + "0xaf3136bfe8f774187bdf87555a1ac505322a956229a285d28bab1c88d4f4d12245af8dff35914a62e90e49f3dce6acb0", + "0xb20e21d28444fc96737958cd951858fda324b924b4d3d08932540fd4b87150f053db6985b96903906ce83dde0578cbb2", + "0xb7978101071268d1f485134b4dfd1e35f89b82c7d99ae91f58b6745f5e0273b7e06f3b23009033ecc3e41b2e9e85219b", + "0x9104b7d75245b784187175912cc0ad869e12f1983b98e052710fb33663224362bffd69ceed43e7d4ad7f998c0a699eb7", + "0xa7624cd71b92699ce3fde0e747976ee04ee820032ac45dd27d769edf3b3379a4b8db358e50c9d057c63b5a9b13d76bcd", + "0x9354a76f294005de8c59db10e638ae6e8c6d6b86a699d8da93143da8478d36116211c788d8285d8e01ea6647dfcaa1aa", + "0xb85935c04cae14af9848db5339ab6420122c041075ec1549314e3c9c5a610d9b794ea3617c50ca7af6b4aec8b06bc7dd", + "0xad6835a62311c84b30ce90e86c91c0f31c4a44bf0a1db65bf331b7cf530cca0488efaac009ab9ed14c1d487da9e88feb", + "0x80339f0245cc37a42bd14cd58d2a8d50c554364d3a8485d0520ea6d2c83db3597bf51a858b10c838bfc8b6bc35619638", + "0xb370420ac1a011f6d8f930511b788708ccf2fe23ca7b775b65faa5f5a15c112a4667ed6496ae452baf2204e9ce0dbf09", + "0x8ceab3dadca807a1c8de58ac5788313419c37bc89603692c7a4d96e2311b7fe9e813cc691a7e25a242828cdf98f8bbcd", + "0xac1526ebc6bd4ac92ee1b239f915e494d0279fbd065e4cab1f1b8a1663f67daa89560f6c99bbc3e63fa845520316d2e6", + "0x8240ab0bc36a29d43ec3059c7e6355ff39567e135f93b243145d3ada97fd1c970743819e0d58bd5171967daec144e7a1", + "0xa99743192a6f1967511b2d3038cc73edacb7e85f84b2926d8880d932d2fa12f5215592311a7548494b68a87ec70c93eb", + "0x8ffffc31c235997e59ab33c2f79f468399eb52b776fd7968f37a73e41949111957434f2c0a27645ab34c741eb627cd1f", + "0x8949d955309415d6d2cf6ee682ccd0427565142c1bfe43b17c38de05cd7185c48549a35b67665a0380f51aef10b62a8e", + "0x9614f727a9dac8ecd22b5b81b6e14d34f516db23a1a7d81771ddaa11f516ed04d4e78b78fda5dc9c276a55372f44c4d4", + "0xaa85d3ef157407bd8aa74032f66bc375fddaff90c612470b5ff5d93659f8c3523b2d1b6937b3cc4201c2aa339621180e", + "0x86f8fe8bf4c262dc6a04620a848e3844f5e39a2e1700c960f20ee66d4a559a90141ef4e5091d0f32acb1e915af1e0472", + "0xb3af2eb785b00588371beb3b49536b7919a3f2175d4817de5dcbf7fcc20c512852ef0f313327fd0589b10173f77b92e0", + "0x8388703c512eea59190351f3bd2cce83ff8bcb3c5aefc114cccf9e9b3f78200d8034c3ebe60448aaf6c912f0ff8f0cc4", + "0x95d0dbbbf08ec1ed3975fe7dd542be0a05156a2b3db5092825d918a849411ee536ed958201f74a5513e9743674d6658d", + "0x8d1a48802f1a2db247e633ddf61d3ef7a2c062c48dda59bf858916e04f56651a7d51e367d6535964ebf3ae6d2b21b421", + "0x971436871bfe868f25247145a55802945409b3150008535b372c949760d7949dd2fdb40d9b96ae7473bc8f6e9b83ecdb", + "0x8ca431728ac0f156763090828a7b6d860bf591e5b9dd3bb3b7f3ba0ca74191f9710ee55efd32db7d18eab5b479cee8a4", + "0x81e28f1a506e84c2b9aba1df720cb50e0b597b2c22f98acc34e710c934cc6f97dcaf33d589e845c2c1f6d8716d05ccac", + "0x8f43b11d3f00c41d16c9bc9bc0c44227c056bd77de4f1ca9a799418c5601e744f99066bef47da2d9088ae88eb259327c", + "0x8d330aa52744c08ef98cc5599eec8b9b4dd18aa01b803f1d1ca0e29b74f1aa2886ed0224390fc377af25852851fbee03", + "0xa06f5b203b67134c685039ec2bdbcc787353e2575ce73a415db24a517c0c31b59d1de89f12b97cbef0219fb6a1e90a20", + "0x9269a5f49bbb8fec1a387b5d105df88a027de615d5ca6afae20fe89b11746f8d23880db78dac238c955fc8bb3de18046", + "0xaf5074b3bc0656421c314547b45b5abd3045ca1b17f5e34ba39d8c1f7928a55d4ca5ea9c2ab59a55909b25255233e04e", + "0x8e7ee5d733c8e08f3fb7d85f0628de3de6835121672c65374905dc6d19e02fa2df14c13d5e9835dacd609a4df09abd26", + "0xa9b9aaf83d31e879dfb8e73a0708801b4dbdb5d7c8654b27d2c0f5797ebcacc8d00a82143e2060f0917c9d41f1a03de6", + "0x904872aa1c093cb00e1c8e369a3bdae6931c5b1ed705dd3bffba243dc4f42df3e7d7cf70303d513b34d2245743d765cf", + "0x8a4d6b3b1d6afe67383c66693f70b397e510be28e3d97dbc8ec543d699b6cbb0e72eb90a7f65e83cf9f7ef50fb18b128", + "0xa914de13916e6a0dc0e0fefecb3a443cca80d83276513b70c22c6e566a2d41acbd33a0e2836ee09abeffd3a4894e437e", + "0xb9c408f5f05934b0aefab301ba22f8254c5ebbf5405b6aa788f76e4b328c150b395f441e3566015a0deb3eca89afe9ff", + "0x8d32aa2c81b2a8b89f347c2e0b6567b2117ddbb778fda8a3f19004b7f5aa9dd814b9b3ad35f9223715d2447b2d12f159", + "0x8230e8b9c84cada1bf14ea6aa9ecdadd978d893cf5962fee6c7167ed21239210ea491987f2c8f2e8cfea8c140704ca28", + "0xa5d7b6285fea51c6f21d0976a7c3a97baa3d733a201bfaac0994db6c65611d91c5fc0ebc2a7724ee02b371e575573649", + "0xa54f00a9530f6930069f5e3a8b8b1d52ee1def0aad1763e3c609ec07f25410969b43d5943a94c235ed5eb207b33a402e", + "0xa8dc6e96399b81397734c61c3a8154e55a670fa25fa5854b3c66734cbb4ec0d8f6ba650ee3c71da3773ffc9e37abf8bd", + "0x8841fbfae1af4d400d49f74495f864804f043416c09c64705251d021b3ab7881f134a00b0241e61010617d04979d747d", + "0x95acea7ff4861cc969c1d8cc8775c5eae014ad6e2e0e2d0a911dd916c34ae69f53eef779cc24ff1eac18c2b478d3ba2b", + "0xa5dce74abcfb8c68031b47364bd9baf71a91db01e45514ab6216f5eb582ef8fe9b06aaa02f17be8b93392d9b19ab9c06", + "0x89e111169e4ae2f4016c07c574a3bdacd8d2f359561fbbdaa3474de9bc24ef8936784dfe6fe0e29a13cac85a3e622b61", + "0xa4c511af6bdf3892939aab651828259e4ef6ebecfdd503ecc14e61001575b313a89e209cb55a77ec19a64d29ada066ef", + "0x923c62156fbf3a44926ffb5dc71f7cef602dbe941a98c61f019a27a18a50c16b6135b6099fe04a2e1dc88a6cad989fb7", + "0xafb9191c541b61afa0ef14652e563cc5a557842ce2afea13e21507dde0ebbe6da5233af949c998c00865c79bb3d45ec8", + "0x8a1f0ad65cb2b225931f41dc53547d756111ecbf5bc57c5ee2cc1ffd61b126d0389d311ffe26cf06eaead95af09c5ca3", + "0x9040b20b5ac2e1a9d30abf7a4eea1ec2db8f3077cb2cfc8736b37222d8d3937f5d9f421167086dc5551e9f0bd2522d07", + "0xb6d888b8c6bd448dccaf99c3f690d47f802e134709ce102fb6f6fc68156943c0762be6f386338163e01eed2d1dd5f734", + "0xb94f0e27bbcda793e4a272603b3dcc739d3bf3207798df7319f8dc9d37cbd850e3724bdd30498c929debad971950223c", + "0x9769827767be9d7bacba1b687289e0794c6fe630d33c9b607da1f6a65e3f34cb8bd65327d9287c8c5f3c8b5f6d3d133e", + "0xaaac72c993aa2356c9a6a030950441de42b2d746bace29865382f0ef54835bc96958b2f00237d805ee6a69ca82117c1b", + "0xa2b1f027d80c1b0e79bfc7dd252e095b436fba23a97a1b2b16cdd39fd39a49e06a1ca9a1345c4dbb3d601ffa99f42bdc", + "0xb3fa0ad1478ca571e8aa230921f95d81aed7eca00275a51b33aadabd5cb9c530030691d1242a6ff24e2d4cfd72a47203", + "0xa43ed4368e78daad51b9bf1a685b1e1bfe05bed7340d4a00df718133f686690c99198b60031513328fc353c6825a5f2f", + "0x965e145711ecf998b01a18843cbb8db6b91ff46f668229281d4ca52236c4d40804ebc54276e9c168d2a2bfc299bcf397", + "0xae18e6efc6f54c1d9230210ac859c2f19180f31d2e37a94da2983a4264dbb58ad328ab3cbc6884ce4637c8c2390f7fc1", + "0x83a9200486d4d85f5671643b6daf3d0290b2e41520fb7ea7030e7e342d7789023da6a293a3984308b27eb55f879ad99d", + "0xb925fb6ca83479355a44abbcdf182bfac8a3c7cce6cfc7962be277ce34460eb837c561257569be3cb28023208dea80dd", + "0x9583dd991b62ae4bd5f379ccd3cec72cfae1c08137ddfbacc659a9641e7d5a82083de60005f74fc807bd2acd218d0789", + "0xae73bc32e9ff5926e1e06c07a3963080881b976c9875777f8e4cf96af91bf41bdbed4bd77e91253b8ec3c15b4a6d3977", + "0xb2a3ea90aa398717ba7d8c46743e4c487b63c5abb140555d8d20e5115df2f70d3c84a2cb9a5e0536b2d93d24f271b38d", + "0x91d119d3bf1d34cd839eb69c6de998b78482ab66bc93fa97e31fb9592f36cdfcd673f52366f8c8e8877e313b92d4a2ad", + "0xa1907e20120902cf68912cc3046f8806cabbd7673e80218814cb088e080dd93b5dccba395b13e0025f5755c183276c3a", + "0xb2e2011df72504065ec4c12cbc2137b95cfcd1355509671feb7b00dbf7f8d500476a49754cb7fb9219cb5cba7c8afe01", + "0xa48589fb7a74a3dfd782cb3503e6294a81dbb6adb412887569f9408e9079371edbd9822388e0b7ec8d3297ba270f53ef", + "0xa203909bfe196ac65ed3e6800d577b6ca5c8fe1d40f7f925a43852951e38883f2ffd250a9e16fab3ed3dc1249650247b", + "0x997ac293722a8b98f7e819f8e6c2d4c5bd1103b82d489d8b8aabeb905e95450b9b75bd61442cf68cc957212ec1c55617", + "0x9895a3de62395c33509b153b7820bd94fd2b011f0cac135fcf916482f1eda272ecc79f83a61837e99c3a3c4ab2c5c2a2", + "0x98c2ece4d49a64ec8e06407a0585081003bcef88af35210e22eab91169f8f0c044d611494b755e5bd915804b1d857747", + "0x8bc6dd083b36d076ddf0e0bb1bb87cfd059283ddabb3886f02eb7e27f1f0539b2819527b56b5c13436523c4603ac1d12", + "0x85ab8b7a696333c82dd5e179e12b2e127e67d911de609ff9a03cab95cbeedb1f364aa1f2b5e59353e4ba0d177f996151", + "0xa9478e214afa68c395aa2c7daf8ba1627feb71ad6d8bc7339734cdcdd5a42838e032736c28e6251c808d5a4875ef0d06", + "0x8c53f62cf06a35321c8af3871ee4459768d0745ebf48942b9f464206309f42fc7b2c50f196ae1e43b664f0e2e718a23a", + "0x8ba80662f6642d8866e832ec8082a4204ebc993fc304c4b794666856de0407620131a18dc053597bb40a3de0bf8aca22", + "0x8c8fac6b911785d1561a985580c03fb2ebc613ae33e486a92638aa7d4493374118d9a6d9d99121e29c68c3d67ee4e3f3", + "0x90f2c793eee07ad90157040b30558bb3b0164e8ddf856389d6742cf5bd1c712e4c6a8e5678da70a8e9e242ec7864117e", + "0x954abed8f6d58896b7f6438c9780236c1c83b02d60a29fa7361559e619e5bc9d67b3646ee39ffafe2b3019bb3357fb50", + "0xb79874f757a33085e1e751544de8fe3afbea92e0234f9c00254c2b36115a16ee46f085f22aa66e0c9177e5106f51b03b", + "0xaa148b287cf4f60c64f774282b421aae075f0eaa93a45aab4927750f47e2ef0b811d1846bbb15eeb2f293c80a7612e83", + "0xa588d8825e7b0168d45499dcff6faf0dfe1ba4f090fdc7c06d50344960c0121f10ad109b0b9d13b06ef22de5a04eef87", + "0x8f61ec93d14ebfa9c31731f9ef0fb8907505fedc79378e9a3f65c27bed4d74b41e129c97672ce5f567d897befbceec8c", + "0xa008218633f1da10efd01c155f7ed739faec902da6dc48e9f19ccbc8d32bb318d71806285cf2003de2c907bbdd4f8b22", + "0x88ad82c66f7085632d7e348d69da84200c53594553acf5432b50dd1e87f410c802dfea91be3cf804e3117ce13103f23e", + "0x8498dba17de0318af227a3f9ed86df37a5c33f9a538be9823f8dce4efc3579e8296cb3b7200cee7c5e0bfd9da23a4b69", + "0xb3c0342231dffe4c9bc7d9265597bc8cc4a82e2980ac6d1407108db5b00349dc91d5116fab51cf2802d58f05f653861d", + "0xb3f2730455f9bf5a058598bc60f47740117ba51f6a767e1134516a4e42338b513f377027acf8825da5c4d047a62984fd", + "0x816360914fbc9d8b865157bfab07aeb7b90bb5a7c5cd64847b1c3184a52266cd3f8f8f3ef99309ba2edc4622304bacc0", + "0x8fd21b2315b44a52d60b39ebc45970a47b9495f42b88217ae057bebcd3ea0e2476c0c3d13de7f72016ae12ae966a008d", + "0xb62014485bc217a0fe892ef1aef0e59604ad5a868face7a93f77a70ba3d7413443fbe7a44552a784d8eae1acb1d1c52b", + "0xa905822507e431b35f56724f6c8d2e93b0607ed7a4533073a99cce2b7c1c35367382447073a53036dfdb0d04978ccf2a", + "0x81672e39c2b31845142963351de3d9cd04c67c806fdfe77467867463dbbd8a9b0e2400ccc55016e57cbedb02d83a0544", + "0x90919c970ec668de8ec48a2a73bb75cb94f0f8380c79a7909fd8084df61ecd631476ddd474b27103c6817c8f3f260db9", + "0x8fbe37dfb04bf1d3029f8070fd988fc5e4b585e61eab6a8b66caf0ffef979d3ed6a662cd99468ce98ec802e985da5fad", + "0x950939aabb90b57a3d667f9820880eb0c4fee5c27fe211ce8ecd34663c21b5543c810b3676111d079ac98644c75ee0ae", + "0xb06201ec3c3cfdaf864a66af128effee8ec42d25f1e173c1edf9207979fa52c871757000c591d71a9b6cde40f5001a06", + "0xa79054e8febd0450c96ac7a5fd6bf419c4b17a5926f3bc23a8616f0cfbc2849d97470174cd1baa7c739b12615334b6b7", + "0x81c7391b2a1844ed26a84f054b5f03865b442b7a8d614cd44805b5705fe6a356ac182b66a3c8d415132e389efac5f6b2", + "0x825af1563d0fe53925ec9ac0df65d8211b333474e59359bf1bde8861eecd03f2ac74534d34b7e61031227c2fa7a74e1e", + "0xb60dd9bf036f1825295cd2014ef1f6d520cf729b4d6cee0b42cb871b60ae539b27c83aa3f96ee3d490ec27ce7e915115", + "0x89ca43d5b7f3622b42df7887572297a7f52d5204d85e2e1ac6e5d7aa7f8aaea5e3a07280477d910db025d17cd2e7373b", + "0xb93a2bc9b1b597f0e514fde76ce5bfb6e61eee39cbf1971ea6db38c3ecb055e7913ec8cd07fb0b0ffae3ca345883101c", + "0x8d45546bc30266b20c6c59fc4339eb633155aa58f115a8f976d13789eaae20a95b064fedead247c46665cc13ba856663", + "0xaa8eacfe00e8a4d9815de3f7619d9c420629ada6489933ca66a571bf6c044d08b391e0d9eec7d1cbebe8def1e7523f1e", + "0xb32fefc59a0d0319ccb1946b351ed70445d78d9fbb536fa710d3162b9659f10288f12d82b32ecc026d55f16cbad55441", + "0x99c7c45c34044c056b24e8f57123ba5e2c2c039e9f038a66899362840cffe021733e078866a8708504cdc35816cb335d", + "0x80def162c134540d5ec071b25ccc3eef4efe158be453af41a310b7916c49ec0ce06bb43dfee96b6d77339e11587de448", + "0xb5f2fa4f68f6a26bcb70d8eab62ad73509c08ee7aa622a14b3d16973ffff508ce6f1aff9ced77b8dcfef7319245cf2de", + "0xb4d0436019e779c789464716e1741c189e8945dab7f3072720bd9aa89882fa5b085a1755c48da21541f3cd70a41b0a71", + "0x931e798ef672e1472f4f84c727a101e70d77b3a9f0c0803a5220958d6bbeb8aeeb56c769ab472a3d6451249a13a3f56e", + "0x918c10a84de268aa8f1ba24b38fe55ff907be07b1e86b4a4adbf305c0d705c1cf5f65ce99e03e11676cedc89f1a4f331", + "0x8e55a8413b823715ccd92daee357cedd797e69a0e78b6fcdacb7318646b9903dfe05e5501f47b3c52e74055b9eb619a4", + "0x8b329bb63e6c985d7d072dff4680b3f8b1217ed20543277386bd30ec25240d9dc378837dcd5cf4fd9548658635f4c537", + "0x8c2be5386052b22986b33dbc63c5afacb6d0095495564ba4aa28fc8c880a3c78242fb083248d788ed928deb1e30a82c2", + "0x83a2b7bdfcbd25d6b059f27218e009ecb5ecc4da68ead885e00216411d8222062ca42f21c4d9cfa19c31522080af677b", + "0x9620334d2633e85646b2e2fc48dc6c3f09c64ef1706ed78a3bb6ce1f6b274a727364df71e97531dfdcb392f70f27f536", + "0xb6c84970ec04545121ec3b79376f4e45053c97e8bf2b11922cc2490a429c38735466097ecb81cc9d9692c74d2fb8abc8", + "0x8e55d707dcf265c5ae29a32c27ce66f200fddb724faa5bbf145ef42280ef645fa2f0cc3cfe2db8599b26c83b91e077df", + "0xb910b96b763966402bbebd68a32c15a225ec21e1357fa298478c5981a4310e556103fef0c73bd8903e11c4ed2c065647", + "0xa8fd933a0e9fe8c459809bd93b8ce153e2af55df94b61a1490736b19c89469954da8b72dbd072d798fc06fc3d7a3d60a", + "0x811b279c113828e114fd82c2070caa7eb089a46c8cabf865f9c77354a77ebebe0c4c6400dda0e66dd017cfc44d76851d", + "0x8ed03e91c331afb3ad6e42767e1b3e8d3a35fb831805ff1b5fd3e91878e04027ff5af1165a3ac295f1578faf2c83b581", + "0x95bf53683d64a0621bf1ca6ee17446783f6c535b7a54d6ea57723487a215759a54f886597a55dfdd560424e368ab2759", + "0xa9bea378768fb1d7ba365a16531c51fc1975f1c73caf2a0891da28509805fa84e2a8db7c6ccfbc620e9002317abf174c", + "0xb8308250891015deaf851c4e5a4cf4704d104f94064418488d7e3076d49f36240dcf6fdcf83f45fe8a1d97fb02e3db59", + "0xadcda6b63da21f4074f142f8e7f3a2274f624c733e3a4001054a1809711529c61356aa087f73aed877a58ccb41d38d12", + "0xb80e7869239ae26d1da2e6683f064d1dc93cf4a2b66e9439b3ad9b25324e969bf98014760d29e6b8de7ff152ef498d0f", + "0x8e9bf968911df3bb5e3a7655e9d8143e91ee87f14464d7ba9c86e1e31b03ab31b91eda121281b79cd974d9ed2657e33e", + "0x9007277e8335a43e6bc3c2f5f98c0ba7024a679b7156aeefe964f1a962e5ac82154ac39d1ffbad85a8f2440f3c1e354b", + "0x9422b9d670e997b7c919a429499f38e863c69c6a4d2bb28d85e36ae0895c620f68b71e39eba785e3d39a45be91507757", + "0x926094e01132938000d82dd9a571fef5ef104cd25b4015a25e3442af0329e585aaad5472f0e7a69899ba2d6f734b40aa", + "0x95552d8057f7e32c24d69e4d6c51c98403f198a20c5be8826254d19cab2f84d5758e2220cea7e38b7c8a7a23178fd564", + "0x8abcf8dcc8488bcc9ab23c51b9e7a0d91dfc7bebe88b7ed370ee68eceba643e939c5eae66a4aa5fe85120751780e351c", + "0xa91bf8198f029e6a4cf6f0cc39b629e9aeff1c77b8739e1d5c73d8c1d3fb5c8f6f23e27b435bf10b5b4ec1cf6a7249ed", + "0xb932d87ee3a4b81341511f90fe5aa36c571e8b914f25abcc33dd40ca67a3f6444fe9362c1434744e4af18d6e045c54a3", + "0xa8e960c2be9b1d805d387b3ebe2134d421a65f1fd4c1b4cccdce78f9926f139eea78e3afb449b3d6dd19b5d16ace48fe", + "0xa7e2f57cce509fe66707eaba9b4c042c1be93fd6034a9b51d1d30c45c4363eac79d54663d525c9873ab0eec0b1cc4ed3", + "0xaa162a31c2078f4b080199debf24494a8dfdfb9d8fc85b198a861b12a629c73128c55a883e4c2de3dfed6e0e1b83eeab", + "0xb5a4d075433eaf4115717a84b4dc37f843d44bba0bf820c92ecdedd5afb61be60f7708c8a151a678d9d5c0ae531bffb7", + "0xb56ab96f7a463c0079e05dc766f3a6a31cae5c5044947734ebe0a26e01367c6763cc8de6c2ee2f3b8218f05bef217474", + "0xb60792ac506b901065a8bc0180a86e028fe34b62ceae1ad640c759538ebf3a2ad9c8c927d662deed6f489ff3ff7813c4", + "0x8c8c2cdf075504d12d441a58542e1f8e4bdf92b3ee4775e836b2734c5ec1e3df919b931386417d04489a1dca806c87d2", + "0x8ed78e91e5c4a68894cefc2f7fa71f02e5e12d40f1bb74332139bc7be4d92c24e07d5ece0e82150ed474aa1337af4c18", + "0x87119c22ff8aa31150bde537d863cad661cc5159b12f084cc319224c533f0deb28526ed8568d00a1441e7d8bb4f05673", + "0x83a60ba5a9cccf22cebadf7318b706c9f29abd25db0e2fc1c802965351b53cbf316df72ee3e9b2d3ae7f3c4494cfdff1", + "0xb73b6a9fdd3e7463fbdaabc9a885b7c82201ad867d1bced1c2484300a01cbbb3f1e21afa95d4c7cbb6cb983416b63b90", + "0xb1d89ad16981ff9217708090d4017662d8838f21f3a3296cffe14590b533905fa06a20e40dd497bd291fa4dfd1bfc511", + "0x8abde560083e071a402e3c7bf31930f537f67d2a7bbc734a7480b1b760aa712ebd1cbcb65b00e11e384e980222fe14a9", + "0x89c731d8f31afea8bdc9c32527bdca257f2a840764d40f6e49403b8e75ae51017d505ea4fff91bf28b6f3a1bc65b8bbc", + "0x80e9ac8e077e86ad050ee73dfce268a69564ff1b8419e9c236d981fe7a5f0c2bc756e8603ec604b3b9e36da8fe10a49c", + "0xb4f1eea0f304898b1323c6382732e6f40e556bfc68af9ce73f6d54e92f5f23cc4f78eb3f43d578d81e7627fb40f092b3", + "0xa0e3a8d1348f8f153e08ac4839232d75d1d6e81b5de184ec4724f8213baf98d3fe739a96f6b39d79a053b628c3a09981", + "0xa6915ba0b52ffe4a381bbb8ff3791d9d3b848bf89b3bacbb2a7d2e5ae21f1353cdc304b3cb6e82416f7e604035c27d7e", + "0xb2c4c9cdfdd2fc9a340ba3ade9423344b9f429e8c7e20a8abbf26400376e312f3ae35d1c456be99dfb5c02fc8a36cbfa", + "0x9657d57ca0641825a0aa5687f3f87659d893f33aee819bafa5b1ca1db554811c1c844f971e278606e3a2f096defdc67c", + "0xa4ad24d0a557704ada24d8e27a15604bca28679e260b2c69ccc8e6cae5499866724b700605a90df7dfb35130756939b9", + "0xb18d9ea6682f73a1f99a9a4fc98c38fcda02c1a18e8c5fc080cf935a2ac877dc5223fca273dcde190b906178d0fd05bc", + "0x8ea5fefad0799c885f50ff10d94bd0af5b99b0a446cd1f367ae5ff529cc47e09f3018115f3c0ccac2fa05bb65b84945e", + "0x92450d52e6c7d13ebfcdf5674d6761bbae2fc5aabc865d35d031b588c383e0a64cf69a73dc93948632e2b98f74a5ed86", + "0xa356f171a98df4ec5a96d556eaccc6ad34b4238aafcf0e94ece27cdbb491749fc9692e78b84dfe80bdef2914079d34b5", + "0xb918703a4d3507d266414712ba8eb7ad17da07cc5f952b5c62ef130cc6ed1ae3bf01237fc8848c179725bdddd465b301", + "0xad2b0554570bfc9d97510cf59bc38e10ca54a93649c30ac9919bd0255e43bf525ab11b74f78a51ac0973cd0c5a5dcb54", + "0xa7ecaf4b631d179d32ac1632390d95196a0035e00da6c0e6e13b5c09ae44b15ae6c21538b5a31b73bc5f650ecd979b59", + "0xa37704eb4d728df2a367e59fcb6c26023136230e37f3b8a2f3ceeb1467f5cd30186fc0116f98b64a8146fd2c5903e8d9", + "0xb09373ce92314678299ae10ec1f93c702911beb4115c6b5ba6efbcab9c7afb599f59793912df70a98868bce6545a33dd", + "0xb52a878a1393094fd2b93f2d1eccabf2830ab10800ba4cc24dcc7849cd0978733263aef2fcb766a7cb575a7a99383db8", + "0x8dac097e006fda4fb9d6d7ae52adabd9448ebc8d5bd5b38ac0c4ed38ceb510763174f7adfb0b473c38e52147ccab4239", + "0x86b19c41efb949937d74a7875549ee5e997f9fdac7f7198085afda233cf74341a38d0ca3767c76cd35f875b89a35f78c", + "0x99f0d927e5ad25cd134f1c70b72631cc6b5cb4ddb86c0642b900464e33d971213a5239dddaf71f7a42f2d6d02a12dcc6", + "0x8355c38806c335d747d4e97f0083fb96585677da18b409a85175ec35dc3f74671817b34203eb18c2f729717ce083ede8", + "0xabb3603adb061a036eae0afa5f23d79c3b62442e0e3bcdeef896f88995585c1105cd3065410368456a4d36b5b0485a83", + "0x9051c5c0011784885187d04749f774b9b4f6bc594b0e4e18226de79dedc4d7aefa3529c3d2c728e180f96f3e204d578b", + "0x91888213e7d321d0bfac884edbd5cb756b280753bb5f8bc6acfc208f525757beca24bdf86fc68d3d8736ef176a960b49", + "0x91258bd7ce6e3b7516fe2f5391a368d826da299e0e99b1f82eaa44b62b110ab696adc92debab8ba098a52f38dfb3c5d8", + "0x96e3907340dffa9da3602d3b94bacff7e1bb8649edd3b9bbd06e1bc6781e78f91ababab12c0b9be7c66dfedc7001b66e", + "0x9513555688fcfb12ba63952ab36a67b36affdd71f7b843e8eb99ccbd45421698024608233efbdc905eaeb26b334b33af", + "0x9913ca9bcf11eeb408da02e4317c5ca0010fb2f4490b282ddb758001c08b438c3b35351a8cbe10b7fffc1293ccd22d4b", + "0x85dc2471860ebca88e5a2766161fdd77f926d2a34825d1134a30418f91a741759668e32fd1e37c415d07ab5824338e8a", + "0x8b128917e828a0b5eb6fa8ed72b52fae2dfaf74febee69a2e2f87e8df702f0c5bc0fb620c8d1d2a07f35a15ec9c0f5a8", + "0x964c39e7840c130b01bb481ae7bfc92682b0f124c9c383f9dbf3027f2249151925f4faf36905af476a54778d69da3f48", + "0x80671ece658cf850e522d46d25678f934ce6df043f25f8707235125765d40c2eaaf39eda6092f75039b22cb58bf2c29d", + "0xad4bb0e79fdaa340b1347a46b0f64e801c72a89770dda0a6e4bfd35f2df5146fce9934e4baecb1c2671077c771eb8089", + "0x80b3bd3adc6cf198fcd997f8867d2839a2eb28f57390352ec423b8a14cc1f2ab21c6e286505d6a21fb134dcd8d8f11cf", + "0xa26d46a6b8a75748895a1d599e7fd120d896340e79813167a400b2fe463452532a4cab419074663fe1d29fa716b76a33", + "0x82b1f3a8a1df29207d7ff020809113ab06080a7f0c631f76ad33f47cdfb6a567143144df97b4ed7f676d929195b04bba", + "0xad96633a3744648ff0a2e4491e8219c9c6ba6e655cb058c36320a8f72cd5f72c00bddf97083d07650ea9ddc005fc1ff4", + "0x91d0783788626c91662359dc3ff36a8bcc6831e3f4114f85c99910256b1d8f88a8612f53c7c417d55581dea486f38926", + "0x84edd9e87ff3d193ebb25f43474c33fe502a1e2100fd3f93fda6520f5e42214cc12e9f8045f99aa2423a0ee35e671854", + "0xb55e06a4b1fc3ff9a5520e0b7c8b5ac11b28385cce78d91ce93b82f1bd7f7afdd4195d0c13a76e80d0ed5a4f12325fa7", + "0xb0b15c7ddede2b81d9c835ecaa887650622e75d0d85f81b8bbec7ef24e9a31a9c9e3de1f382d8c76d878d1b01373f6c8", + "0xb1adb47c20f29784116b80f3670182d01b17612d5d91bd6502b0dcecdcf072541f582aafc5e7dd9a765cad52151684f4", + "0x8efd1018df9c9e9814a9c48f68c168551b999914a6719229f0c5bf0f20a288a2f5ba4a48ba966c5bffb0fbd346a4fcc6", + "0xb34ea2bd3269a4ddb2fbf2514401d2712fc46c22642f3557e3b9c7acbce9b454dcf789573ede9aa14f39605fdd03f8c4", + "0xa9e1428ce24eacfc460aec2e787c053327ba612f50d93510d58b2cb0f13291ca3d16358325ab3e86693fe686e4f526f7", + "0x91eac7361af4c66f725c153da665a3c55aca9ae73ead84ca2662cf736fe6a348a301be1954723206dda4a2120202954b", + "0xa6f02db89739c686407825fa7e84000ceedb9bd943e8a0908fef6f0d35dbc33c336072ba65e33e15ecfcd5714d01c2f0", + "0xa25666faa12e843a80365c0fef7d328a480c6e3cb7f224763c11d8cbabd0e7e91a5b647585ee905cc036afca14842bae", + "0xb4348576439cd2e48c01cb9cded7cc4a0ea364ab936dd679ddc7d58b48807e7fab070f2f1ea88595b11af4500849026a", + "0xa8c6c731e0d0464ef7e4fc1b049065eb4ce100c01e1a376365c636a0b23851022bf55805963bc15eb57434a837e81167", + "0xb0952937b154e3a4c206f96cd96c76ba37624956b0e4d43470bdd97b4af878326b589e3eaee82fc192437123096799a2", + "0x97d07ec31ecc9923192e48d37df2cf08750050fb452dcfbdb350fbc43e146bae3590c5b732b31ebfa1ce5d884ad5ad57", + "0xa69359aebbfe4cbc4d39d178150039fbf284cbc0edc68a6bd635ee3a1c76569a4a575c907fff691b2a4d82a384c2945f", + "0xb321c2c0f6b5902ee9056cce7404d858da9a573d27348c1a6bfea29b2746f2aee7abcb6192504e5a583b0caeaba117d7", + "0xa74e738aa6eb4eea58855ae6f422af22812fb388c83aacca5bd5fa4a88d4c01463174a229aea2830c348dd9ab9307854", + "0x94306a3b106bc1644346bc45c05cdc8287811d5c86cad691bde0c65d6a686eb9c0ce79ad91baa4547e5d058ae8bf7310", + "0xb64140fd77a07633e4ca8d60786452311dcdb8ce7095ba51dad8486f57c3bf4e69bced92603f71da992a48ad817ab275", + "0xaffe7f4310f1dc68e5e3cd640bedf864f51bfb46bb752063bfc18e95930021f784e509261ff9c560f53000c361b142d1", + "0xb0d2fee222c6f963ba3385547f921a48964da031d737892604f8f2677d4905dbf615046db57eae6c6dd756709ae6932a", + "0x81700c66aad7c2e51168e028b0fe086dea75d3b17d93a4dc1f47a6a0f025df0bae1c8c997901837ad859a84197e7bb00", + "0xaa4ac5fdd602f8b79cace18690e67bad557a93d00c0e295074185e8c6b4059a65495d9971685de2fc01d2171ac8b706a", + "0xa8becb3a64fdf35d65d2857898dcf8053b5057a73ab8c5bb5324af1a8015cff47efb85dc3eae7364cd5c850b7962bedf", + "0xb72ea09bd0b72f8cde3466f359ea69b194ede93dced534efba1b9ebc6f3bd53942fe2965e992e82edb6050cac4ed88dd", + "0x85bb8dd7eef023a251fb6f220af54687747f4c91983ff728163c4618ffac40ee6edc29a0aa6d455276bbe017f63757c2", + "0x85a485254a11b4c4a943d9ec509c0dd1cbfc0ff5273a00cf5c9f0babec973efb15348e5d9451b548293d778e3a2b62a5", + "0xb109f3ac809391e772b589c196b013db69a9b2b10ac3898feb70b986973731f30722b573cd0c9324158ec20416825385", + "0x8a4eb579a840d438bed008644f373ea9ba2f28470d50cf1d70af38ba0e17326c948527b1719dd1bd9ac656ebd5aedd10", + "0xa52e9d66ead5ee1e02ce6108e4ded790d8ec83164a0fa275ab1f89a32200726c8e988d66df131df9e62dd80203c13dce", + "0xb541cee9febf15d252475507e11d65c4b7819c26cf6d90352f5e8a8f5c63e254eddf22df0c35a7be5b244233e8e4ee5e", + "0x8153c297772adf4603c39349142f98cc15baeccaeae10c3230ee87d62255f6814d88d6ed208c368d2c02332426589748", + "0x970dc9782f1828474e9fab7dcdec19aa106725465a5844caed948eef5c9e48199c1b6bc1a637ed7864116927e84bc65a", + "0xa975a920624967f4ecc77ea5d9869c434caa64c330024194615a8d0640c5d4d4fb139ea11a0c73a5c6ae6dd3fbf0ab5d", + "0x811f0f9e0c12acfb4b9dca359eaef3bed18083bad96188befc036ad3143b121fff4777ca6dc70a835bbc4921bd25f5ff", + "0x82341c6ebdb97c8b72910da95c7eebccd1308b6a92999886aab552f0642882d5c7cc60931577d200efd6066530c998dd", + "0x860f7162c2f5fd1c0953c6ce75bd8c52eaa48032b914410681b8cc05e00b64130d1f96ec5a52df66a04c78a9f9f42981", + "0x8a578e674875571fe1a0459843495a5ee1d9fb6cd684b244feb9488f999a46f43363938cd0542879ea18ed14fba10a6e", + "0x8df217aba4da6781f0f5139aced472025523ed6e17e504511c04b677ca8197488e237d8bb5dff7b6b3898cd5a6393dd5", + "0xb2c9230ad35d7b471d3aee6f771517cf3145ad26200bd6fe9c7cf28120e2945fed402e212d2330a692f97bb9ac4dcf12", + "0xb78b89e29e8b782603b222cc8724eeb83b2d9d56bc02f59a3c899ab76429dc721358b07dcdaf422f59520b7e7ab4fb55", + "0x82682a5617843c4ac8d4efb4c3ce715c76c1da2c3bab1ede387db503f3489c1bfdfc07d9231d96f955df84fd225bc81b", + "0xb0f53725cc610e78b8e8a4e6823a2ffe44dd15a9a5bc8151ab7a3787ddd97e1d7f2f0e6efd2876e5f96417157143e3bf", + "0x92c5a93233085e2b244519078770c7192af62f3562113abc8902f9d72591eacf52bd15ce78653ab9170d5067606287f8", + "0xa43ef97dcd9b6ad288846bf31fccf78df72f94bc7ad768baf5bf0d5dfa27bd74ffcc6b6c6ed1d1f09e09be3afa5eaedf", + "0x817d43bd684a261fb30f709f7926cc4e1a31fd3a1a5e7e53ba4d664856827b340d7867e23d55617ab3514c8a26a7040d", + "0xa599e22d3286b32fafaaf79bd5b0c5b72f6bf266ec68948478f055391336d756b58f9afea0167b961fd94234989f0f02", + "0xb70db7d8e8356df2e2070f8d658e560081442f3f3b95e20f4bf30106835d76161101163659d5d12cc0f335fb042dc66e", + "0xb8f725b70c957aa3cd6b4bef0d9647393f7c9e0b7343e92439372f0e9aa3ceddd0cb9c30be331742b87c53f2eb030593", + "0xb2fb5e7762f26036e7e966f4454f886758804d1f4c2da17f3d13b0b67ca337f1fd89fd3cc798b07da6e05e8582c9537b", + "0xa377f944dccc300921e238ed67989872338137fe57f04cb5a913c787842e08b8a1adcfb4d2200abdc911fc1c766a7092", + "0xb82e98a606071c2a33f2ad44e7ace6d9471d5434500de8307b5d4e0083e3a5cbc67f0609ca8055f0ea0ee7501b9ed916", + "0x8e58f9a04d33a41ace4944615041662dc35057e645f63e127cf0d70f96ac307d33a62ce98f164d6eed8536c1a747dcbe", + "0xb5b11388071ffbf57ac47fc195736613b964ebb91cc8e2c17b32646f91d64ea506282b881897fca96c317364d3290de2", + "0xa40ee9b7551133856cfb3904837f9949a9558e59a418898affb78adf1500fd6ef6328fc4422161909aea2c79ad08c14b", + "0x81f9eb4ef28aacdb43e11dfc9aa92ba990be4d3c14b484fa677edad3a3fbfeaa859a7f9322b5e95818240d7326215abf", + "0x84939b2b6bc859437d1a7a8d6ec9a357c6b716c4b4cc22abc274af872655940cfc72c99f5d0283d90e05191fcdb1c232", + "0xb78a5b74a90a805410b6225fb9576d6d73752520f25cc3fd1edf8ea9f6559d3080f9acaa2246809b6a66879cd2ae446b", + "0x8d0a92baa88bf38dce5385ccf15d345b28e2e5d0a2d469e689353d80eaed8e8408933816d70ad752f226c59a0d5b5f0c", + "0xa7e15f8a8c1655b7b346c9488cff278c793505379b781b31b273b4bf09b3bdfca1c8ab2334746075d636b2e05859f215", + "0xb70daf14f2adce03c7b92d6aa181f0c507a80a37493d8dd12419d5ed5f943a98099fefb46ac827d6e4efb9b8233c99d6", + "0x8c2480814661744d116fba7355bc6b1914975e44cf0e976d50b6a20092bb1c636b7b44ed3fe8d63b5555ffc89fa759d6", + "0xa6059528a4fed36abb74ab992b22a4f9bf1d05c5de2bfe6837b9af1adfed98bc37ed7481b5a99675d432743021fcfdb3", + "0xb7e19f1b25bc159e5a769811e773c3a8ffe8be8ac77ed0b711540915e5c6e7bafdb407cf9b85c551f67fd621ce8142a5", + "0xa2f66d4f7d16ed3e7ef5fc90b42676c61a98ff18bd26ccce91de03b6a0130c1db17a6bc57be135e410a76d2255b15813", + "0xa139c916927dc3d3fb83598da9217ca64f0ae127215332e9a7ed82be923b89a801c44580d5617297175f9dafb1c4eaf3", + "0xaf08e1e1b04ec95366a12d99c80a9a9ac40ac984a575dd0230cdf4eb346a7686da55ef0a276f3356f814af31f9cbf1aa", + "0x98840aefe287369221c0721cd7c1b15b1d670c3cbbfda191cdb5434bcad757e59c30ec82b2d8c75947405888d44da435", + "0xb7c61c8d42daf2e278a12d8f6eed76090b71c82275f8b33504aba75d95103840e8acd083e97a5a5aa79897876a68940d", + "0xa0264048d2a2061d32eee4f661957ff351e78436bf49ef973c059612874ce9c91970869d011dc13a5b7c754476880a68", + "0x897199a4d8db8aa2db5d9be3d4f4312e41fa0739eb06c62e2e046c4b9be829a447e5d47227e2d96195d3b7b66eb59da6", + "0xb512a9082881f5dc90b02f8bc4f38b133348c2e933813852f6a8e7d8c270c9ce68a5524af7d1d3123e53b2d02a53d465", + "0x80b332469254a96f53c95ec79bb5a8bb1c387d40e58b73d72f84384c696ba0d3c81d6ac90be2979c364c44294e90432e", + "0xab680c2e547ea5cbf95bf813020beb461d50ee4341dea944eb48f6a8584d35682d20186e3b190b849a1ba25625a7f499", + "0x9070581993a0531d6be372d370c2e4ab2ee53f30e04a75ae61ea0fc2c320914506c4d2d4b4487c1f8fa88356fc45c895", + "0x8424303dad6b4051ab633ad27ee51783b2ead61c5a6dae1eb3ed72fc1f36e2a9b1f315504a4bd90f9664091f2f403d4c", + "0x82225611eee626556553b9316dab4043aff241a81826a33aebd9864a91e299b765ba1fb43eea2c2047e6b75b6d7fe3de", + "0x8a3fb221c616ad55c352dd5e0c09ee892022013d6965aef40d4f277a42e9fa01226fe973cb99aaf6ffe4f4f348fb54d1", + "0xb07c07679aa51713e8a7d7bc304dc15ed5664b66bd371877023f3b110b3927e09e259ef22895c4001421a69c6c013cc6", + "0x83556c76bdac0dd8db6da231b863c335be076e7299802eebc259e0818c369f933a4a4b18e2df8ca07e82f60767b462e0", + "0xa516f659b7915d2f7cd0f0f5ea2491b15f0c84dcb191e7671b28adf7cf14a56d42cfc0da94b3c269b45c535f6eeded49", + "0x80d7cc6f26066f753041b17ff1bd27f6d4b5603a43729d33d596e21a67356db84ca9710158089def425f6afaf3207f9e", + "0xb802a47f9009dbd48851209ea1e2739020e717f0ae80671d9f97a0e43de923273f66b7fcc136a064c8467372a5b02d28", + "0xac92fec1864a8a911633f377df87aab56713876316d48240fefeee49ab97f7406c22e70f4938b5912c5c4e766146b7a5", + "0x89224225b9835d04428b0a74edbff53dee2be285ddd1e5a3a8c37307c0500578155f0c4052e4bc8be04c56862fac099d", + "0xb1d3c8492fbf22ea60732745edd3b0163ba5a20d1a3315e3773f2540ee38cf308d42ec72cbb3e3dcea457d1d132c3904", + "0x8bd00e38ec30ee6c44a0e5b222f1f737c9ed2a4bb9225f1741d6334df966318c8a0fd2fbb109557fe8c9479694b8d8dc", + "0xa930ce5454efc0b247dc148aff869963fc5c240241d5590415cbd36634801a04d3873d93635911bb9c0c42ecb005cc63", + "0xb83d4f80e9e0fa47b42175df74935ba8aad2e559b80e84478ab1685bc3eb65d51b93e5738d5ca968cc055ca0c552a03c", + "0xb3ae21258f98051f13af3878b8103bc541fe6f20b1c3f8fb4689ddb8800b3c25cca9b55f0a4104bdf15dc4d5844abb8c", + "0x831ef8684c1cd446c58c59d0152aeade5cc305bca6aa296b92162615f052ba280fe289edd62fda6d9f0667c186445f52", + "0x97bf9659b14f133885916733b7d4ac7e215495953caba970fa259f7bf6b79e661090ec8d79e1c9ce8dfb17e8552f93af", + "0x84d5a89cc2332baaaf3d19627a65f4b107f8dd9228a1434b327732f59883bb54fb8ce60d6acd026ed4b0e94e545d1c33", + "0x8e66cb743f95ca5486400b0d89d02e20b98044be1e3a12983ff9fe086179e5a0ebf4dcd5098703191552e9aa660a6de5", + "0x87b4cfb35bacec805f8148786788db84eb8f4bcecdd0570ecb592c705450ce1a90b6d183d37ef58780ede3995be67497", + "0xa72a4fece5478011973afa543f6d8a8ea06a64b241cf7d8bd81fa3740ac2a4cf10e5120abcc1c1101f94da89507a40ca", + "0x89dc6001a96adcd2679916f43dd19ea00508c8d5dd6b0090eab7982fd2f3571b62f3029588a0649e73f49124525407ea", + "0x8ca75edf1259599e873530eff6151c822a4018e71a340534219ef8641cb6683215891df41d4e3c0ca2560e57a7aa913e", + "0x9282d32f868e5ee6f7fc229dda5b94b603476de30cec0a44a30edf396b52dc0ebd472b8f726d4b67d76179fecc1666a1", + "0xafa24704223707db89690bcf9761f07a093f6009ca9fc945e0a8801fc29f9f51292bf95243e466fe736088af36c55ca6", + "0xb51332508ddd9a2610edd2b0ad120272ca342e96c28baae37a2c4f07e689303a46c237712d07e446b1d67c75aa8ce32f", + "0x9219249f3799dfa4eb4770ee323f821e559e7406bb11b1f1889286221b22c8b40ccacbd9ac50ea3fa9ed754860bc24f0", + "0x993515270c128ede64fe6f06755259105d0ec74947b7eb05924a375fa5c6d14822f3d7d41dd04fa5df8aa2aa205a1dec", + "0xa83be4c2511bae430034ab15b194ac719d7b7041f9c0e321317f513a97db39e97b9ee1df92a1962f265b7a3e98cdd753", + "0x8ac7feaecd26f7b99fda3ed0b8a08bd6dd33ed5ba687c913ec0ffc64bbbefcda6f265072add4d944f2005634601ce68b", + "0xb4e3ac6b09299db9e1a469f3a0b2d8d724ee47a417a517bebc4c2ac3efc5cde086b57b9aa4efccdef2bcf8f456d973f6", + "0x9262a24a84fb7b2a84d700f98dcf3fefab8b47293778c20bfc356860cb84e0bf102bae9facd9986d92d1762e0a955836", + "0x97be2041c42bd25e5eb519279163b0857f8bef627492c27b1182f8bf0033769246be5886422cbd2409c08a2615352465", + "0xb0b87d059a00e3effa2e5e4925da913b245785f2932ac3ed364ad19a064d3561b8aa6afea22c951316074f0df179af36", + "0x891644b7b3321b06a2a40cd96c2b8b29d81cde5b48546483fdda439000982a9cbf1f6333fb6c089d39da6492cdfaefe9", + "0x8da9149b7f4783a24240b7b9c7e6df4abf8d699d3834e31ee591489bf4744141ab199c173db64397c1f9bd5f9c862ca1", + "0x8ad7f9fb2742654aa2964fd468e7645436cefd1308b064fd63fdf0d3adb4caf6cfe5426354f6cc284f208b03d6b2d918", + "0x8435e4668f7aeb027100d21e4e0b6ee22b401d21966a3736b95610de86c7e2f2c9ee5d0f901353675eee5ff458dad69e", + "0x9010895f045538bd11b47bb8996f27198c8d6cffd3220569e6b7407f68f35c47d1efdbcecbf9b5e241c3c2879a4f6936", + "0x92a9aa443b5ee7bf13b6f43f2d8d8db7f6f33fd4073a606ec5772421a55f464831419726130dd97829a7d4bfeb1ab078", + "0x843f3266560be6dcbe0258c3c7d7e332330e10630c069892954290288eda301e247f479505a8a1bf7e59c99ccafd104f", + "0x915bd1dad808f8a568725bd243f80b5476a2999d0ef60ea3ef6e754155bc4121b2b879d01570725b510c5a3f09cd83ef", + "0x97250d781815b1825be192714884630e9f564b9bd737d55b8ac79ab48d0fb3ca53bd21ead7b2fa82a05f24083f25645d", + "0x81e2d52333391ff2faab39611689a62d6ead77039e8703f4e012d53eea17a4d46f2e3342e44b6edbe73a542b461bda45", + "0x89c9f9fd5f638156b018831c1bb70c91215f4a2f5a73c84b1208bdf6ad652a55df7213336ce12bd910a0e1a726474f95", + "0x92bd02984d090ea7e2f3eb7d36d1e7b9d731b6b047e3cdd4af7cc4ee177415fea7a145205e484b366d84191f06af85c9", + "0x85a86fc61d5d916ccbb219db52953e1495230aaaca63237e9165276405f07ad9644e253ae394f1ccdd231944e7143313", + "0xa2ca5b3fbc9f3530f88c0ed7071ec3d89b272174c366eedb5d15d2b648c65d23c0faa4e92c776357e7c6883a0084d03c", + "0xad171f5badcc99c8ffc9d8b707d792046f86cd0aa478e0e2fbb32fe095f96cd134ca548d1f7713057694dc6b26465315", + "0x96bd15d57da9980870fbadc98c68db76824407dff2700c45b859bb70d98374d4a4ba99e3ed0b0c17f480fe08f16c6b8a", + "0x8300bac69ca088c3ff35749b437215e9e35a16393e9dc094f520516ba57a485def7029d30adfc72bca36eeb285c19301", + "0x8a09e20be64f346668fcc7b07fee9c0ea8094c935cbf4f3a4cdbb613d4b936c1edb9256b7c884efb72393d97c0da00e1", + "0xb1f85827ee6f041f93ab174d847a55710824fa131c9ade9561168c3962a25c617475ebc4105eba6e738961a754442bc8", + "0xa131558f92e215969f41b6a57d1e2f424149eea531723821dd4cf8c54325cbe66b002de2c8287de6b41ab4b5c35f060a", + "0x81ba492b8956f73557f361a856c6c884ebb300d828287d5699e22e0cfa75c8e77a61616551d0be5178263898c461d6f7", + "0xb2608f44d3c22fac8e13cb59e4ade8b9a98c4eb1ec0959ea400c97eb937ae3f66837e91917057148befade8389af2f6a", + "0xa6ff0323b5a18a4becb2cc6b376086b47cb2baffbfd1b0f2229ef2286fb4a34c5cd83a5faed5def7bbad519fcab8a856", + "0x857d879cb9eff22501d883071382832730704bfcc5cd5b07cdce7ab8dc41c565a1eb0e7e4befce8e0e03a4975d3f11ef", + "0xa2879a20c0360c516811c490289be7dfbf7dbd41d2f172c9239f99e3d091957e0446854f9d0f753d90384a80feb6fa56", + "0x83518624f33f19f87096a47d7b8e5f2d019b927e935a9021823fac6564c4f2328dcb172e25bb052748191e75ac682bd0", + "0x817ec79132faa4e2950665712b2c503d7fb542aa57b7b36e324f77cda79f8b77bde12314e2df65c5b5296a6bca9bb0b4", + "0xb2abf8fb7c3690816fa133d5b4aa509cd5a6e3257cfeb7513d1408b12371c4d58c44d123ac07360be0d0dd378e5bcf99", + "0xa9fe1e4fb1574c1affac5560939face1af6657f5d6abce08d32fc9d98ef03186dbb2dbb9fd1decd6d8f4e4687afecce9", + "0x89b2f41e51f33c3ca3e44b692e8a6681eb42a7f90b81c9e0a0bc538341df9e2039ee61f26d2ebe9e68df5ed1bccf8cdf", + "0x8b35aa7b1d9e2135b35a1d801f6c9f47c08a80e48603f3850b425f64e7fb9860d1adda04f92a1ba22d00dd0a26e781ca", + "0x960574978cadedbd4cd9f764bee92f94e08b7af65403de36b21bffc9424bcee845b3b028af2e9e545dd77cf1e69a6a7d", + "0x840aa0f34b5b6c39471f54d9e85f1eb946468c4fc01963a9027cd7864df01f73c2e864f1f07aeed4b1b1af72808dfa07", + "0x834464a84a11200e3c60f816044c254a7d9baed64aed45a17325cef7fd62338e0a26da78d199d30ac3411714dc813223", + "0xb4ac6fe2f5059546f4ad9a361426ead33237b6b9030b129bf0122085c85fe4ccb33cf90f5a7f23c5b708a5ac64b487f6", + "0xa12aa9035464795f2a67f3eaba478d5ebc838ed9e997c7dfa241e1ed60a94b367d3f969ccf0ef02028c35215698b309f", + "0xac8d926492ec2bb68c6d8aa9bce49085d3d266f3d5f1f924032b87c42b44e41da7c047eeb01e4618f9d0f123dcaa537d", + "0xa5142425825d813ed8ce1849d81aa40b11f1cc3daa89a9f798dd83065c74820b4da6122b3308f528b074531df66e1a5e", + "0x87ff55c9f5aae079e7bf24084dd9c6b3bc260727d942d79cbe8dc13341d98525b4ece3ed8169994b56a387642f09134a", + "0x88e680f148ef2ecdcfed33b61f9e0224790fddc9069bd6999e9bede1791e761637c0fd60b52990b6c93e6e5429e483ce", + "0x94bc20bf5aac6e9f1060d02eacd06c42aeac9a1c5635b15a83985dfb03938ddb4999a822e865635201489c7f75601b29", + "0x849221cab7599f25f0b114df092bd5e8c2430503ae959bef1543a101de0790a78245db6a145e26f40b5f9bcf533219a3", + "0x88b6f2c2e7a7954fad11009d839ce50780921f80292320868d481e38d26aecd80fa607e82219a99532d88cf33b39f562", + "0xb0d82947dc23c0b88b86c321b582c15decdb825ed909a731b42d46bc895009515a3dc646c98dbec7d71b0722df82392e", + "0xa2cfb9f7c1a76c8073363c1c3bebe5dc29fa76533caea41046c51ea9bbdc693a121b957cd96be5b6da18704d1865cff7", + "0x8f0ffab9a83355a22683a9d998d1c1089449eb308711eaad4265f05927ec6d0d1ca39217082a0b372e02234e78dbaaad", + "0xab024661e2b2937ad374c8cf2e3669f1dc55558a3a881e9ec4d461f27e0fa92e2bc88230f038bfb051cf2145ca747a07", + "0xb98d9b9ec9eefa56d38cca959ce1aee7b6d4b41a8dbbd34b3f50c0a5f97f84ed2502ded1ce8cdb5895872360d4ba6d61", + "0x851244158b3184a62d2c98d148e2b1102cf0d5500906bbc2deda95acc5e3bc4b4a3344febbb31ce05a56dfee86a74913", + "0x860d9e2cb886bd3620b5d7499d14b415532482569bd45fd76e3e8052d78a73ae4b2b41f139f9cfb136564108cd93c0f3", + "0x8305a052a0fb2bcd41f3aca075c5f7f233bd8f861451d03f3a6e6e31f7d08dd89fe1eb4dd7b238a78b12ddceaad9768c", + "0xadb703e4778c7e14fb83541ab00b5fc344108243ec6827c5d9b302ee68321aa569da1718424e6a57979ab7536d5eb43b", + "0xb1a754b87b9e21aeb86217ec5b4fadb7535344567f1bd15e88ec12a833fed68e26bfbe03b7709ce24ba6c925ea0a0e07", + "0x8c1e2f6bf820e1653f3b8213e9d959d8649196223c2aab57b7ebda094f4919f88d883bcc6a0cd0be335f26f5a2a9c962", + "0xa082deb9865fe8668e91db0e4fd7fb50fb3fdae3e7bf1217ce0aa6f286a624624cf936d762bb2b6c3fead6826694f846", + "0xa10540ca05fbcccdd0a2a66aabab3b36e9bb525794cbae68bc3dace6116f58942218e9d5e9af10d67b5f6fb6c774fdd4", + "0xb81d22c4ab0ccaf447cc5fc2ff3bd21746617e6773bf43257c0d80331be2e8437b88c9c45309ee46402b38d3d4911caf", + "0x84c7c6e924713cab3b149f641dabf63ad5abbc17c1d8ee7802a6630507aa1137f7e034ba1d12ec13f1e31efbab79bf13", + "0x8773b9d236e5fcfa8c32e471b555264692006bf9a869a3c327aed33da22dfbf5780ecea7158904d4d6ac4acfe9789388", + "0xa4c2c1bb7290eb7af2013f7dde78282148593f066b09faf42e61a3fcf81297caa5a00fdbf6b93609c8c5782a0f25341a", + "0xa7bfa6e3f273da3dcfac7cb9906bbe9fa4fc2872b184d79813ee273e6cc4d7f37f46164362707a1976f5b6a2c5d7ed1a", + "0x8b71502019e4263fcda354a0fd10aaa7da47f4abb7a0c715c7b017e9eea14f2b64009b29b467394668c7ca995adedf82", + "0xad7460fba7deccc3f9a7d204233de47ce30ffa55e1e164975cdf06480a6108720bc397b93ca8c959df77d44a1e1f05f4", + "0xa5b8df96ccb7b078a3918e74b1b10da21df982538d2c9313f5129b2797c8a6db9ff8707241ff72d3e9d5983397321736", + "0xaa6cfa6386660c01879656da6c4e72497690708bae6c5cd1d088f443cb5bbbe75561d6eec256a72b9728377eb83ef973", + "0xb9699ce7c5c878e44114ab7a598646c6c7616b8e08a9ef8ec291189ef9945c1a538d2abf1ce3b0da0f8eecb303b81b43", + "0xb8d0fd1d278f53c455de92ec4357885fc6648dc5f276930263da7dc885b4a9628a2113e28b66b1e64fd08189427c614f", + "0x84ad8d262f6ef5d93e82ff6f4af995148eedf6d8e079124daee9b99f506e2968922eac2c7d4aea741fceb7733f20b2d2", + "0xab5e30ab54641e3a44450118b8235554e0fcfffdfbe1430ceb3f7ef33325725741995fbbbb0c16f0875aef0f1e0c98ec", + "0x80e2cf8bf386ebda46045852751611f2af80eca2e910d9ec5f6e2c7376611534604ceafa639272b3d503b02bd66525a6", + "0xaaac69af8fbb87da1c1b7c1b9e59942887ae839a91f0c1d191c40fe8163d7f1dbe984e4fd33619c73e63abfa7058f1e3", + "0xa6194224ad838ab86e84dc80e9b8abb121ae6c3c7fddc476463d81f14168131e429a9757e18219b3896a667edda2c751", + "0xb68f36aa57aedc7d65752b74761e49127afa65466005a42556230dd608ecc8f5efdb2ce90bb445a8466e1fc780eea8c3", + "0x886c3fa235d6977822846b3d6eccb77f1e2cd8ba3dc04780666cf070cae208b7513dc4525d19a3fb6385cb55f5048e2a", + "0xa9801273ef850b99eb28f3dee84ba4c4017c95398730c447efe8c1146b0719f252709d3397ce60509e05da74ed0f373f", + "0xa58c2a5dd13e08ffa26a6c5e5eb18bd8f761ab64a711e928e6101512401ef2b1c41f67ba6d0823e16e89395d6b03ebb7", + "0x91318b564ec8b2d8c347ca827d4d3a060272aec585e1acd693b2bafa750565c72fec6a52c73bb3ae964fdaa479700532", + "0xa058db5d76f329c7e6873e80c7b6a088974522390ccaf171896066f0476742fd87a12fe9606c20d80920786a88d42cec", + "0x9838e07f9ed8b3fbca701be0ef32a3f90752bbe325aca4eaea5150d99eb2243332745c9e544fd1bb17e7e917202edab9", + "0x85a9ae7dd354f36e73baa5ecf8465d03f0c53b24caf510036b3e796e4764a2bc17f0373013af5b9f1b8973226eb58cd1", + "0x896a4ff4508d069a7da6ef7bed66e1080991daee8b227f3c959b4f47feaf75fd1b9e03d0917b247c2db11e105395d685", + "0xa36d9a6a037bf498dfc0e535f2034e6cd433c7b52e520469811eb2e9f04499a6ce40257d2905300df7d81f38d1bba075", + "0x97aac3c5492aca879b4c06db1834b30b8850a244d29296046a84c637d9580c8521ab4752ef814c96f255a139660d7639", + "0x8552bf592a84ab4b356d01643c90347377ebf1f2b38a8c2e55a3f34537b8c7dcbd62e6776d6c2114f2bc2d4344d1567c", + "0x84474ad163db8e590943ccd1dc50b4f444beb8275919b33f53d42cba89831e9d42ce2de52b26f4412e2a0676ce913277", + "0x900799dfaf5eafeb297c7b4f892438bf2a65ce04034d66f8e5cc3836e4eaffe782fba4f4455a0fcab49102a240d1780e", + "0x817176415e35ad4a204b9fd5771bae6cc270f6ff050996cec89efbe461b2940ae5dd3c6c7d7e31b1da5285b207efed27", + "0x965e5791c927d47569bc54ec9b4c5305788aecd87a26e402aabeaeccc03480df46f0586ca2e2a9918885cd03332af166", + "0xb96d9ada4b5a04a94807d71726bd557de94fbd44042d7dba40560eebe8658d1da49eba54499360619f3b2c38e8b5ed6a", + "0xa07b6d641a43e02e7868f30db4dd5069a2f221b4f122ce9b11eac04abadc4f25f3207f1d2d86c7935b1a3d9992ea9814", + "0x8250d4d8ccac846a4b1a9fa392d9279b5bf2283c8b95d8164c3c0d199fec8849eab85755f2a2a99d584a0407742e3200", + "0x8324cf49f56fc14162f9a9ebda1ebda0388d09d8688f1938aef7dbf9505fc119069efc552f68cc7cd9213f96fda2c6de", + "0xa98e6f1e85268dccbe3bf4e92c9f455c58dcb53de1dba3b78589adf2e50e79f8e245f956e0d098eb46f5d3746826c6dd", + "0xb103ec12f266b4153d67b54d8fc079357ee342cbe5008adc3e0689a7f788534c4601e60e939731f49e4a1e24fd589f82", + "0xb2d7681e866420413cc98eae67614d383943e3762d5742cb3c57e26157633c20880eea1209feaf68402d5d33dd699708", + "0x99fed0ae4112ec9ed74baac70d202a885aa51cb555a3886b49016744dd4017640dd5dd564998c4d842a9f38f3e004e68", + "0x95c35401314467219c8bfb1ccd1f1eae6ef4fa9e48fbea14f70d5315e67b16c46cd03554471840e4a5030b077d2a3856", + "0x8d029380e0c294400d6b8673a23aed43697cb6460fc1bcf217aca3b47cf240886644ed09521d6a05f6abf56f99722d84", + "0x8ef54d1dc0b84575d3a01ecba8a249739edfd25513714dd4d1941fbde99dbbc392f7eb9fb96690d7052609af23aa57f7", + "0xb8ad2b7af4812417aa8de8f33a26547f84bb84f39501d4b7c484cc8bb54c7e166c849b95240fbe459a4719a6e3bf1651", + "0x9858545de898721d19930d8b360cacc5ce262c8e004867a050f849f7a2f2aba968c28d51f24a9af56aaba23a9ded4349", + "0x94ea5043b70df1db63f9b66b4f9d8082776f721b559f27d37b45e0a84faf47f948d7c4532dfd854a4bac49fb2ec8e69e", + "0xa2fd88d7b15e3c2778f6c74470d0f9e1a1f979a4d58bd205361eacadab9973d585a6508e685e640b272d6f8a448eae05", + "0x88defd6bccd55db8ca84e3c8d0fc55a3456b41788f1e209d0aec19c9c70febebf3ae32cacaa1dbbf796d7ddea4b17995", + "0x88b8cde2449d5ee7de2ee2f32e845d27e171a51ef64f1d3d8a5fd7dbb9f898ea70eb7f6410cddfd7b7ae70ea8073cc2e", + "0x8e044fff6ec557824866ac76301b6d93ed19b7177aa6baa95046330f5d69b572b59200e3653cf2f2b559455e782e8960", + "0xb5446b4d6741c824885790d2d26258729dc0ba2f469c85a47d38886d933b785a4f38a951d37f3ef4bd5091c03fa3a071", + "0x956c8afa8056e9a71ab2e8be5241ddbb3a8b3cff2110cb0e7389493d9fa45e6c4b769ebef540a952db6dcd8bd55baf64", + "0x925950cae25615246e29d594ebf34fa7d52f78a9867338648158f2131e6eb4dc17e18f9db8a5fdd76d017b3a9798b3a7", + "0xa17ea4b43211ba990270c21562690b3ef154a46c3d669c4674c80bd424cdfa95d8850c8e882b8d06504f929cba3d93af", + "0xb315ec723973a138508afc387ef651fd8a8804f93975fc36c2eeb796a304eeb1508518d8703e666a74d14318253f526f", + "0xa995742d7433b3f230e622de23cb2d81cac76de54831491cc29768eb4a56da60a5cbd573e1da81fddc359b489a98f85c", + "0xadb2e89f0d15294d7118fc06d4fdbd9c51d3ecbcc23c69797e5b8197eea0d6cd1240910cf22fcab4ef1e2dc2dd99da91", + "0xb5ec9f9fcd0b5d176b643df989bb4c4c1c167112373d662fb414875662d1a93160dc0b5cdf540e8a30e5fcbe6cfbbd49", + "0xb1291b53f90aed275df8b540c74a1f9c6f582e16c5df9f5393a453a3e95624ab7552e93d6e2999784e164046e92ef219", + "0x8bc7b7b1a584a12d5ae63d0bbe4dc1b63c9df9c89bdd1095ff4b8e7c822bf8c1994c92310a3644033c7c9689f4b7d2b0", + "0xad7fc45506a10ca48f991714ecc055cea376c0cbe667f3b40ee8dad8446218835439ae59bccc474cf47b053748ceba6d", + "0xb134756828a5f5725c0b95109e09ca450e3834b127163a0aeeb544e63cc0cdcdf66f8ed98c331c7c98758f46af369a84", + "0x94535bf1636be0974b112fcec480ed8eafc529933f3065c40e417e608e43a392206cfde8bb5a87b720263446c90de663", + "0xa4df4f6efbc3701000fb072e5cbed2754b9ef5618386c51ff12f95d281d1b700fea81fc1365f4afc66a7c83bd0228fbf", + "0xb0336b3552b721087c7e2194976a9119aee13ebed9f1c3c494353707fffde52d004a712965f460062ec9443620716302", + "0x99a39d1d1ee4283b75fa8c1fa42b6a3836b734be48bdd48050f9b05e48db6354fef509623c6ec8d447d630a9b3352b77", + "0x8e3dc3583d40956f9e784e8bbd0b5e65671d2ff2a7c387b20fcb7da9b969f2d122aaf7f054d450dc611737604548c03a", + "0xb5068ec5b7bcb5d8583d51cb25345990f50d1f7b82fe535a6a6b17756355885047916f466ea3ab09eef5516bbf2dda90", + "0xa8284ec1eb1d21e693f31a6c074199ee85d8a8da2167bffab5fe240defa2773971c8437e358a18f7e58d1e2954f57f6f", + "0xaa7415639d29081acbaac3e9c6b059d68e8702db3f430b86bb6e220d476fa74841c875e9d471c8a5423c58b6fee3cb54", + "0x8afcfe6f65fa6e07c2cb3e1756c0ef2c589830be96edd50c3c248e3b17f51a4b08ba92ef7eed7991d81667ddfbf2bf7f", + "0x83b9c8dec8ca8f9b85f0e36c08c5523cfeafb15a544398e6f93b48b5fc4b15a0bd05c0f176a9c2469664acab8dffb0a8", + "0x82a128a89ea46b9debe5c903b950c0ab30cd7570b979ca911500b5c2cca5c4ee6b2c2fa414b5f28e367f4671ffce60f4", + "0xb79fd0ccd2629a361cd6f9307c02ecd4d1f07e4ee03ce4b542997e055b07a026cbc0ba05fe3da309efc58db2e401a8fe", + "0xb190751141093823b4b5324cc26c4f3258552f7893241201f2fca1ae9b1a1d4d4964a9abdde8642cf308ded61ce5ef09", + "0x935fd48b95aa6f9eada0cf9a25a573f0ffe039888b3410788c41d173747bf384c0ec40371bb4383ddcc7d9f2db3d386b", + "0xb9affe100d878491ff345636ffd874ce1f27852a92417694afce4163e6a80c78b2f28d78102fd06c3283ef273ad37642", + "0xa877670276d49ec1d16c9f1671e43ade11c0c1a1413755f6b92be9ad56bc283e4bd2ad860367c675d5b32ff567301fc4", + "0x8c660d16464878590761bd1990fd0fc30766e7e49e97b82ec24346937856f43990e45aa8ad37283cb83fa16080d4a818", + "0xae1412087da5a88f3ccc45b1483096aeb4dcf4f519ff3dbe613f63712f484bdd8b2c98a152a9db54cf1a239ae808f075", + "0xad83cead97a9c3d26a141604268f8a627a100c3db7e5eefaf55a1787ddc1dd5ffc7544e4947784cb73b90d1729003c8f", + "0x97c3140ce435512a509e6ff3150da385fdf9e0883a5dc7cb83d616ec8d0a0014e4e0fa57a4d12c7997cd84e07d49a303", + "0xa353773ff68f1615454555bf658eabdcca40a9c7bced8537ea6fa8d54764fd1f032889e910d2a2a342835513352e2d2e", + "0x89e8df0c17a36ffe08149c2ef8b27306d04cdf437135aaeba697abc65e3c8e91bcf1817919a8a826acdbbe7dce79a18a", + "0x9928c2da15ac6cb20b15859c22508cfcd452c5643cd22eb84abf5f0a1a694fdefcd8fc329c9b40babc52630743d6b65a", + "0x99d837b556f8d13108eef6c26333a183f59383b39958dd807b10590c3d37f62ade6c4a320ca2e70567e0218b0ad5807d", + "0x9272da080e4aa18720b634640b01bf1fe506c7c8a89dee8759a53e2ca5cdbbd4a4f3aca54924c46b935362cf1eca066e", + "0xb4d39752c882de1c1daf3854202c1d58c2bcf35c882006eb640fe54a97be2655281cdb91c30d1a41c698617c2cf64b01", + "0x8bf827f4a7d47e07374d338a3d8b5c2cc3183015b5a474b64b6086fcf0cdcf4852046c9e34d7917d69caa65a9f80346c", + "0x901bffc7db9c9416e06f593a76d14f6d9e5dea1c5f9557bd8c93b9e70aa4782bab3518775c2a5b285739323579f7cf0a", + "0xaf7e204388568627ca23e517bcf95112ca8afd4c6056b7f2c77c4da4b838c48791191565fd38398587761c8047d11c47", + "0xab2576b5366e6bd88b347703f9549da7947520d4e9de95d7e49966d98249406ed9270fe69347c7752dad47e42c4ea2f4", + "0xb12e3b228b761dedd99d02928105494ded6d4fea3026d73d65ebffa2e85e2cd75b6d091135d418dd95ac102c22b5ee31", + "0xa20b4a752685d5e31ee7e2353c8a1b9a5265f12bb775004d282a3ecd9deda44831bac1ac5151646428b66909b2a423f5", + "0x91a1d4bc0062a86cc6786a96fd3eb4436d8a4a187b7cbba02190d1cd6ed3c3797d9ae7d6ddc413f1c94a21f62bd04ef5", + "0x977f18da1a5df5cfdd0276f583cfba2b2a0fc6139520664e20068f8dfdde33e29d179abfd722f142448f4677aa47be6c", + "0xabc3ece90f0f7b1d80fd917de27ab0d88cca584ef959da520825e54cb5a71336b15f8b348532d08d47a6fa600527ef25", + "0x888d36a2c7cc13a1c1aa338a183a74a1f57713e76cb825f9837f43279ce4741999b76a16928147537bcc20f2e0195b0f", + "0xaf3f5dfdc2dcfe19de893f385f39f550cb1dab67c2e97f1d5fa735e5ec96d6680066803e8a0eb010dd4399f654195513", + "0xa0fb4e08ff56530a940a86c28830956eb6dec2f020f7faaea7566faf0a4fafe0cffe01480e87763ec22f201be51a6451", + "0x92343c5b107910b203c64a79c93d354f7ee5b7d1e62e56732386776e275285561cb887019cc00d3fdbe3b5d54460bec1", + "0xacfe7df83c4624188a1011ad88c1e1490d31a8a8c8016b40aebcdd7590d9c0793e80d2d7ce6a7048876621c252a06a5e", + "0xa7da001dc1e33e0e129c192d469d2bd6e5d2982eb38f3ba78bae0670690c8e70f40e8114a57bd0718c870ca5dd25b648", + "0xa903de5ff97dc83628290d781e206ef9d7c6b6d00cadc5bacffb31dc8935623ab96ade616413cb196a50f533e63641d6", + "0x8f9658d42ad14a60bbf7263f6bd516cfee6b37b91a8f53715d69f718a090ad92484061c2cef999816760a78552fae45b", + "0x8c15b72b3d5fcb9ffd377fd67d9dfbdd706593fba9629002639973db12aac987bd1db70250ded31c88e19efff612cdb8", + "0x88a2a4034decd854fb557960194ff3404e239953818a8a891bf72a0b26a8e570a65c4a630884de991ae7452b3234f31a", + "0xa09cae5c4c190537bf1dd75bd7bce56f7b799762af865bb9d1ee970f6a133c27cce0dd0f14a0e0516ceac41054e6998f", + "0x9760ebb1b40f9a97530c3b940d4ef772a225e5b63bf18283f8e302b9436c5209f6294980fd37058060e429fb7fdc3a56", + "0xadaa9400eb86d857dc591b25dbe3bc8f207b69e77b03cb5ee01f7e4b006b5c8f6ba2b51b5a45687479885708509363de", + "0x949efe6b00b3248846747a9ad4a934d6e4255994c2b540a59fbbde395fe96d69bb67908441cfadd8c8bbb561fe52da03", + "0xa19a45504b6b1dc3a0fe0e6a1384734a3dcd5a7cb8fb59eb70e49426c4fc44946547443d558e5719a04884ab3a2811ca", + "0x8934c9ee21e8d1435426fd0f64232a0670a7946ec524c054cd4f2cc8b1be9f89cc11002ca8aebae646a2050d91716b10", + "0xb1150ff8ffb34ffdcf7d603348c0aed61e5f90ee0a1b814079fc2a41325c75f2f9ee81542797ede3f947884266a772e0", + "0x86ce8cc7c1f92af68de2bca96ccb732f9b3374dad6657dfd523a95e8a931a0af2a80df74098514a06174406a40c16ba5", + "0x90faabb9ace9e13fd9584932846ab28a618f50958d2ce0d50310a50c3bc6b0da4338288e06e5fcbaa499f24a42c000d5", + "0xaf4a935c2d8df73332a16dc6da490075cf93365bd0e53e2374ef397514c30c250bcac569b6df443985cf3720a4534889", + "0xb7f948ee90f394789eb0644d9f5ad0b700c8e44e5e9ed0e49da4cc18483676d25740710b1c15a557965da635f425b62e", + "0xa917913091245beed6a997ff7043ecf60c4d655c4db0b1ef1c704fd9b0e1ea1335ce8b9f45d6e120f81805ce31555e30", + "0xa48099da8406399bfb1ba834f6f7d864111d0036969a5cb64089947a63dd9467d3857b605e9f57f5ad5f4ec915088d9b", + "0x9784c3f9be42eed354542b1446d734521f8e3f01cd9d495ae98f2e4a3a16767fe2ad909e0def5d9a6267f3fc6a172cd2", + "0x8d9afaa323847a3226ad7d7b60d87322ffcda2e4a8df89f58a076f7972d896588de685a2e155e243bcf9456b0a0d6d1f", + "0x994413faf0b843f4ec1842c706c45ea5f24351c68674a27887bc8b182eda756856e507a4e8bbfd937e2c4c581b629ee6", + "0xb3e72d9d1ddaa00c7d22f25462d6e9f2faf55e30d138dce8bb1517eb0b67132db758668aac26164fd934d732633bdea5", + "0x8e95875e338f714e9e293df104f0ad66833bbd7a49d53a4f7f5fd5b18a66a61aa0a0f65cc31d55e0c075e0d3e412cb90", + "0xb980091862b1a9f9334b428eae14bbf1cecb4849e3a5809773b0d071d609727270f6ad97f329eca896c178ce65883db9", + "0x915d7ae5ae780bdba27ba51a9788a8852a15355b569581d1f18f0d94bcdfed2c1ed5a4f58e049e9825cda11f92b2c2d4", + "0x83e581058edf9259d0b06128282327cacbb6afc939578223cbf93544599f799a8dce1fb21d52464f990a877086f42506", + "0x803612a38b6f6efb97941997e101ac1878e192456f8fbddb3359aa7f3023434ed8fa92e60ec8e7b4473b1948850e4311", + "0x864a1bf4ac046161617dde282e44ab3cc1843da01a09ca58aa00ed00eaea9351a07a9ec16d910819e7dcc28b8d2c8ada", + "0x922eb142845975d5f6f7dcfee6cac8c299b3730400e6bf82cc0bdd9888de21de9d9f1530640f702c003e1ed63b140cc7", + "0xa7db03c5be647dce1385ebc02f4825a654447fa8c4c8d4b22e635dbdd2b3ccdf219384e49a80cfb1e9e6182b6e4227ed", + "0xa167289ff0f0967bbab6479e4a8a6f508b001bbe0d16cad36ab4c105ad44f3f180e39a6694e6cd53bc300fe64dac1e8c", + "0xb7766431f6379ce62cba22ab938cdbb1b0c7903dfb43980a417e0ee96c10b86b447241e9dd4722fa716283061b847fb3", + "0x90cda18c5d66f5945c07c8c7dc453dee1370217ccb851bbea32578599aa669b4dd245dd8a9711b27c5df918eadf9746c", + "0xac690cd2af39932874385fbf73c22b5d0162f371c2d818ec8a83761e0a57d2db2fca1d757343e141e1a0348016d5fc44", + "0xabac820f170ae9daa820661f32a603ed81013c6130d1ca1659137d94835e1546c39a2be898b187108662cdcbb99d24fe", + "0xb2ea5a5950096772f2b210d9f562f1a4cfacc021c2e3801ac3a935f2120d537471307d27b13d538dcbf877a35ff79a2e", + "0xad94af4d0699cd49ba8ca3f15945bd09f3f7d20c3aa282a3113cdf89f943d7793e59468386b067e3c1d53425dfe84db4", + "0x83788367ec97cc4bbc18241cbed465b19baa76fab51759355d5618067009298c79d0a62a22e2a1e6dc63c7b90f21a4a5", + "0xa3e142d879096d90b1e0a778e726351fa71996466c39ee58a964e6b5a29855123d4a8af47e159027e8e6be0ca93d9955", + "0x860831f8d3edaabd41be5d4d79c94921625252aaec806251fb508e364e39fde8808d38b10d557e487603a1b274c9bc3a", + "0x88da39f334bd656a73c414ec17dda532059183664bbbac44eb4686c2601629ef8ff9da992c337a842e3885b684dd0032", + "0xb50addbdf7164e8303f33de5ce854d6f023d39c1c1984b214d9e5fb6f6001cd5bdda816f048a438ff3d696872672f805", + "0x999e58c4c69a912b84561cb09610e415b43832beeb95897eca8c403ef4754f4277754d492eef3673afd4362f50060fc9", + "0xb88ea0f60f8119c5a1fd9294796d387472dfad22442b29659713d1d88e7d854cb7cf5c9ef773627781188626bb2fb573", + "0xa068b3844e9dbcf74b54fd55904d56af754d8ce4c619fead7a07f9bfb9d02118db7c512ccec2489d2a84374ec1d1fb6d", + "0x871dee023768636003c799e6f6fd8d31315a4c0da7286345cd64264a016693b3485e0732be1bbd34dd5fa04dfa58a983", + "0x8021e8f508680df12e4a5a1bd49f2d7142df65158b0a7198ffa83abd16053a542fb93ffc33e5279020ba8c6a26feacf2", + "0xb5d3cd64df5bc965228b0bd4ce9e5797c409f7b64a172ba165e44a8e4b38e3d5fabc3e0b9a19afbfe427f887c40a315d", + "0xa54fdebbb594bafcefb1a03697711e0091c072e1cc24fb441fefd4e0a0518675a1d7b0966cb8294051d7ec0ac175d0cd", + "0x93922202337f72969d6d6e14a29c9c75e0420dfba712029941d1504b9f6f9761d706cbc0652cd09a1aa5d22aec766af1", + "0x9711ebf1c7c7426190d4afd5dd03b014a456bbd9d90ed101623866a280550df26a629dde400c03ee3699f7d827dc0bb9", + "0xb4d686d8bc5c1e822a50124c1cc23c6bc3a1577a3d0b8d4b70d1797418aaa763283c09e8a0d31ae6d4e6115f39e713c4", + "0xa533ea2ac683e4ba07e320501a5d82a1cfc4fa1d65451000c3043f0fdac0a765cc1125d6cc14fe69975f3b346be0fdde", + "0x94ee563134fe233a4a48cf1380df55ead2a8ec3bf58313c208659003fb615a71477e5c994dc4dcfb2a8c6f2d0cb27594", + "0x93e97d3f3f70664d0925be7aee3a358e95ae7da394220928ae48da7251e287a6dfbd3e04003a31fab771c874328ae005", + "0xb57440d34615e2e7b1f676f2a8e379e1d961209fe00a0cf6798f42b7c28dbd03172fce689305e5b83e54424bc3f4a47c", + "0x97644084c6f7b4162bc098bed781dd3af6e49e7661db510975528f1dea8154f3d87e979bcae90c3df3a7752eb0752889", + "0xa923b27b225b2a6dd5bdc2e3d295b101cac5b629a86c483577e073cea1c7d942c457d7ff66b42fcf33e26c510b180bc2", + "0x86698d3b3873ed3f8ab3269556f03ac8d53c6e2c47e5174ec5d14b3ed5c939750245441c00e2e9bb4d6f604179f255ef", + "0x87946826d3aa6c7d53435c78005509b178fdb9befc191c107aee0b48fbe4c88a54cebf1aae08c32c3df103c678bad0ca", + "0x860864896c32b5d4cb075176f4755ea87fea6b9cb541c255a83d56c0a4092f92396a3e2b357c71833979b23508865457", + "0xb78fa75d687349e28b4ddfe9e2d32bb6a3be13220b8f3ff1ded712088bd0643da9b72778bcca9e3b103b80097f48bdd0", + "0x8a188b940446598d1f0e8c6d81d3cada34c4c1ae0118ec7e0eacc70d1bced28ae34b99667d5793d9d315a414601c3b22", + "0x842ac6f7dc14191ab6dddffcbc7cb9effba42700a77584aa6a8e17a855cd444c5d138f9d61bf55f43c6ffbcc83f92bc9", + "0xb6742902c3d145a6af9738c01cf9880dd05c85f0d0ef7dbe93c06fdd6493333d218339ebc2a02be1895436a2f734a866", + "0x98bf18488483c627b7181b049d3e6f849fce1f15794de59dcde6e5a9b0d76fd484a46e48822a6a93001d3aa12f48bc6d", + "0x8769cac10bda8c53a1c19419ef073a5998f73dcf2ba1b849561615a17cbc0a49bfe3eb4ff8801dd36a22fa34b9a3a7e2", + "0xb45c084d58028fdfae792210fcd183abc4ffddeb4cf52ebf3f8a50e4c4eec2a2758f1241b0920bebcb24b757c778577c", + "0x85c1216eec8e1fbc1af9b36b93c5d073a81d5fba86a6daae38748ec1573eacc6bef209e76c87a6efbd7a3f80e11d4c3c", + "0xb8007e34bb3f927ec06a050b51e633d7eb9e9a44715d5b39712e69c36177a03cd68391090cc3293098e54f6cf65f6caf", + "0x8e85527b27c9152b1ba3fdd532a76a79064ab097570508f233e09978761dfe3012d537411b47d0e4b65265eb32cea2ae", + "0x899779f3c31a20b76068ec8d59d97a64d2249588ddfd69dcbaac6bfaee8ce0ff3c5afc4e17c934ae7cd041b760eb555d", + "0xa5dac3d8f5fbef018509612e25d179f60d2a62451c76426bf546e9666fcdc73263d34aa6fa7e2bfd4c9947bbf5095eff", + "0x896900eeef9be2b2e755128e7b1c436af6fb3984f1e66c444bc15fcf3959013b4902c381f0eab1247f878a6ebd1f4ee0", + "0x8cb17f4b0af2e9b2cbb56f46e6a5d6874ea0daf147aae77303020b4e592ddc92e0dd058def7da96258b3a68b223bf22d", + "0xa1b6d3f09a9fa7ecc021ab7c5396541895da6e9bf1f9a156c08fc6f2b815a57f18c337ccfe540b62d79e0d261facb2be", + "0xae70888811434ef93da60aeee44f113510069fd21161e5bb787295492eb8df85103794663fc9305f04adcbcf11ff0c5e", + "0xa84bbc8624100acfae080ba8cfb48fd4d0229a60b62d070bd08fade709efc6914dc232d3f7bed76a59204f9252321aad", + "0xaea47d54652abd8ca213cfc623c8e30780f37b095b59ac4795252a29c2b6bc703a5203acff8831314478b8ee8771d4d7", + "0x8dd438eb8be14935f759aa93021c2b24e1d588f7a162c42c90ec3a647b0ff857f60e24c0a8953eb7bb04e04be70f11ce", + "0x922b07b5469680a10e7532766e099896f4dc3d70c522d8add18f5f7765d4ddb840df109146607b51ceddd2189fa7b9c0", + "0x83ef6ebd0ae6c569d580093e8b0b78daa964760556272d202d343e824c38eccb424262e5b7809d3c586f9e2e9c5c5f22", + "0x97f98bd357db6e093e967fe180cf67ed09fa711580a5ad48f07cf095b2e8fabbe6319f97d1f15d62c0ec2227569d8dbf", + "0xa1953a4a22fe6c2beaf2a5e39666b0eb53018af6976e3a7aab5515550ff2efa89400605a43fb2c4ac1e51961dbd271d8", + "0xa5cbd67f4c0bc98e20aa74c09e6f5fb6f42c08e59aaa477b4b4e61434c8884bc14f17cf11faecf46dc4b6c055affbad2", + "0x87d96818f2c4f12fd7705cf4060a97bd28037c5ac0f0cc38f71189ec49361e438ce863e6617651977708094d5336d1da", + "0x85e7c2daae5fe59f8a1541c94df50402a671a17dbb8838113fa4b7aaff6114cf2bb5969410cf21e6a162857f2f7a83a8", + "0xa19575083e1731bb04bb4a49414e97aaadb36d883aa993d1f6847db50007315444814740e67e10177a14e0e074fd4c7d", + "0xa00ebfb5bcc3a6da835078189038a1e56b7dab6be74332b5ff7440e53b0f9e1eb9973effecbbf37000021fcf50c7c1ff", + "0x8969d7943abd3b1375fdfc7d6124dde82b0f7193068ed6ec83bcf908734daf3487a6a30f7b322e54a4818ae5f86d91c0", + "0xb959c8d210fa43af9b20d1fe0ea8c4921280eb4544ef6ea913309ff9d61c9327096707e84dc1662960519be8e7d080a4", + "0x9011d8ac651c42e0cb03931a9e960f58e02524c6b666047525e3b9097e9f35fb2b4b278efcce2bd5ad463c6d7fd56694", + "0x937e3b22ed0fcdbd9ea5a1b97b84bbe86b7f5b2de3866a930611112f2217f4ee7d9822c4ab1253823f77bceeae0c8e10", + "0x828997e5d121f4c305e018a0a0ba338bd6a34a7b4dc3c5ceab098ee57490311c130e2c045b9238a83908d07098d9fc32", + "0x8d114808eac0f2e1a942d80dad16756ec24f0276763cd6771acb6049472e05a9bb1d3bbd5957f092936b415d25c746b0", + "0xa063c5c26267ae12887387cbebbe51fd31bc604630b3a6e8e177e71d4f26263be89112cd12d139dd4c39f55f0e496be0", + "0xab1e1582c8d67196d10f969eeb44e6e16214f1316aa4a2a821f65ba5834326da6cba04373eabfd3b3072e79e5c9717e6", + "0xa17b1dbaa11d41457e71a9d45d032448091df7a006c1a7836557923ab1a8d7290ec92a7a02b7e2a29fcea8f8e374c096", + "0xa1ed7198da3591771c7c6802a1d547cf4fcd055ca9010756d2a89a49a3581dfe9886e02ee08c4a2f00b2688d0600509a", + "0xaf09aa60c0a185e19b3d99ffdc8c6196d8806169086c8ff577bf3801c8ab371e74165ba0f7329981e9252bfe965be617", + "0x98c04cc8bb26ffce187fa0051d068977c8f09303a08a575175072744e0a5fb61191b1769f663a426c30d405515329986", + "0xa542bf1c9c3262d488ea896f973d62923be982e572172e2461e0146190f2a531f62acd44a5e955a9f1e242b3e46d63ae", + "0xaef7b7f30efd50e4a66c87482386f39f095bff6108e68f74fd3bb92156c71c75757912b111060cdee46a6b3452eed657", + "0x8afe1e0ccd00079702f16ab364a23bbbd3da1889d07c4f8cb04fd994bf9353216360dbd364492932bfe20b8b69ae8028", + "0x9896c690999db3c08cd7b25efb1b912c3e0f976db98a3e830f086aef93222d06ce570a7b2babcd7c81d8f9955169669c", + "0xac7bcab6a281468907ef1ea8a6c1cd624159c88839131bef6aa0c22f331fc87ec6128a2c2a333fb79df549e4587e1a12", + "0x987935c08a30b099d19f96901315a2e60591baf898581c40bf5eddcda806ff24a4536e30ed1e6c0b128a83fc77b6e81d", + "0xa0a6945bbede3bb09a4a09ef27baa20619d3e15af5673b9350601bcebe952597c989870746cf75767ffb73b32c6c9c6f", + "0xb0f5590079f0a0302b08a0cc1b7a5f39cc6900c2a5cdc7baa333d8328a731b2df5dbb67e27a154d3c44ed1a795fc4adb", + "0xa7294bdeea210e528f277f3d50e89e6d79950494478998181ecb38de675020130256f2f2a075899170be964d478458b0", + "0x8ab3041b895a631869b439d5599a66facba919226ca9b39d915f19d59f9fc82393ea781377e9bd3bcc5a310e41376914", + "0x8da399b59151fd48b2579948bb82698e3c9804d70ec7d6f3cc7e82901f9f2de5ee850349a7d6f43e5e9ebd47bd78620f", + "0x80e8c32de83d1083916d768b11a982955614a345d26d85b457f2280ff6c52bb776958add7c1c8878f7d520d815b8e014", + "0x81bbec7bd99d2917d2dcd8a288722fb33ad5a4bf5416fba8609fa215fb80e0f873535349e7dc287f892aa56eb9e39c4a", + "0x9665796fe04c8519206fba58496bc84a8b9113e7ea8e152b65f7f732e88beea271dc97b1ea420dbc8257cc4b18a77463", + "0xa97e342aaaf693ddc87e02790278e4bb50117af4413cd703bdf3b7cad2d1facf31fde1303b43ab2e0265467474f97a8a", + "0x925549ebebed348886e37773b05cd8ad04906eca4536bfed951d1ee41b3d362ddc6e1a302c21ff3a2d1e70e95117922c", + "0x818fdf74d7903502101551bbf48d3c7819786b04b192d9e94362d2fcb85760d8b6f45165a5443aa5221bef400525ddb4", + "0xa9d29de7e8fd31b59f4a087168d062a478b1329cd3c81c31e56de4fb40de7a5be9a5269ef0be452c487443a0b097dd50", + "0xa85286ad573db4c9aa56221135da1e31d742e0f6ff01d6b159086d7258f78b08dad55ec8eb5c91ee9d3404b2eeb67e1e", + "0x92a79b37db5e777f9ebbebde24a95430a199e866e56597c7d0b0e7fb54c7b092c2f6cf61fb24470ddf250cf609898281", + "0x8d79f5ca67ed67d52c82949af342a9fc60fb793c47c76d84b4863c550796fcae2dd59e285897c6fb96fe31cee1efa62c", + "0x8ad2e0bda03415ab86324992bb62dfa3612d2d003765bcad1468087c27971d08bdbae5252681f0115a184f4885d444e4", + "0xa08815af979286538c31b4aa5ec805053790af1ca58a8c4341be51136d094a8a05e569d876a079033298ad355ccb7ca8", + "0xb96c2978d0165d619d08281d295e90df78bc2375d0afbc3142ebff9c2cd4b0f0aa97a9a0e3740bc4dce0ff8a9fac8252", + "0xb7752cd0e582f35ab0d0036ca9c0a9fe893a6ad325164d78d865a604a85d3d23729e0362553e8b8a3d51816beeaa30cf", + "0x99cef1fafc29e7adfe247c753c475ad4bda7a5f9558b79c86e8a65968ede67adb38dc30071925c9d66a13860027a6735", + "0xb9f6c65af178c791b6137d71980651fb09cb5b42f268999c728c6e129985a9c7d77b3dc3b50751bd29ec9ee0b3111dfc", + "0x8d73ae61fff5be883a281782698075c5650083f00399992688738856d76d159803be0059fbd9dec48f4f0432f0590bbb", + "0xa8a4a2865226de9bbf19e12c7e75318439fa6cf1cbf344d5e79a8f363439d3bc5bcf4df91b54581e7866e46db04eaf0d", + "0x894582aeff222e145f092ba15c60d3207340c38f2c6792ee2ab4d82d50fb544ae366c2985cc2b6c2f970bcc5f4b46385", + "0x956014ba2d20a056fd86cb8c7ceeab9a2c6f905dae24fc1c5278fa5b84335148ebdefec5dcde8eb9b084700724fc93d7", + "0xaf217fe2b654eff6d11a2a79fe0339a1d4cb3708b7be9f09d852158b5a44b4f9b04406d6d67c4f144fb6b69a41ae9d0f", + "0xa90752a784bc00df94d960e523f5596695d16a534fc806179e0f878fc0e82a91b25e758e91a165debd815dd1af5f1028", + "0xa697606fb32979549ad822b31df8eaaf50de4ead984439a0a33e955937d326519bb9f62c8243ad37f764655f8d32cc80", + "0xa3ad4a30922e45a3e665551e5611384f1c2d414f6fa806184b0c826af05f014dc872585e255543794ee41e43cdadd856", + "0xb29c255843a82ea74a013bac6c36a694646e61e6b9cefc4c130e2ee261e3bb5da3e0fe3ee7e6fbb009deed0530bc1c82", + "0x87e1cc7febefa829cf050aa2aea59385d1048f8617abba691f7ea9ef58eb90ad12eeb9c439af228b0e34897ba1cf1b47", + "0x994d3222f89e9c8c154362190be7167c8c2662f0cfa9d50eb4d8175b255ff0de09dc548ee312fc8226963c8c16f43e8b", + "0x8f1a980be640820f2d1e953264ca4c30330878971669852be3d5d6b41c488be1628b935388bfa2bd4de484acb0fe661d", + "0x854d90d0721579c8c88e147a4aa83553c960617b18075f8224b975562dccb30b0e02e81fa9df7070f356a0eeffc3b14f", + "0x8e156da9d4330a03e32a25a2f0b861fd3ea5c719fa4f834119baab6e5fa5236a9baaf0d44147bf0841418900037f6eac", + "0x96586fc49e53a6799242ddf617000db5a0ad20c6cb1686af2102623d64a71aaddb8e468b15fa6d100d0384e448548db4", + "0xb44d8d85c8df95d504f82d597f8c515866d4d4a326fa1b816dcc5bb0cc4ef1a52647aa5d2e84c62e194c01cae0885d21", + "0xb75c43e676a7efd199f8b32ae31f176ec667e714df355e9eecee97246f72af5bef9c5b04c11e7e90fc37bb9163f957ec", + "0xa49835ac0565a79f6a9078cf0443c5be20561a68b448289589721fded55188583f1d301925a34eea647f90a6e66c6774", + "0xb47c17ff6824a00b8f29df0adb7f06223208d062bd703b0f763c6eee4ae62d4217eef2da4f4dde33f0b469c2f2db9e42", + "0x957cf039cea6f6d41e368e2bd0cf77315938a0738f15ed9ca342f0a28658b763659ac1d1a85ecb362f13de12b77bb582", + "0x903a52f8d2439fa63f59e1e9aba864d87b0464ded63814474947112375236a6f84e8fa003cc4433c8208d80e05fbd1b0", + "0x8afd524209ff08d1eb6312b078f7afeb8e1155af649e930ab711dedda226dc2db6b0354aab9652eea7f433f90015bf7b", + "0xa95c3c9277b11bc8fe191773bf567641be57c0549913b973fb18740ff9cd7b3f7ce198fa4dc1086b2b8a446012459193", + "0x9455ce8163fce04aeff61e7808ef3aac4725e51404f0858fe5d39d7344f55dcc7871ca332aa5cb1a63a4399529e48907", + "0x809fa35b6958f94e781f2c584438b33f5ed528a6b492d08960cf22ecf63ea3aa1e2d29bc879e17296e0a6cc495439cb6", + "0xb0f50774de212dd33e5837f6b496556215c665437e657f674fc5117e5c07dadbd0d057e6ac4c42d50a8eb81edfebf315", + "0x844c65e263891d0b2fea7db6934cc4b7fb6bee2c1d0b9ab4c47f2eb3e9c5d7197dad828d38c54139123740151420280b", + "0xb13c78c9efcbb3b28eb3fe0b971380b7d5151c80948a99cd93c78b4c3ab0e86df6226a64d91e0a2ea4a1c0a46bc0404e", + "0x90300a541decad460c348b8f4257f7a29687b2362ebee8d92fd03cc0e85b285ccb0ab1cb2ff5e29c5cc5295e351017cd", + "0xac49b409ded770c6d74f6e70104c2cdc95b7b90609da0743c9923179e8e5201ead03becc0ab10d65b3d91a5be0d52371", + "0xa257b815bd8289dfdfc21af218aaba12ccfd84ebf77642cc4cf744d9b0174ca0b0d7ab2a545c2a314fd5f63c140f41ab", + "0xa34778d8446e4d74d8fe33de64b2694ef1e50bc140e252af6eff3ce7b57acf8b6577a02ba94b74a8ae32e5113cf0a29b", + "0xab9e935bcf0d8607e3d66f013d9bce7909962cb7a81174923db02dc89e485c2b1c33d6065bdc7bbbe0450b5c49fbe640", + "0x94d2c5c5c309c9eac04be4636f61bc47fd9579b47aded57cc6c736fefb8dfd8f8a5de32210f7baf2052d04c0219d3b4b", + "0xb8dda9046ae265214086355101be3460421f7cd0ed01bde9c1621da510941d42bc93cd8060fd73f374fb1b0a5f38d45e", + "0xa6674649dab5f92ab9fa811d9da1d342cf89ff6eff13ad49f4d81de45438e81a384098d3ae5ccce4c67bda5dbe246d95", + "0x8d619f7564677bacba29c346c4ef67c211f7a3a14c73433dd1a7692e16a7e2562f1d0532454af62fc04c2fd2bb1789b0", + "0xa2b93d2fd4c707f5908f624a0fc889e20164d3c61850af9125f47a1719757a6ce6375aa1910eafa4c1e8b6e20c312775", + "0xa07d5585447654d82817ef4d199984542328b238157976eb9a267f0bdb2229acc25aee510be68f65a312b68fdd9e0447", + "0x8ef55cf95e2b24d8ec88e4136399a7763bd1b73d5e90ea45e9845123e9d39a625cc336e9b67988374b8ebcbc75f2ed21", + "0xb62c1fc32e27c767c461411b02fe9aa44a86586e1427406f4ef0b346d077db91952abce79318b382ec75b7be23058cac", + "0xb252900345f5fa15a4b77fb6af6a2d04db16e878b7bd98005333f7f6e3c8e6e46cf38fc5d1b2bc399c5c2ff4af730dc6", + "0xa4ab5ac0cc15d3d17b1747c6e3133d586870eae0a0d9c8fa7fd990ebd4fbb62e9090557ca2792a6bc6271856aa3c9a05", + "0x8e706b3f2e902faee10b22742c6c33bea6f670a8937c243db96885143c1db5c979e33ab73a38359b52b8d668ccd092a9", + "0x8a6792190ee6c959d79f60c22980ca140c638d88d75660adaf9bcbe6dc4692ab5f01e0c460170f09f74d5e582e85ff1f", + "0x97ffeedfc94c98ec85ea937e064d7b290a326838e62cebd407facd1ab4f08d9c0c109d79af7cb6170fccfa6c8243c127", + "0xb79970b67c09453614ffd83a0c923c17f857c6ce3c87a356298f8351cab0def7ed83efd4f6638f48df67e07bef4ad9d8", + "0xb90f1931c7cf1822cc0a97401119910cdfd0482daf09a4d7612e4e05046295cfb4cc50d5214b31676bb1a1c9d15f9c7f", + "0x922921ad813c01fb5d12fa7fb7ed8e0b0abbf7b19affa190b36013c55b88fe3c7df0ae663c970eec7725ba37b95a7cb7", + "0xa124f33e7f28feabb4089a063a08d52b7395d24eecd06857a720439dd9414b7073bb86fbd0b04e7bfac62d3dc0fdb2f2", + "0xb252fe50bc6677c004550f240fe670974a33ffe7191ed7675da6ac36c780c2f8d02be7da5d92cbe2d0ce90147847f8b1", + "0xae5f8c9c56070f919f3df2d2284348fa4b2e39881f7bc42c9b2f5b7cb1ebeef8ecac000f37329bbe04cc1680cefc7f4e", + "0xb432a4575caf7337f11eecfcbd34a6705d0f82c216301725ceae2b3c9df20fa53d1ebef65513e305013d1e0c2df522b6", + "0xb7c016fbbc4614cdbb12db1c9ac41f9a45d5e5ce82594d568a30cd2c66c3cc9d91a2c959697b67c582a0913de661505d", + "0x8f6f3e5e0347dddc1b2a34ec0dbbbb7cafbf976f19c9c902efb5c1427d1bbd4b71abd9f3fba20dda75c35a39393c989f", + "0xb0042a1d33a1ee9fdf3fad2299b8d70c4f1862d8393b5ebe3ac2189a2c5a58bb826128cd7a39b70d524a6dd976097e26", + "0x85297c4e8ae8d9b44c3fe51aa926c77d55db766c2a9f91b659040de36e34c9a4fc6f44380f8d61704498f6fd52395a49", + "0x8c61a988b6a00fe5a277450f30bf6daa932e42a2eae844568e3babf8815e09311f3c352dae6eb2d57a98d16b7beb2d22", + "0x990be28aaecd932e7edb2a97b9be2789a3905cb88737b1c79881302585801c69a3dd5fb230808b39db1352fc06e0b4a8", + "0x82fd14bdb335aa46f022dfe0ed4d631911e6b6f5eefb10d11e9e2e02a7df55012ed8162249d10b58eb76ced5a7b06cda", + "0xac39cb058df764e161db9c39b185f09aa210bddbd66f681f1697ddbe6b305735612d5dd321d3ffbb4876771bdb321e2f", + "0x858a3f7e57ccb81387caf8e89f9b6039e9aadeab06886d8688fe6427151a59ab2e77e85ba850c67d099965426c97779a", + "0xb57fb9ea623cec432946819937c6bded0b5d03c8c67b52b44a4b67d34adfb055e6cabca67a48e4d859b4be45162c5083", + "0xb84d2990b563d6d7fe1f4c1894989db25b81745090b94b1fe2ef708ac3b2110ef93d647820b2a51fcf78e3f00fef5412", + "0x817d85b9f5e1521733d2b1fa6d4f4957ac445dc803f97fc495e20b819b14e651332f9e0573d684b854fd47824c53f0e8", + "0xb09e18e97e93a8523101af594422fb71afc5b8826002314269016fcc1b44002d91bcb7c90d923d460f0cc03bddfe9af1", + "0xb867cbede82102de7cf6cd0dae68506869576eaa66c3fc806e73585310602682fc912dc37adf5ff6f0f34a07831735b1", + "0xb1126255798368b692f2796a3470ed16e5ffdee2d8c9e0f7ee3d2e92950c3e6365c32895171c3494aff2a6d6356f7e25", + "0xb05f0a0996dec16335c770a5df3f0b08e20020c838c2caaa1d3a4a2490ede98552f5de349de2ce6e4c4a839731d80919", + "0x98c512bb91c8fa191120ddf5d63c88076581cf41e15eec3c168822f12b3dd0ce4d6df74a7e3093d3e35cad1cb3135421", + "0x84ce38fd97f7f90012c2c1e59a67bf9f465a7ccfb6f308bdd0446cc82b8a26ff7c30e5c7cc375011718cad1b31adaa9f", + "0x93139db52c9fb96dee97a0825f21e34c5d6d36838e1e42f4d12d01eacbe94426c85a811fe16ca78e89e08f1c27383d28", + "0x81454037b1e7a1765f67e4288b8742eebf6d864d9b0f508ab44fa3243168ce0ed30cb5f33dfcdb995cd2c2710ff97a6d", + "0x828deb2a26efb2ff1842f735e2cc27162360f619b6e3e27a85bedf384912d4726bb2759a3016937973092ece1bf90540", + "0x87e5a7d4e7bd301078f625d9a99b99e6e8e1207c9f8a679f8ebbbfb467bfa0b5f7ef4a4d577c7d2670efa88221153012", + "0xb9dc9d0ea48deee201e34379447bec789c8924aecd030eeb93db159af77eff230976ef60ea9f4b4a9e9e95c1f9f4284e", + "0xaa6528268d46bf0627d87d58e243d3ac34b863513c725908a2617e4c6a46ccb1d8c8334bd6dd0eea7ffebec44259dae5", + "0x8d26c9ce07293f6a32a664d31e6df9a7ace47e6c38001635918efd9872aceab62de7757b13b783d422eb67bd28ce7bbb", + "0xb0d3ca88d9829a7459b89b0dcbdb8bbb5180b00d750bd959bd110f53c2dd5d4db554b6005c4765fbe7ec5903669e5ebc", + "0xa94d1c72bf3b2dc6bfebc9dee40f6a89a516b252bd9f4fad96f156e3dbfc151a9b8a02324d764c7656d59230a18eb61f", + "0x88996e79171e30b16505638d8ecb25afd875e5f3cc3e29860937f2b5e751c66e78dc77f744a0cc454a8a655142a93ffb", + "0xaf4d94f342665fe7ecda318de6cf1bc1c40c37dd83d060fedaf827459728152b5f0e280286ff5e6a0012036f6715f53f", + "0x96beaa7a2d565ec14a4e5cb895d33624c69da56b75c8d06ac729cb6d0cb64470ed4f9b0387083cd827b1609c8cabde8c", + "0x96b773fa2fcb7377bf71a7e286f37f1f24ee42cba5b4f33903c4566e5e5bcc501ea360e3c8435749107c3de84e272d8e", + "0xa69ac6218454c3f40ad0beb48821a218fb0a4f33ebade986d2fffd9a3900d8cfa613bc71676c46cfeaa5f644d1f239a9", + "0x857f139c08fcc45370f448ce3e4915bcb30f23daa4134407fc6d78efac7d718b2cd89e9a743eec7bf2cc0eccf55eb907", + "0xadeeba36af137fd3c371a2adbefea614c3ae3a69f8755ce892d0dd7102fb60717f5245d30119c69c582804e7e56f1626", + "0xafa97ca3548b35aeda6bfed7fbb39af907ed82a09348004d5705b4bb000173270ce44eb5d181819088aa5a2f20a547a2", + "0x8423bd2d07073b0e87819b4e81997e4d3188b0a5592621a30981dc0a5a9d0578fde1638a364f015078a001afb00891c2", + "0xb92e9d4ec3966981ee574695d6e4865810b8e75313e48c1e4bc5eebae77eb28740e97ecc3e5c42040f9eb1ee4b13b0ea", + "0xb07b218321d54cecfcd2ed54a5fd588a6be8d7a5b6a66dff7facfe061222c40553e076e57cbdfa0bdb08e0a009c94ba5", + "0xa71e1ae4d6096eac9ea4c21f621c875423de7c620544e520fb6ec3cb41a78554aedd79493cbd2c2ba4f0387f902ddd2a", + "0x807cdac291246a02f60c8937532c8969e689b1cfe811f239bfdee0791e7aa0545e9686cfb9ed0c1df84748e5efa5e3da", + "0xa1faeb4504c057304d27d54fb3ec681462384a354a4f0b6c759d4fa313253a789250c6b0f44f751b0718592637438a19", + "0x996bcd3215182d49f1cd15a05e1e0a4bf57e264400bf14f7253c6611d2571de7130cce81fd28e0411e0a80e9054f4f98", + "0x89d15b38f14bcd46f4b2dcae82b0e7bf9a35e40bf57aa947e9c4a8f87a440b5cea95229708de08ca596762062c34aaa0", + "0x8d8ddcaf79374c750b8b0b3d196acb6bb921e51b4619876a29d09161ba82a42271066187211ef746f9f40a5ca17b75f7", + "0xa3dc7f70f3a6c7edc483e712770abbaa94bfa3174cfee872b2cc011b267e0ef9baa1ab49e4a6c6c30dbba0e0a1237117", + "0xaa9e958bbdcb192b19c43fc6fd34afcd754949fdada98e9f4848e8db0e23acb27d19dd073c951a8819000f2356aa22e1", + "0xa4714e45ec853eadfe5c3bee7f683b81f97857bbd7833192a48936dd1460aee68f700a21658658b74b737c4fecf90c7f", + "0xa1ecab4215c1892e4a8ff3405d710163875e5dfef8a8cb84f5cac4e317d89c7696e3f496ed1747ca6f52b304190f4ba1", + "0xb9b48943eca3686219575026d395b969e6ff8159dc5317005df090e79d26901984e40ae4b1af060ed3ff6f42e0417d76", + "0x9644b9f90a66edb0396abd8c00066886f978ebf56fc22081031fbc9ce371bf9b04aa5a4ef59e59319b3a05bb7fb88b43", + "0xb2bb14f1c055a78596488e4e2d4135a6470c1ee43961952160b8498f674a4d23040606e937c02c1fc23dbd47e9bd4633", + "0x8c61f2fce9a42b94a389c7e52d7d093fc011099d0f4914f6d6f05b631df7b88182826edf9bbb1225971a080ca5c0d15a", + "0xaa6a7b8499cc7d256043eacad18528d38bf3be970bea4c6d4cb886690280bdb373688ceba3e506471e1d9493dc76f3f4", + "0x8127703363b3b35b06762c2353d4de82b7b85bb860db1028d3640f46bdb78f2d104fa77ee3e0d9db83833d2b12a966f8", + "0xb7b01f5909f2c66ae0fab156be5d79954e3a304615e1fe55945049dd4bd95f973bb3821117eb54db7e9ed1ee9a527652", + "0x8be47ba5dfe212420649193490838670c40540e0ea24adbab18c4a66e7ac3dcf94f068dec2533b60e08c1f64e7533e54", + "0x905a6c7e24b86aa54a05c329a6b4616d335bb0b1f1e9987562eee0acf82ad302c7c44981a1dd6b24c6121ca12fb92996", + "0x86969ccfd91deed93b355a2c21319e3bb08cc652b741463bf68c626b7ba2afce3f7cc397f2fb74588c2893477c948ae2", + "0xb5a9d20eb12c331d0d300fd4b85b0ac0bb74573178a5fac8ec9dce5e95acba07fab444260355ece442a846737a2dcd1c", + "0xa13497c11df21b11fc1a63b0ffdcf7f432da4dc2c98f8d07d36da4fa68aceb57af2158088e5b05e334fe0f264aeb7a97", + "0x882e4597cc66498a45e86a2ed9ee24652da4699af00ad35f73b5e74fde6ac3cee70630962d5ddd86162d4aaf11bbc11c", + "0xb748858c2bafa4a14ce44af35195e9c52aa75e109719243bbe278095acbfd6a7ae7e084caf8dae6939039b5a4e8fd675", + "0x83a2e0524507e74f51fe976441108f8226ba1b3a33f4e16ec45c5661ce80cb1840a93d17122cb8ca9e0f80d14f69877d", + "0x846cd2946c93ee5f24243d9ebc69936b3a1a6d59f45fec6c79b1eddf15ce30a8e73ad03cf606ee66baea3d8ff115f70f", + "0x8d98d0a3a94f6efe158f8423c041b546416145c5c2254bfa157efea0d1c99fe58acc7df6424ef29f75960b18d664ea4e", + "0xa39fa47e4b79f54dbf59d0b1726f1e78bc219fcfc56ad238c84b4b610e7892ff1e65d537baf5118a32f5e2eb80d5ee0c", + "0x8c30969a4519131de5e30121c84c04f67b98c8ad109fa4710dd3149cae303d51778add3f258f0482f1c89c169824dffc", + "0xaf7f80d141ceb78b4762015de17fef49d7ff6202d292e9604deb508272ee7569f7fd5be3b2438da1dfecf0c26533ef86", + "0x97cf82f70128251944d79b8845506975405bd720e150d836205b048ff36ba8801eb74cdcc6425f28f6bc0acec0a81463", + "0x8c276c876eb88688957d1868bf3a1462375e608ff72b49870a5dac82cbf6584e00e3f36f236f732348a47502ccf9539d", + "0x964765f1a5c8a41d8025ddf56dc01b78424703d8a64a4e5539e477cb2445cb541c70127c561e717256d13f91a830ba83", + "0xa2aacd9e21b8c8efaf2319611addea1b9f41430aee42e7f2a640cc693aa395287cc8fdc2806b76b577d84fbd05378ead", + "0xab11eabbf5be4345a77323a3b75f9ee93b011fd2a9d0154e88183cafe47f82a7888666af16b40d3cb677c94bcc755ff7", + "0xa0bfe715a7af5a29b1b6148b8cbee585d2b49fa6ce59bcd173ea3bbc60d71a62f9da27ffcbbd5a6da75502112fe44d70", + "0x902e6cc38ee42245103d90b65028a471bc7a48b825599d361aa81d8c56e0fcf9fbe8d4c13802040d2cfb85b7e022eea1", + "0x8832e2b5014fdef4003bdbb87e3298fdbdbbe49673f6b66e2373f1cb2605f9c4af2cdf9bfd45d1993208681d29ee1c9d", + "0xa7d39d3fa1ec1e0c87730fa43d4900e91932d1cafb36c76b2934907becf7d15a1d84d7234591ad4c322b5a24673bba8d", + "0x836ed5f09d99624204aa3aa7ac601980fda223f3b4b96b4a8fb235c574a3545d518787c12f81bd5851987f2860d41886", + "0x94235e94445e6086f6e9331923262070a4c2ed930ec519eabb8a30133bd4fc6debb99185f4b668431fae1b485c5c81b7", + "0x9828ffe20b9405f117dac044159be2d3c6e2b50ecdd1651d6a73f7633e6e2a7ba3d783ae939973604446d3a1ef0fb20f", + "0x92f03dc365dfe9154743ca70e6dd2758f064e3286fc543cf8c50f68effdf7c554bd17b3507c6ff4127046d9bbb5522ef", + "0x91ed07df479d8eb3d31292a0e987672a7f3d45ecafe72935b7abbc3f23493605134ce573f309e226c9efe830b6868220", + "0x93bee582661e6d6cefeff29002afc2f36dd2c13dbf33f0574c35b290ddc426170a5f7f196369ad592efcd72cfb6f8fc0", + "0x89a51467d966f48fed15dea5a12dda54d0015f69e2169b5e34f44c7b5a5d4c282d6f138116a0cd06a8476980e420f8d8", + "0xb8ccebc14b6679ba2399370848864f15f63512fd6139df7359b7b93e82c1007fd85137ecb0597294b46643e1a9e7ab5e", + "0x841fa301567fc57b2cd09508ce75326684e12bfb8add671dc208f579b2500b93d5b641e9f59bba798ed4ed1259757f7d", + "0xb3cb45c15eb00b4ccb7013299f761cb8fefc17adf6db50e9ecb8abe927a3bc7f28e359e64693813e078e1dac800ad55b", + "0x96e55d3b9f445f5679e34fa5425b3e87cb221cfbdd07f8353868c7f7f4ba388ee3841cb9a1d638583bc20d03a9d071f2", + "0xa7dee9377de740270c5b57cf86699004ba8dc2766af56b388b5cb0814ec71bb99ecf43ee3d82a552733854ecc7def0fe", + "0xb129dfff23b3c1c95ddb214c4711961fcb129efe2b6557ec9e116ada909593d0d2eec2c628434493393c58c52aa86847", + "0xaed2670e201cb3e38a8be3c86735a4d76255e1e5a4c67b91df6ed262d09c8d10b0a3891da3e6ab934058cc9a7178931b", + "0xb20b8921ae52e5b3c94fa3a8b46489044174f7b897779e7763d6eb419e808d76705b7e7ba5131576f425aa81b6b0de53", + "0xa7e45bbc3ba1bc36617291ba7663806e247f1b57a89e31520c64a90cbf8d426cac2e2f381338baf78c8f92fdbbcb7026", + "0xa99e651e73a507e9e663e2364fcc193ec77e8afdc08c2bed6ad864e49b537ec31e9114ee72291a7657899f2033a849e2", + "0xaf966033636c2e9e8280d173f556fe07f8b6940bbcf6b2df7e2165c30bea66cced2596f6c17ca7c1aa0e614174953ba9", + "0xb69ca7a79e3d55ef21e0ebdc6f0c4bd17182d30cf6290cccca7d2551c91c12b966020d8e40e4ee4179488c9809c03ae4", + "0xb981cd36244e035fef043f70b1d7188d7cd045b4de0581c459fc5730e10eb7f3d5893b54cc4243849c0855e4e621167a", + "0xb20fea858a36921b35a3051ce787b73f70fdecd3fef283c15a2eb1bffb1dcba5991eee4a047ce4e87802da923fd9457b", + "0xb040e6f2e56dc1860274c263d4045837456f74b354a679f6b5ea70919835ebe5d32bf1f519e218730096c98ff396dc9d", + "0x8d2dd60e702c923a7204b530e7d6c193c6f93ca648c4f7bb38f4edbeb0aaed84184213afafb8db6aeb9197c24364276c", + "0x95dfa7348709e43d71285b28a0bfad3ca805b6ed4ae99753e9f736c79d58a35a3a50b42760ccdd03eda50f6e59494968", + "0xb8585632a13f18c139a411bb2f02df809591834d127cd1ff081e26d0abfe0e3fbb54abea26538b25a0dcb4d7e969590e", + "0xb46ba47858a29c6d523c9982660949567666daf2582b93393a4802a9e077eedbc0d49d454731696bc8e46ca50c7caa40", + "0x84b756b901b98a4404e58d70f39f6ccac877146c866732ae65e7e82727448d1550343bf7cdff1bfd4ee1ed73793db255", + "0x83e5be888eaf877a2c755897410865f64a6d1169a8ccf0336092f3932abab915e542ab75a35ffe016042340d581ee987", + "0x8cb274fc39285aed451a7def72cfbf73168ee10be02affe355a2bf87cf361a81ad284e9334cf00c5bf99a13d9f75e116", + "0x91ff6220924b94ae13f50eeac16a159232e4f16a73fbd5c22c0e185cd1998403904d36bad203baa82b85819ee4a8ac10", + "0x87f46e08e09aea2ab37b55fc300689d9b58ff3e72f1cffe023386035888f714fac4673c7c5193d3f3f3c568c640694f0", + "0x835d7d84ca7641e1b15095830114aa6072fe12260d2202456cafe2308c22651af9ffbcf6b7e56af97167dd0c4e2a4cf2", + "0x91202183f79794f114fd9e3b9bd05553c0e8985919965101a57d97ef666b028863e6cea9735af016dc1864f1542dee51", + "0x81ab2b02a9b0a490a74ae615ddd4fe560734c1bfdde6b8dd13303c1481ba0e8ab14473535a93cfe4e824a0ab29445f8c", + "0x8a32d73f4fc006551d4e2c61eec6130355ec9b8c39a65c24ec1edc00e80155ca83a8ef2455e892521a3d47634d82a987", + "0xaf70d7b8f13bc90193cc1cfb0c400c4224cf10f1887848aa93e6380f7087782fc41a159926ab53c53eb95c2383b1a849", + "0x989bf42f9d357c51774f1c7c0f7c0c46a8cb7398a74497141c32685be098e38b4230ffe833a6d880ec391a35b1a747b6", + "0x94cb6715ee95700020c630b8c19e35f231de970219bd7e6ba7ced01899197da473b6c45cacfab0d652ddaf547b4ea58c", + "0xb12e3331f1f7d7458393a785e22e9a5e1d1daea521b4e78c0ee8ca59b41ade1735a29820e18f6afb2f2c3c56fecc16b6", + "0xad4b7cf654349d136fb41fb0dd65b588199f68b462b05f5c4e5c2b468bfaa6c26329033e3c3f7873dc8ace89cf873ea5", + "0xa3279969e1ab596df0559ffc5ac7a6dc849680354e01c3f4fd34c6413a3f9f046f89c1e1be0b315d8b6dfab3d23d5c14", + "0xac74cc5562836ed89d09a9ae6a3644c936d64bdda9e77659d9982f1be29541b03ef2723236d5465e398373ea19a4ccc6", + "0x98138ebce1af531dd8b631b3e74c84f0c700355a2a9bde31e5e51bb10c8bbd766559c63f6041f4002568803fe08438e0", + "0x9006445da131349fe5714e0777a4f82a82da343612589a0c1596393e8b6894ce1cf42784f95ff67a8384ffe1f1a4ad76", + "0x88502a84a85e4ce54cfed297b5d355867cc770a8ffd0714a6f23b1ab320a9903c6e42809e034bb67dbf94c4fc0d9c790", + "0xaa8b4bf123d1a6ccaa44b86be8f980005f2a0a388a76cb111b0e85cd072ef64167fb0c097c7b23c4bca64c0260f6cce0", + "0xad49eb35dfea9feabb513a78dd1152ad7eba22fbb02a80cefc494a7037699c8df81202dfec12acc1b9e33ad680cb72d2", + "0x8694da730231b29afd5196371ddcb15b4dcc499574bdd063f4864ab80749833ea38ab8b0ca1629a367fe378e87a60a86", + "0x8eca7b488e810c479e7e32e24b8afcd837f7df183fe4f621a0336b53a9ed77603c84bdc365d8be68179a32b71a1deb7e", + "0x8875cd3e23c7e1af55af1b091025a08255743984186770bcd43f30b4a58d175cfdf1984bad97a15e08dac2da27198c3d", + "0xabdafcf58ec72997e494d4714645f40d09dcd0fbd0733e640eca44eeea67c25bb0c270299c459991f2fae59d13b4f4d5", + "0x8f040970141e61489284f3efd907705eae6ec757fe8e1d284eac123d313e9ac1e8dc14ae3f04d281e1effc49d5d2f51d", + "0xa7ff115f0d2dbf66c0e8770b3d05157b37357b9e33e9a447f0f3fa9da69ad04e371fd1e4848cfb9e8d05e3165bd969d8", + "0xa39b1a8c39d317fcc97bf6c396e6ed4a85640aeeadbf45166bd02bc3bdfb6266509159c03afd492e642384c635b824c0", + "0xa2e1b90f3dd2d0038eaa5be52127844ccf35d997143179d95ffd3749c0896398b130094d01eb1bb31ffe80ef34b42b48", + "0xa2bbe31f89b0c3c375ffaf63c8b7831860a921d5e388eb7907dbf61f2601ea40db86bb3952ecaa26a5eca4317a848ff9", + "0x87d885bb0f2ce04b40ce94d2557c15f1698dc652e938f9a2d69a73ccf4899e08eafa1a59a20cae92823795f5b94f04b9", + "0x8f7746370f8a24a2889d351f3e36b8a7d60e75e50e8f5abeea7dafc75441e95915721654e61ceac51bb6f112780d352c", + "0xa7272847526ed3d9e0d0fea1d8685b07b5b908971490bf8a46748c8b1783c629b8644feb5bac772ae615daae383d5e72", + "0x978c9aa2996d8bd6fda7e0393fa8b38747f8f99712427705c00f6e9a12c36f8d8b4cedb03fcb9867155cbddb5200e6e1", + "0xa4dec4a2354b2b32434c5bcdc380bf84580c6f9940f94dc0498a5bfe89c675a0921e66b807a3d859a6059a464cb2a9ac", + "0x99459ddecc7abce437f68722dae556d8ffaf8ed974f459e52e6d4a64f176caa4d42c2f2ec57e8a5b5f2034638e8acb0a", + "0x928c68c0c9213fe6258ab5bb0c693d97203d15da359784de7824dec143212da57d062a1fc70a79172cee31adc7aff382", + "0xaad3f318f1622ea87e12541dfd982d71629b8f1ded4c301f9f6b6af9432716ad057773c33bdaa6f15dc151b0ee4505ea", + "0x8eb8e978f149a983fd6ad01773f9aacf57bd0cc622d8a301e404184b37e610123dd081faeda571a0ab1f149a3960af10", + "0x851e7191d7b94bd422bcece5b92609fc1b1c8556229bc53e32963b2d2fd1cacd8ce5da9040b599eca6e610540f8a7987", + "0x9414157fe9d50e5a0b5a7397417681bcb3a651eec1cab63f2a88d5df68ab1fef6e4c1d7ba657cbaf241a7cb790297633", + "0xb5cb2dafdc5408959780754a58b2da55b2a9136672ebca42f34da4e329ddc89360e7218cde3efdbf784ddb390deacc57", + "0xac6b70f65503a8e94b773fda3e72615745824930114fe72b6d833484285462392617c1b2eea4a250fedbee88f503f3ba", + "0xb0829a5312f9ac6c06fddee2f835a3452fe994f6d42c9edfc390d7d5b3240ca544433b544cbbddd6516b38a6d5d7c21d", + "0x95f8e2c59905957e34d53be3d6fb85732f834e2cb9ab4c333fea2f502452a87ccd035fc9075d7c0bd8530bb0a0c96527", + "0xb93f279b7045f2d97c674495f6e69a3e352f32f43cc60300193b936c2850b2805c15457251f7e3f633f435cb2b60405c", + "0x915abf16cba1a0b655b92a8a70c03e7fb306b86f3bbfb66967ca63e64c003b59c7a5953675efa4fa0bce9bed536b6700", + "0xac2047f50a319d09df1ec44d71afdcec5ac3bd2765dc98aba347734aa780863545df9f6d71214d443e3f37edc0dae45a", + "0xad49c74ddb24c8a26b14ec08bc807313c77c5967fbb36237f55994d7511bbac8d7e7b9b8ec53eb1b3b066989f078dbd9", + "0x961483105f605e959213fe9e8a52b76dac62d7efd2319ec71fc4e92d68fbe44cd2f65d7adefb2eb64d591b91648b8085", + "0xb67fcafc97d8df2b3075bbff7b3d7471dbf1f3048f309e55d5e2c5bcbc7a73aebcb0697859be9f387cbc7ce98041e154", + "0x8da70ac16468cab6066992389cb37c79ff5e0babbe67d76878aef9408b9597a3dc2eb5de87428bc761a0d78957b0eb28", + "0xaec0ce89770d299b631f15ae12f94b1e1014ac57d38fcf037c2c7712d770d074affa06e97c60691bad8733874b6ad2ed", + "0x8b702c85fa4c915a09fc86507f44d7aeda0993b77af87780d70cc98d580c6e996b64b7c16cdb4dd4562cb0f75da36ee7", + "0xaaeb43aa472aac2253e211fd1066c3a5422ea041cef20168702d0618a1a742a44f7fb30a76677640fea1a24e7fae1996", + "0xa8820e92825d6e02b9b4ad5ebc86161d3244cddd3d244333ba1576b6ae10948145b68d9e926bf6b7a2c25dab4cf43f3e", + "0x8ffdae28a1f1d15d7ffa473628a66ee9a739073f59ba781248286b39cb8f7255f66d62337064246713cbb5017e615174", + "0xadfc5dd142b7911326d8424881d5d92006f3b17de4cce91674d6ea37f00fbb266c791ac13f6c7a0f61d04f2a952e6a04", + "0x87f98982444bf661f539bec73a10256f079a4baa88a1cea0351ae3de929e1c500485b2d1b5d933063cd7d9123d5050e4", + "0x8f217ba4dd404c5ee384f0c9a126686db001ff0344c01c82174c5e5ef89d1a241b146008c534b13a0da6c8afe7450fbb", + "0xafc85476dddaf1cbb4ba8b22186789f3818c7964f9f613e55010278800cd95422702248bdf9c73760702ef24854795ec", + "0xa59e0f6ac2ccdfbd01f002008034390c0ea78716f5e0de4e474e3558755705c9c7afb6e3c5c4370e7bbc85958a9c7a63", + "0x97c0695c58d792ec31d9b86d3b2fc1382f0855057b24d5f6a54c41f76f9e2f52882cadc89a8b2f121530e7f1393faa95", + "0x8e49112de0b2649c08a96cf737af68fa8055f1af594846a2d0534c94df6f926f200405edaa6e6ac9db7e380707a2571d", + "0x99a1bd83a7ac5f8d77ddf044c80ebfc5745b998714696d67b94d185c97e9d6db989bacac646d9def463127a8b2febc00", + "0xaba80725f9f9f7abe10760eca73ba427ca8df864a157122eb9af828a05b0199de3add02019a297750bdab5380e505c58", + "0xae18f62573275c1eb268f74c5e54e8958547f9e7d1d36a05b084eb53e5704fafe2200b8aff95cc7e9af5be2391c42b7c", + "0x908b8031d09d22b2aefeaa876a998e0a97c7a1070aad9e9c97836cc5aa6d2d5ef94230e1222074837b5e21b4e6490f01", + "0xb3132282e8b41ca6789ec5c43c1fecf3a65b8eefbc2f3d10f746a843b9ba4ce6db664678e75e424f7b11a00c1440de15", + "0xa1eb49440cc106ebc09cf198c93e8070271eb5a936d31c04858a2b311a037350100c7957d5545c9653f396aa968b91f4", + "0x81df6ad1bdd5eee4cc2f94318467b8602d15cc1be2b48b09ade12cc46ee05cbaaf77a20397e5015030b1f1db5dd9dac0", + "0x87236c68a2a93c8442d15d7f1d1dc01d1fd123439c183e1d843f4ddd2bcf638c128f66f1ef9b710e5d1f64a52726007a", + "0x84f2e7f85563bb2f61b10a712c7605d63f79af5be0dba056814fd3efebc20e9c53227c56577b72c68d185571b775eff6", + "0xa36d4ae06688ece2927aeb2c7f058a3cd2aa1de1601282d4e688e1d76ef20728b892928deda2314eba41675eba3912f1", + "0xb8326dcbcdcfce017b263c456c47692fb476c4225c95981666fff0b7d4522fc23b7f12273f0f47cf0442662124e6648f", + "0x84c66463ab277cda2cc7007d0509269e89cdd41c5e0d3773a92615f0fc5da63811186b05d7a11088048a5d4834a7e0df", + "0xb20d3571d970712ef4699b0e7034fd269c361f53e1572e2ea2676b4245e992d43b8b5931a801439a44d977a988cc360b", + "0x94dba6007e6d4998ca1eb84aa8e2a7e9f5c164b9d80df2825f2208ce5640a05aacac2e4f08918268990f43ae1ccab69a", + "0xa1c25f0b3ef9d1982153207570d9ce8d692e1b6963b509958dc4d9bcd80074bb221c46804a6d9a29e76149cc7787c282", + "0x8857748fcdab1199fc96084323a81d3bd8b5a7f0b1abc5bc3b5252a19268344e2e7d2d086c90fc9b5fa4b92feedb93a4", + "0x8b9c1d841447354b6c086549e4d1d435ab64c13933488c34bc30f0f6eb36c5c5b838b7b6bb018542247edd1ada091045", + "0x8f5b655416da0e719a204fc567e93792c301acb4374cf7bbabc6ce51dbeaaadfd75c2db0e16ce073ab8e91fd3d7ea9d4", + "0x90f2846b19be46a75c5cd0cafefcf9192e6fd80c479e8d6320c4b8d8d7d96703c9e77ff31a67afa9858e6b7bde1f7cce", + "0xa53e383947fd98aa1a55ac956214b46b20a52758461e8ba41341a23a835ebb713038bf048edb1202bbfd0b56a96bf292", + "0x9542d7debbcfb9cda6fa279c699a7b655c03b9a9b456a5d3cfc41a826c94eafa43e01155a29e39ff0bcd965f4c0c512d", + "0xa43792864ec5fc549f7afc02622454afc0e425c310c4039ba615067243ebb26a4c7ebfd19bd4d57ff412a4bb2a7958a0", + "0xb85123950e30c048465bf32365d24a5d4b21fffc6183cdbf71643a07b87463989b72dd9a6a47f134856f704909a6b38f", + "0x944ea689aec1376f855c0bc9c51378ad06ff758a2c075b95a60b535b88b36eca0be11e4edb5152e98cb2137d6e749f27", + "0xa6bef52cda22325e4c62d323e2a0e3fa91c5552fcfce951edfd52ad6f652bfdcc2341f1cd349e6b5d447924dc569bfe2", + "0xb56bff8ffe981bfcb30791836da10b87f2ccbe17ed969e7f7a650af07d27ae0223805b1264d985148208483be50578a6", + "0x8b209cac898dd580c82d854a553e2517497ad1a4cd198e1360b8b50639b380aee70ee4b87625d9b2278228ff644cd25c", + "0x877cce233fec74c7158b3c5bf108365e98238418b8a71f058f1aca44a0fd3a1021e3e9025bd11fe244d9fe0f5034ce7f", + "0xb1b871aeedb03d6f6accc99816b89f5958178738d8d8cd9717527d04363c80fdb5f6848122ae19fdbc450cfa11e753c8", + "0x858aca51b9e5b0a724e88688d5124eb24c9faf01a3d465e74d31de6da315f311143f22f60201ea09f62c92f61f09d889", + "0x8521d409615dfc8c8289e00f6aaa6297c2c4e1439b25952afd76aac641b81c70b9cef07cd58c1c0198382bddd2bd8544", + "0x88647c3e41666b88acca42505f1f5da226937e0522b538fe0cebb724e9a99730ca2522989e94a96cac94109aef675c0f", + "0xb417fdaf719caf38854e89ce52031b30ce61a632e6c3135adec9002280e022d82ab0ea4ac5ebdb21f1f0169e4c37bcda", + "0x9367a6feb5e23ea2eab8ddd5e7bdf32b4d2419fad1c71a1ed327b77362d8942dad971a1c2e6f7073885149cdf0a0c339", + "0xa71c5c08d50c57d094d6a4f02e97d3799bada92f238ffc07bd223bbe8379507b7310d20b28f5bbbf331e5e153515e491", + "0x9630a9a3bcb044b51299c4d3d3388a4ff47308dd27be3229601985478c0f6b55faa7e20815d8694f910611396a9d0d45", + "0xb0bfaf56a5aa59b48960aa7c1617e832e65c823523fb2a5cd44ba606800501cf873e8db1d0dda64065285743dc40786e" + ], + "g1_lagrange": [ + "0xa0413c0dcafec6dbc9f47d66785cf1e8c981044f7d13cfe3e4fcbb71b5408dfde6312493cb3c1d30516cb3ca88c03654", + "0x8b997fb25730d661918371bb41f2a6e899cac23f04fc5365800b75433c0a953250e15e7a98fb5ca5cc56a8cd34c20c57", + "0x83302852db89424d5699f3f157e79e91dc1380f8d5895c5a772bb4ea3a5928e7c26c07db6775203ce33e62a114adaa99", + "0xa759c48b7e4a685e735c01e5aa6ef9c248705001f470f9ad856cd87806983e917a8742a3bd5ee27db8d76080269b7c83", + "0x967f8dc45ebc3be14c8705f43249a30ff48e96205fb02ae28daeab47b72eb3f45df0625928582aa1eb4368381c33e127", + "0xa418eb1e9fb84cb32b370610f56f3cb470706a40ac5a47c411c464299c45c91f25b63ae3fcd623172aa0f273c0526c13", + "0x8f44e3f0387293bc7931e978165abbaed08f53acd72a0a23ac85f6da0091196b886233bcee5b4a194db02f3d5a9b3f78", + "0x97173434b336be73c89412a6d70d416e170ea355bf1956c32d464090b107c090ef2d4e1a467a5632fbc332eeb679bf2d", + "0xa24052ad8d55ad04bc5d951f78e14213435681594110fd18173482609d5019105b8045182d53ffce4fc29fc8810516c1", + "0xb950768136b260277590b5bec3f56bbc2f7a8bc383d44ce8600e85bf8cf19f479898bcc999d96dfbd2001ede01d94949", + "0x92ab8077871037bd3b57b95cbb9fb10eb11efde9191690dcac655356986fd02841d8fdb25396faa0feadfe3f50baf56d", + "0xa79b096dff98038ac30f91112dd14b78f8ad428268af36d20c292e2b3b6d9ed4fb28480bb04e465071cc67d05786b6d1", + "0xb9ff71461328f370ce68bf591aa7fb13027044f42a575517f3319e2be4aa4843fa281e756d0aa5645428d6dfa857cef2", + "0x8d765808c00b3543ff182e2d159c38ae174b12d1314da88ea08e13bd9d1c37184cb515e6bf6420531b5d41767987d7ce", + "0xb8c9a837d20c3b53e6f578e4a257bb7ef8fc43178614ec2a154915b267ad2be135981d01ed2ee1b5fbd9d9bb27f0800a", + "0xa9773d92cf23f65f98ef68f6cf95c72b53d0683af2f9bf886bb9036e4a38184b1131b26fd24397910b494fbef856f3aa", + "0xb41ebe38962d112da4a01bf101cb248d808fbd50aaf749fc7c151cf332032eb3e3bdbd716db899724b734d392f26c412", + "0x90fbb030167fb47dcc13d604a726c0339418567c1d287d1d87423fa0cb92eec3455fbb46bcbe2e697144a2d3972142e4", + "0xb11d298bd167464b35fb923520d14832bd9ed50ed841bf6d7618424fd6f3699190af21759e351b89142d355952149da1", + "0x8bc36066f69dc89f7c4d1e58d67497675050c6aa002244cebd9fc957ec5e364c46bab4735ea3db02b73b3ca43c96e019", + "0xab7ab92c5d4d773068e485aa5831941ebd63db7118674ca38089635f3b4186833af2455a6fb9ed2b745df53b3ce96727", + "0xaf191ca3089892cb943cd97cf11a51f38e38bd9be50844a4e8da99f27e305e876f9ed4ab0628e8ae3939066b7d34a15f", + "0xa3204c1747feabc2c11339a542195e7cb6628fd3964f846e71e2e3f2d6bb379a5e51700682ea1844eba12756adb13216", + "0x903a29883846b7c50c15968b20e30c471aeac07b872c40a4d19eb1a42da18b649d5bbfde4b4cf6225d215a461b0deb6d", + "0x8e6e9c15ffbf1e16e5865a5fef7ed751dc81957a9757b535cb38b649e1098cda25d42381dc4f776778573cdf90c3e6e0", + "0xa8f6dd26100b512a8c96c52e00715c4b2cb9ac457f17aed8ffe1cf1ea524068fe5a1ddf218149845fc1417b789ecfc98", + "0xa5b0ffc819451ea639cfd1c18cbc9365cc79368d3b2e736c0ae54eba2f0801e6eb0ee14a5f373f4a70ca463bdb696c09", + "0x879f91ccd56a1b9736fbfd20d8747354da743fb121f0e308a0d298ff0d9344431890e41da66b5009af3f442c636b4f43", + "0x81bf3a2d9755e206b515a508ac4d1109bf933c282a46a4ae4a1b4cb4a94e1d23642fad6bd452428845afa155742ade7e", + "0x8de778d4742f945df40004964e165592f9c6b1946263adcdd5a88b00244bda46c7bb49098c8eb6b3d97a0dd46148a8ca", + "0xb7a57b21d13121907ee28c5c1f80ee2e3e83a3135a8101e933cf57171209a96173ff5037f5af606e9fd6d066de6ed693", + "0xb0877d1963fd9200414a38753dffd9f23a10eb3198912790d7eddbc9f6b477019d52ddd4ebdcb9f60818db076938a5a9", + "0x88da2d7a6611bc16adc55fc1c377480c828aba4496c645e3efe0e1a67f333c05a0307f7f1d2df8ac013602c655c6e209", + "0x95719eb02e8a9dede1a888c656a778b1c69b7716fbe3d1538fe8afd4a1bc972183c7d32aa7d6073376f7701df80116d8", + "0x8e8a1ca971f2444b35af3376e85dccda3abb8e8e11d095d0a4c37628dfe5d3e043a377c3de68289ef142e4308e9941a0", + "0xb720caaff02f6d798ac84c4f527203e823ff685869e3943c979e388e1c34c3f77f5c242c6daa7e3b30e511aab917b866", + "0x86040d55809afeec10e315d1ad950d269d37cfee8c144cd8dd4126459e3b15a53b3e68df5981df3c2346d23c7b4baaf4", + "0x82d8cabf13ab853db0377504f0aec00dba3a5cd3119787e8ad378ddf2c40b022ecfc67c642b7acc8c1e3dd03ab50993e", + "0xb8d873927936719d2484cd03a6687d65697e17dcf4f0d5aed6f5e4750f52ef2133d4645894e7ebfc4ef6ce6788d404c8", + "0xb1235594dbb15b674a419ff2b2deb644ad2a93791ca05af402823f87114483d6aa1689b7a9bea0f547ad12fe270e4344", + "0xa53fda86571b0651f5affb74312551a082fffc0385cfd24c1d779985b72a5b1cf7c78b42b4f7e51e77055f8e5e915b00", + "0xb579adcfd9c6ef916a5a999e77a0cb21d378c4ea67e13b7c58709d5da23a56c2e54218691fc4ac39a4a3d74f88cc31f7", + "0xab79e584011713e8a2f583e483a91a0c2a40771b77d91475825b5acbea82db4262132901cb3e4a108c46d7c9ee217a4e", + "0xa0fe58ea9eb982d7654c8aaf9366230578fc1362f6faae0594f8b9e659bcb405dff4aac0c7888bbe07f614ecf0d800a6", + "0x867e50e74281f28ecd4925560e2e7a6f8911b135557b688254623acce0dbc41e23ac3e706a184a45d54c586edc416eb0", + "0x89f81b61adda20ea9d0b387a36d0ab073dc7c7cbff518501962038be19867042f11fcc7ff78096e5d3b68c6d8dc04d9b", + "0xa58ee91bb556d43cf01f1398c5811f76dc0f11efdd569eed9ef178b3b0715e122060ec8f945b4dbf6eebfa2b90af6fa6", + "0xac460be540f4c840def2eef19fc754a9af34608d107cbadb53334cf194cc91138d53b9538fcd0ec970b5d4aa455b224a", + "0xb09b91f929de52c09d48ca0893be6eb44e2f5210a6c394689dc1f7729d4be4e11d0474b178e80cea8c2ac0d081f0e811", + "0x8d37a442a76b06a02a4e64c2504aea72c8b9b020ab7bcc94580fe2b9603c7c50d7b1e9d70d2a7daea19c68667e8f8c31", + "0xa9838d4c4e3f3a0075a952cf7dd623307ec633fcc81a7cf9e52e66c31780de33dbb3d74c320dc7f0a4b72f7a49949515", + "0xa44766b6251af458fe4f5f9ed1e02950f35703520b8656f09fc42d9a2d38a700c11a7c8a0436ac2e5e9f053d0bb8ff91", + "0xad78d9481c840f5202546bea0d13c776826feb8b1b7c72e83d99a947622f0bf38a4208551c4c41beb1270d7792075457", + "0xb619ffa8733b470039451e224b777845021e8dc1125f247a4ff2476cc774657d0ff9c5279da841fc1236047de9d81c60", + "0xaf760b0a30a1d6af3bc5cd6686f396bd41779aeeb6e0d70a09349bd5da17ca2e7965afc5c8ec22744198fbe3f02fb331", + "0xa0cc209abdb768b589fcb7b376b6e1cac07743288c95a1cf1a0354b47f0cf91fca78a75c1fcafa6f5926d6c379116608", + "0x864add673c89c41c754eeb3cd8dcff5cdde1d739fce65c30e474a082bb5d813cba6412e61154ce88fdb6c12c5d9be35b", + "0xb091443b0ce279327dc37cb484e9a5b69b257a714ce21895d67539172f95ffa326903747b64a3649e99aea7bb10d03f7", + "0xa8c452b8c4ca8e0a61942a8e08e28f17fb0ef4c5b018b4e6d1a64038280afa2bf1169202f05f14af24a06ca72f448ccd", + "0xa23c24721d18bc48d5dcf70effcbef89a7ae24e67158d70ae1d8169ee75d9a051d34b14e9cf06488bac324fe58549f26", + "0x92a730e30eb5f3231feb85f6720489dbb1afd42c43f05a1610c6b3c67bb949ec8fde507e924498f4ffc646f7b07d9123", + "0x8dbe5abf4031ec9ba6bb06d1a47dd1121fb9e03b652804069250967fd5e9577d0039e233441b7f837a7c9d67ba18c28e", + "0xaa456bcfef6a21bb88181482b279df260297b3778e84594ebddbdf337e85d9e3d46ca1d0b516622fb0b103df8ec519b7", + "0xa3b31ae621bd210a2b767e0e6f22eb28fe3c4943498a7e91753225426168b9a26da0e02f1dc5264da53a5ad240d9f51b", + "0xaa8d66857127e6e71874ce2202923385a7d2818b84cb73a6c42d71afe70972a70c6bdd2aad1a6e8c5e4ca728382a8ea8", + "0xac7e8e7a82f439127a5e40558d90d17990f8229852d21c13d753c2e97facf077cf59582b603984c3dd3faebd80aff4f5", + "0x93a8bcf4159f455d1baa73d2ef2450dcd4100420de84169bbe28b8b7a5d1746273f870091a87a057e834f754f34204b1", + "0x89d0ebb287c3613cdcae7f5acc43f17f09c0213fc40c074660120b755d664109ffb9902ed981ede79e018ddb0c845698", + "0xa87ccbfad431406aadbee878d9cf7d91b13649d5f7e19938b7dfd32645a43b114eef64ff3a13201398bd9b0337832e5a", + "0x833c51d0d0048f70c3eefb4e70e4ff66d0809c41838e8d2c21c288dd3ae9d9dfaf26d1742bf4976dab83a2b381677011", + "0x8bcd6b1c3b02fffead432e8b1680bad0a1ac5a712d4225e220690ee18df3e7406e2769e1f309e2e803b850bc96f0e768", + "0xb61e3dbd88aaf4ff1401521781e2eea9ef8b66d1fac5387c83b1da9e65c2aa2a56c262dea9eceeb4ad86c90211672db0", + "0x866d3090db944ecf190dd0651abf67659caafd31ae861bab9992c1e3915cb0952da7c561cc7e203560a610f48fae633b", + "0xa5e8971543c14274a8dc892b0be188c1b4fbc75c692ed29f166e0ea80874bc5520c2791342b7c1d2fb5dd454b03b8a5b", + "0x8f2f9fc50471bae9ea87487ebd1bc8576ef844cc42d606af5c4c0969670fdf2189afd643e4de3145864e7773d215f37f", + "0xb1bb0f2527db6d51f42b9224383c0f96048bbc03d469bf01fe1383173ef8b1cc9455d9dd8ba04d46057f46949bfc92b5", + "0xaa7c99d906b4d7922296cfe2520473fc50137c03d68b7865c5bfb8adbc316b1034310ec4b5670c47295f4a80fb8d61e9", + "0xa5d1da4d6aba555919df44cbaa8ff79378a1c9e2cfdfbf9d39c63a4a00f284c5a5724e28ecbc2d9dba27fe4ee5018bd5", + "0xa8db53224f70af4d991b9aae4ffe92d2aa5b618ad9137784b55843e9f16cefbfd25ada355d308e9bbf55f6d2f7976fb3", + "0xb6536c4232bb20e22af1a8bb12de76d5fec2ad9a3b48af1f38fa67e0f8504ef60f305a73d19385095bb6a9603fe29889", + "0x87f7e371a1817a63d6838a8cf4ab3a8473d19ce0d4f40fd013c03d5ddd5f4985df2956531cc9f187928ef54c68f4f9a9", + "0xae13530b1dbc5e4dced9d909ea61286ec09e25c12f37a1ed2f309b0eb99863d236c3b25ed3484acc8c076ad2fa8cd430", + "0x98928d850247c6f7606190e687d5c94a627550198dbdbea0161ef9515eacdb1a0f195cae3bb293112179082daccf8b35", + "0x918528bb8e6a055ad4db6230d3a405e9e55866da15c4721f5ddd1f1f37962d4904aad7a419218fe6d906fe191a991806", + "0xb71e31a06afe065773dd3f4a6e9ef81c3292e27a3b7fdfdd452d03e05af3b6dd654c355f7516b2a93553360c6681a73a", + "0x8870b83ab78a98820866f91ac643af9f3ff792a2b7fda34185a9456a63abdce42bfe8ad4dc67f08a6392f250d4062df4", + "0x91eea1b668e52f7a7a5087fabf1cab803b0316f78d9fff469fbfde2162f660c250e4336a9eea4cb0450bd30ac067bc8b", + "0x8b74990946de7b72a92147ceac1bd9d55999a8b576e8df68639e40ed5dc2062cfcd727903133de482b6dca19d0aaed82", + "0x8ebad537fece090ebbab662bdf2618e21ca30cf6329c50935e8346d1217dcbe3c1fe1ea28efca369c6003ce0a94703c1", + "0xa8640479556fb59ebd1c40c5f368fbd960932fdbb782665e4a0e24e2bdb598fc0164ce8c0726d7759cfc59e60a62e182", + "0xa9a52a6bf98ee4d749f6d38be2c60a6d54b64d5cbe4e67266633dc096cf28c97fe998596707d31968cbe2064b72256bf", + "0x847953c48a4ce6032780e9b39d0ed4384e0be202c2bbe2dfda3910f5d87aa5cd3c2ffbfcfae4dddce16d6ab657599b95", + "0xb6f6e1485d3ec2a06abaecd23028b200b2e4a0096c16144d07403e1720ff8f9ba9d919016b5eb8dc5103880a7a77a1d3", + "0x98dfc2065b1622f596dbe27131ea60bef7a193b12922cecb27f8c571404f483014f8014572e86ae2e341ab738e4887ef", + "0xacb0d205566bacc87bbe2e25d10793f63f7a1f27fd9e58f4f653ceae3ffeba511eaf658e068fad289eeb28f9edbeb35b", + "0xae4411ed5b263673cee894c11fe4abc72a4bf642d94022a5c0f3369380fcdfc1c21e277f2902972252503f91ada3029a", + "0xac4a7a27ba390a75d0a247d93d4a8ef1f0485f8d373a4af4e1139369ec274b91b3464d9738eeaceb19cd6f509e2f8262", + "0x87379c3bf231fdafcf6472a79e9e55a938d851d4dd662ab6e0d95fd47a478ed99e2ad1e6e39be3c0fc4f6d996a7dd833", + "0x81316904b035a8bcc2041199a789a2e6879486ba9fddcba0a82c745cc8dd8374a39e523b91792170cd30be7aa3005b85", + "0xb8206809c6cd027ed019f472581b45f7e12288f89047928ba32b4856b6560ad30395830d71e5e30c556f6f182b1fe690", + "0x88d76c028f534a62e019b4a52967bb8642ede6becfa3807be68fdd36d366fc84a4ac8dc176e80a68bc59eb62caf5dff9", + "0x8c3b8be685b0f8aad131ee7544d0e12f223f08a6f8edaf464b385ac644e0ddc9eff7cc7cb5c1b50ab5d71ea0f41d2213", + "0x8d91410e004f76c50fdc05784157b4d839cb5090022c629c7c97a5e0c3536eeafee17a527b54b1165c3cd81774bb54ce", + "0xb25c2863bc28ec5281ce800ddf91a7e1a53f4c6d5da1e6c86ef4616e93bcf55ed49e297216d01379f5c6e7b3c1e46728", + "0x865f7b09ac3ca03f20be90c48f6975dd2588838c2536c7a3532a6aa5187ed0b709cd03d91ff4048061c10d0aa72b69ce", + "0xb3f7477c90c11596eb4f8bbf34adbcb832638c4ff3cdd090d4d477ee50472ac9ddaf5be9ad7eca3f148960d362bbd098", + "0x8db35fd53fca04faecd1c76a8227160b3ab46ac1af070f2492445a19d8ff7c25bbaef6c9fa0c8c088444561e9f7e4eb2", + "0xa478b6e9d058a2e01d2fc053b739092e113c23a6a2770a16afbef044a3709a9e32f425ace9ba7981325f02667c3f9609", + "0x98caa6bd38916c08cf221722a675a4f7577f33452623de801d2b3429595f988090907a7e99960fff7c076d6d8e877b31", + "0xb79aaaacefc49c3038a14d2ac468cfec8c2161e88bdae91798d63552cdbe39e0e02f9225717436b9b8a40a022c633c6e", + "0x845a31006c680ee6a0cc41d3dc6c0c95d833fcf426f2e7c573fa15b2c4c641fbd6fe5ebb0e23720cc3467d6ee1d80dc4", + "0xa1bc287e272cf8b74dbf6405b3a5190883195806aa351f1dc8e525aa342283f0a35ff687e3b434324dedee74946dd185", + "0xa4fd2dc8db75d3783a020856e2b3aa266dc6926e84f5c491ef739a3bddd46dc8e9e0fc1177937839ef1b18d062ffbb9e", + "0xacbf0d3c697f57c202bb8c5dc4f3fc341b8fc509a455d44bd86acc67cad2a04495d5537bcd3e98680185e8aa286f2587", + "0xa5caf423a917352e1b8e844f5968a6da4fdeae467d10c6f4bbd82b5eea46a660b82d2f5440d3641c717b2c3c9ed0be52", + "0x8a39d763c08b926599ab1233219c49c825368fad14d9afc7c0c039224d37c00d8743293fd21645bf0b91eaf579a99867", + "0xb2b53a496def0ba06e80b28f36530fbe0fb5d70a601a2f10722e59abee529369c1ae8fd0f2db9184dd4a2519bb832d94", + "0xa73980fcef053f1b60ebbb5d78ba6332a475e0b96a0c724741a3abf3b59dd344772527f07203cf4c9cb5155ebed81fa0", + "0xa070d20acce42518ece322c9db096f16aed620303a39d8d5735a0df6e70fbeceb940e8d9f5cc38f3314b2240394ec47b", + "0xa50cf591f522f19ca337b73089557f75929d9f645f3e57d4f241e14cdd1ea3fb48d84bcf05e4f0377afbb789fbdb5d20", + "0x82a5ffce451096aca8eeb0cd2ae9d83db3ed76da3f531a80d9a70a346359bf05d74863ce6a7c848522b526156a5e20cd", + "0x88e0e84d358cbb93755a906f329db1537c3894845f32b9b0b691c29cbb455373d9452fadd1e77e20a623f6eaf624de6f", + "0xaa07ac7b84a6d6838826e0b9e350d8ec75e398a52e9824e6b0da6ae4010e5943fec4f00239e96433f291fef9d1d1e609", + "0xac8887bf39366034bc63f6cc5db0c26fd27307cbc3d6cce47894a8a019c22dd51322fb5096edc018227edfafc053a8f6", + "0xb7d26c26c5b33f77422191dca94977588ab1d4b9ce7d0e19c4a3b4cd1c25211b78c328dbf81e755e78cd7d1d622ad23e", + "0x99a676d5af49f0ba44047009298d8474cabf2d5bca1a76ba21eff7ee3c4691a102fdefea27bc948ccad8894a658abd02", + "0xb0d09a91909ab3620c183bdf1d53d43d39eb750dc7a722c661c3de3a1a5d383ad221f71bae374f8a71867505958a3f76", + "0x84681a883de8e4b93d68ac10e91899c2bbb815ce2de74bb48a11a6113b2a3f4df8aceabda1f5f67bc5aacac8c9da7221", + "0x9470259957780fa9b43521fab3644f555f5343281c72582b56d2efd11991d897b3b481cafa48681c5aeb80c9663b68f7", + "0xab1b29f7ece686e6fa968a4815da1d64f3579fed3bc92e1f3e51cd13a3c076b6cf695ed269d373300a62463dc98a4234", + "0x8ab415bfcd5f1061f7687597024c96dd9c7cb4942b5989379a7a3b5742f7d394337886317659cbeacaf030234a24f972", + "0xb9b524aad924f9acc63d002d617488f31b0016e0f0548f050cada285ce7491b74a125621638f19e9c96eabb091d945be", + "0x8c4c373e79415061837dd0def4f28a2d5d74d21cb13a76c9049ad678ca40228405ab0c3941df49249847ecdefc1a5b78", + "0xa8edf4710b5ab2929d3db6c1c0e3e242261bbaa8bcec56908ddadd7d2dad2dca9d6eb9de630b960b122ebeea41040421", + "0x8d66bb3b50b9df8f373163629f9221b3d4b6980a05ea81dc3741bfe9519cf3ebba7ab98e98390bae475e8ede5821bd5c", + "0x8d3c21bae7f0cfb97c56952bb22084b58e7bb718890935b73103f33adf5e4d99cd262f929c6eeab96209814f0dbae50a", + "0xa5c66cfab3d9ebf733c4af24bebc97070e7989fe3c73e79ac85fb0e4d40ae44fb571e0fad4ad72560e13ed453900d14f", + "0x9362e6b50b43dbefbc3254471372297b5dcce809cd3b60bf74a1268ab68bdb50e46e462cbd78f0d6c056330e982846af", + "0x854630d08e3f0243d570cc2e856234cb4c1a158d9c1883bf028a76525aaa34be897fe918d5f6da9764a3735fa9ebd24a", + "0x8c7d246985469ff252c3f4df6c7c9196fc79f05c1c66a609d84725c78001d0837c7a7049394ba5cf7e863e2d58af8417", + "0xae050271e01b528925302e71903f785b782f7bf4e4e7a7f537140219bc352dc7540c657ed03d3a297ad36798ecdb98cd", + "0x8d2ae9179fcf2b0c69850554580b52c1f4a5bd865af5f3028f222f4acad9c1ad69a8ef6c7dc7b03715ee5c506b74325e", + "0xb8ef8de6ce6369a8851cd36db0ccf00a85077e816c14c4e601f533330af9e3acf0743a95d28962ed8bfcfc2520ef3cfe", + "0xa6ecad6fdfb851b40356a8b1060f38235407a0f2706e7b8bb4a13465ca3f81d4f5b99466ac2565c60af15f022d26732e", + "0x819ff14cdea3ab89d98e133cd2d0379361e2e2c67ad94eeddcdb9232efd509f51d12f4f03ebd4dd953bd262a886281f7", + "0x8561cd0f7a6dbcddd83fcd7f472d7dbcba95b2d4fb98276f48fccf69f76d284e626d7e41314b633352df8e6333fd52a1", + "0xb42557ccce32d9a894d538c48712cb3e212d06ac05cd5e0527ccd2db1078ee6ae399bf6a601ffdab1f5913d35fc0b20c", + "0x89b4008d767aad3c6f93c349d3b956e28307311a5b1cec237e8d74bb0dee7e972c24f347fd56afd915a2342bd7bc32f0", + "0x877487384b207e53f5492f4e36c832c2227f92d1bb60542cfeb35e025a4a7afc2b885fae2528b33b40ab09510398f83e", + "0x8c411050b63c9053dd0cd81dacb48753c3d7f162028098e024d17cd6348482703a69df31ad6256e3d25a8bbf7783de39", + "0xa8506b54a88d17ac10fb1b0d1fe4aa40eae7553a064863d7f6b52ccc4236dd4b82d01dca6ba87da9a239e3069ba879fb", + "0xb1a24caef9df64750c1350789bb8d8a0db0f39474a1c74ea9ba064b1516db6923f00af8d57c632d58844fb8786c3d47a", + "0x959d6e255f212b0708c58a2f75cb1fe932248c9d93424612c1b8d1e640149656059737e4db2139afd5556bcdacf3eda2", + "0x84525af21a8d78748680b6535bbc9dc2f0cf9a1d1740d12f382f6ecb2e73811d6c1da2ad9956070b1a617c61fcff9fe5", + "0xb74417d84597a485d0a8e1be07bf78f17ebb2e7b3521b748f73935b9afbbd82f34b710fb7749e7d4ab55b0c7f9de127d", + "0xa4a9aecb19a6bab167af96d8b9d9aa5308eab19e6bfb78f5a580f9bf89bdf250a7b52a09b75f715d651cb73febd08e84", + "0x9777b30be2c5ffe7d29cc2803a562a32fb43b59d8c3f05a707ab60ec05b28293716230a7d264d7cd9dd358fc031cc13e", + "0x95dce7a3d4f23ac0050c510999f5fbf8042f771e8f8f94192e17bcbfa213470802ebdbe33a876cb621cf42e275cbfc8b", + "0xb0b963ebcbbee847ab8ae740478544350b3ac7e86887e4dfb2299ee5096247cd2b03c1de74c774d9bde94ae2ee2dcd59", + "0xa4ab20bafa316030264e13f7ef5891a2c3b29ab62e1668fcb5881f50a9acac6adbe3d706c07e62f2539715db768f6c43", + "0x901478a297669d608e406fe4989be75264b6c8be12169aa9e0ad5234f459ca377f78484ffd2099a2fe2db5e457826427", + "0x88c76e5c250810c057004a03408b85cd918e0c8903dc55a0dd8bb9b4fc2b25c87f9b8cf5943eb19fbbe99d36490050c5", + "0x91607322bbad4a4f03fc0012d0821eff5f8c516fda45d1ec1133bface6f858bf04b25547be24159cab931a7aa08344d4", + "0x843203e07fce3c6c81f84bc6dc5fb5e9d1c50c8811ace522dc66e8658433a0ef9784c947e6a62c11bf705307ef05212e", + "0x91dd8813a5d6dddcda7b0f87f672b83198cd0959d8311b2b26fb1fae745185c01f796fbd03aad9db9b58482483fdadd8", + "0x8d15911aacf76c8bcd7136e958febd6963104addcd751ce5c06b6c37213f9c4fb0ffd4e0d12c8e40c36d658999724bfd", + "0x8a36c5732d3f1b497ebe9250610605ee62a78eaa9e1a45f329d09aaa1061131cf1d9df00f3a7d0fe8ad614a1ff9caaae", + "0xa407d06affae03660881ce20dab5e2d2d6cddc23cd09b95502a9181c465e57597841144cb34d22889902aff23a76d049", + "0xb5fd856d0578620a7e25674d9503be7d97a2222900e1b4738c1d81ff6483b144e19e46802e91161e246271f90270e6cf", + "0x91b7708869cdb5a7317f88c0312d103f8ce90be14fb4f219c2e074045a2a83636fdc3e69e862049fc7c1ef000e832541", + "0xb64719cc5480709d1dae958f1d3082b32a43376da446c8f9f64cb02a301effc9c34d9102051733315a8179aed94d53cc", + "0x94347a9542ff9d18f7d9eaa2f4d9b832d0e535fe49d52aa2de08aa8192400eddabdb6444a2a78883e27c779eed7fdf5a", + "0x840ef44a733ff1376466698cd26f82cf56bb44811e196340467f932efa3ae1ef9958a0701b3b032f50fd9c1d2aed9ab5", + "0x90ab3f6f67688888a31ffc2a882bb37adab32d1a4b278951a21646f90d03385fc976715fc639a785d015751171016f10", + "0xb56f35d164c24b557dbcbc8a4bfa681ec916f8741ffcb27fb389c164f4e3ed2be325210ef5bdaeae7a172ca9599ab442", + "0xa7921a5a80d7cf6ae81ba9ee05e0579b18c20cd2852762c89d6496aa4c8ca9d1ca2434a67b2c16d333ea8e382cdab1e3", + "0xa506bcfbd7e7e5a92f68a1bd87d07ad5fe3b97aeee40af2bf2cae4efcd77fff03f872732c5b7883aa6584bee65d6f8cb", + "0xa8c46cff58931a1ce9cbe1501e1da90b174cddd6d50f3dfdfb759d1d4ad4673c0a8feed6c1f24c7af32865a7d6c984e5", + "0xb45686265a83bff69e312c5149db7bb70ac3ec790dc92e392b54d9c85a656e2bf58596ce269f014a906eafc97461aa5f", + "0x8d4009a75ccb2f29f54a5f16684b93202c570d7a56ec1a8b20173269c5f7115894f210c26b41e8d54d4072de2d1c75d0", + "0xaef8810af4fc676bf84a0d57b189760ddc3375c64e982539107422e3de2580b89bd27aa6da44e827b56db1b5555e4ee8", + "0x888f0e1e4a34f48eb9a18ef4de334c27564d72f2cf8073e3d46d881853ac1424d79e88d8ddb251914890588937c8f711", + "0xb64b0aa7b3a8f6e0d4b3499fe54e751b8c3e946377c0d5a6dbb677be23736b86a7e8a6be022411601dd75012012c3555", + "0x8d57776f519f0dd912ea14f79fbab53a30624e102f9575c0bad08d2dc754e6be54f39b11278c290977d9b9c7c0e1e0ad", + "0xa018fc00d532ceb2e4de908a15606db9b6e0665dd77190e2338da7c87a1713e6b9b61554e7c1462f0f6d4934b960b15c", + "0x8c932be83ace46f65c78e145b384f58e41546dc0395270c1397874d88626fdeda395c8a289d602b4c312fe98c1311856", + "0x89174838e21639d6bdd91a0621f04dc056907b88e305dd66e46a08f6d65f731dea72ae87ca5e3042d609e8de8de9aa26", + "0xb7b7f508bb74f7a827ac8189daa855598ff1d96fa3a02394891fd105d8f0816224cd50ac4bf2ed1cf469ace516c48184", + "0xb31877ad682583283baadd68dc1bebd83f5748b165aadd7fe9ef61a343773b88bcd3a022f36d6c92f339b7bfd72820a9", + "0xb79d77260b25daf9126dab7a193df2d7d30542786fa1733ffaf6261734770275d3ca8bae1d9915d1181a78510b3439db", + "0x91894fb94cd4c1dd2ceaf9c53a7020c5799ba1217cf2d251ea5bc91ed26e1159dd758e98282ebe35a0395ef9f1ed15a0", + "0xab59895cdafd33934ceedfc3f0d5d89880482cba6c99a6db93245f9e41987efd76e0640e80aef31782c9a8c7a83fccec", + "0xaa22ea63654315e033e09d4d4432331904a6fc5fb1732557987846e3c564668ca67c60a324b4af01663a23af11a9ce4b", + "0xb53ba3ef342601467e1f71aa280e100fbabbd38518fa0193e0099505036ee517c1ac78e96e9baeb549bb6879bb698fb0", + "0x943fd69fd656f37487cca3605dc7e5a215fddd811caf228595ec428751fc1de484a0cb84c667fe4d7c35599bfa0e5e34", + "0x9353128b5ebe0dddc555093cf3e5942754f938173541033e8788d7331fafc56f68d9f97b4131e37963ab7f1c8946f5f1", + "0xa76cd3c566691f65cfb86453b5b31dbaf3cab8f84fe1f795dd1e570784b9b01bdd5f0b3c1e233942b1b5838290e00598", + "0x983d84b2e53ffa4ae7f3ba29ef2345247ea2377686b74a10479a0ef105ecf90427bf53b74c96dfa346d0f842b6ffb25b", + "0x92e0fe9063306894a2c6970c001781cff416c87e87cb5fbac927a3192655c3da4063e6fa93539f6ff58efac6adcc5514", + "0xb00a81f03c2b8703acd4e2e4c21e06973aba696415d0ea1a648ace2b0ea19b242fede10e4f9d7dcd61c546ab878bc8f9", + "0xb0d08d880f3b456a10bf65cff983f754f545c840c413aea90ce7101a66eb0a0b9b1549d6c4d57725315828607963f15a", + "0x90cb64d03534f913b411375cce88a9e8b1329ce67a9f89ca5df8a22b8c1c97707fec727dbcbb9737f20c4cf751359277", + "0x8327c2d42590dfcdb78477fc18dcf71608686ad66c49bce64d7ee874668be7e1c17cc1042a754bbc77c9daf50b2dae07", + "0x8532171ea13aa7e37178e51a6c775da469d2e26ec854eb16e60f3307db4acec110d2155832c202e9ba525fc99174e3b0", + "0x83ca44b15393d021de2a511fa5511c5bd4e0ac7d67259dce5a5328f38a3cce9c3a269405959a2486016bc27bb140f9ff", + "0xb1d36e8ca812be545505c8214943b36cabee48112cf0de369957afa796d37f86bf7249d9f36e8e990f26f1076f292b13", + "0x9803abf45be5271e2f3164c328d449efc4b8fc92dfc1225d38e09630909fe92e90a5c77618daa5f592d23fc3ad667094", + "0xb268ad68c7bf432a01039cd889afae815c3e120f57930d463aece10af4fd330b5bd7d8869ef1bcf6b2e78e4229922edc", + "0xa4c91a0d6f16b1553264592b4cbbbf3ca5da32ab053ffbdd3dbb1aed1afb650fb6e0dc5274f71a51d7160856477228db", + "0xad89d043c2f0f17806277ffdf3ecf007448e93968663f8a0b674254f36170447b7527d5906035e5e56f4146b89b5af56", + "0x8b6964f757a72a22a642e4d69102951897e20c21449184e44717bd0681d75f7c5bfa5ee5397f6e53febf85a1810d6ed1", + "0xb08f5cdaabec910856920cd6e836c830b863eb578423edf0b32529488f71fe8257d90aed4a127448204df498b6815d79", + "0xaf26bb3358be9d280d39b21d831bb53145c4527a642446073fee5a86215c4c89ff49a3877a7a549486262f6f57a0f476", + "0xb4010b37ec4d7c2af20800e272539200a6b623ae4636ecbd0e619484f4ab9240d02bc5541ace3a3fb955dc0a3d774212", + "0x82752ab52bdcc3cc2fc405cb05a2e694d3df4a3a68f2179ec0652536d067b43660b96f85f573f26fbd664a9ef899f650", + "0x96d392dde067473a81faf2d1fea55b6429126b88b160e39b4210d31d0a82833ffd3a80e07d24d495aea2d96be7251547", + "0xa76d8236d6671204d440c33ac5b8deb71fa389f6563d80e73be8b043ec77d4c9b06f9a586117c7f957f4af0331cbc871", + "0xb6c90961f68b5e385d85c9830ec765d22a425f506904c4d506b87d8944c2b2c09615e740ed351df0f9321a7b93979cae", + "0xa6ec5ea80c7558403485b3b1869cdc63bde239bafdf936d9b62a37031628402a36a2cfa5cfbb8e26ac922cb0a209b3ba", + "0x8c3195bbdbf9bc0fc95fa7e3d7f739353c947f7767d1e3cb24d8c8602d8ea0a1790ac30b815be2a2ba26caa5227891e2", + "0xa7f8a63d809f1155722c57f375ea00412b00147776ae4444f342550279ef4415450d6f400000a326bf11fea6c77bf941", + "0x97fa404df48433a00c85793440e89bb1af44c7267588ae937a1f5d53e01e1c4d4fc8e4a6d517f3978bfdd6c2dfde012f", + "0xa984a0a3836de3d8d909c4629a2636aacb85393f6f214a2ef68860081e9db05ad608024762db0dc35e895dc00e2d4cdd", + "0x9526cf088ab90335add1db4d3a4ac631b58cbfbe88fa0845a877d33247d1cfeb85994522e1eb8f8874651bfb1df03e2a", + "0xac83443fd0afe99ad49de9bf8230158c118e2814c9c89db5ac951c240d6c2ce45e7677221279d9e97848ec466b99aafe", + "0xaeeefdbaba612e971697798ceaf63b247949dc823a0ad771ae5b988a5e882b338a98d3d0796230f49d533ec5ba411b39", + "0xae3f248b5a7b0f92b7820a6c5ae21e5bd8f4265d4f6e21a22512079b8ee9be06393fd3133ce8ebac0faf23f4f8517e36", + "0xa64a831b908eee784b8388b45447d2885ec0551b26b0c2b15e5f417d0a12c79e867fb7bd3d008d0af98b44336f8ec1ad", + "0xb242238cd8362b6e440ba21806905714dd55172db25ec7195f3fc4937b2aba146d5cbf3cf691a1384b4752dc3b54d627", + "0x819f97f337eea1ffb2a678cc25f556f1aab751c6b048993a1d430fe1a3ddd8bb411c152e12ca60ec6e057c190cd1db9a", + "0xb9d7d187407380df54ee9fef224c54eec1bfabf17dc8abf60765b7951f538f59aa26fffd5846cfe05546c35f59b573f4", + "0xaa6e3c14efa6a5962812e3f94f8ce673a433f4a82d07a67577285ea0eaa07f8be7115853122d12d6d4e1fdf64c504be1", + "0x82268bee9c1662d3ddb5fb785abfae6fb8b774190f30267f1d47091d2cd4b3874db4372625aa36c32f27b0eee986269b", + "0xb236459565b7b966166c4a35b2fa71030b40321821b8e96879d95f0e83a0baf33fa25721f30af4a631df209e25b96061", + "0x8708d752632d2435d2d5b1db4ad1fa2558d776a013655f88e9a3556d86b71976e7dfe5b8834fdec97682cd94560d0d0d", + "0xae1424a68ae2dbfb0f01211f11773732a50510b5585c1fb005cb892b2c6a58f4a55490b5c5b4483c6fce40e9d3236a52", + "0xb3f5f722af9dddb07293c871ce97abbccba0093ca98c8d74b1318fa21396fc1b45b69c15084f63d728f9908442024506", + "0x9606f3ce5e63886853ca476dc0949e7f1051889d529365c0cb0296fdc02abd088f0f0318ecd2cf36740a3634132d36f6", + "0xb11a833a49fa138db46b25ff8cdda665295226595bc212c0931b4931d0a55c99da972c12b4ef753f7e37c6332356e350", + "0xafede34e7dab0a9e074bc19a7daddb27df65735581ca24ad70c891c98b1349fcebbcf3ba6b32c2617fe06a5818dabc2d", + "0x97993d456e459e66322d01f8eb13918979761c3e8590910453944bdff90b24091bb018ac6499792515c9923be289f99f", + "0x977e3e967eff19290a192cd11df3667d511b398fb3ac9a5114a0f3707e25a0edcb56105648b1b85a8b7519fc529fc6f6", + "0xb873a7c88bf58731fe1bf61ff6828bf114cf5228f254083304a4570e854e83748fc98683ddba62d978fff7909f2c5c47", + "0xad4b2691f6f19da1d123aaa23cca3e876247ed9a4ab23c599afdbc0d3aa49776442a7ceaa996ac550d0313d9b9a36cee", + "0xb9210713c78e19685608c6475bfa974b57ac276808a443f8b280945c5d5f9c39da43effa294bfb1a6c6f7b6b9f85bf6c", + "0xa65152f376113e61a0e468759de38d742caa260291b4753391ee408dea55927af08a4d4a9918600a3bdf1df462dffe76", + "0x8bf8c27ad5140dde7f3d2280fd4cc6b29ab76537e8d7aa7011a9d2796ee3e56e9a60c27b5c2da6c5e14fc866301dc195", + "0x92fde8effc9f61393a2771155812b863cff2a0c5423d7d40aa04d621d396b44af94ddd376c28e7d2f53c930aea947484", + "0x97a01d1dd9ee30553ce676011aea97fa93d55038ada95f0057d2362ae9437f3ed13de8290e2ff21e3167dd7ba10b9c3f", + "0x89affffaa63cb2df3490f76f0d1e1d6ca35c221dd34057176ba739fa18d492355e6d2a5a5ad93a136d3b1fed0bb8aa19", + "0x928b8e255a77e1f0495c86d3c63b83677b4561a5fcbbe5d3210f1e0fc947496e426d6bf3b49394a5df796c9f25673fc4", + "0x842a0af91799c9b533e79ee081efe2a634cac6c584c2f054fb7d1db67dde90ae36de36cbf712ec9cd1a0c7ee79e151ea", + "0xa65b946cf637e090baf2107c9a42f354b390e7316beb8913638130dbc67c918926eb87bec3b1fe92ef72bc77a170fa3b", + "0xaafc0f19bfd71ab5ae4a8510c7861458b70ad062a44107b1b1dbacbfa44ba3217028c2824bd7058e2fa32455f624040b", + "0x95269dc787653814e0be899c95dba8cfa384f575a25e671c0806fd80816ad6797dc819d30ae06e1d0ed9cb01c3950d47", + "0xa1e760f7fa5775a1b2964b719ff961a92083c5c617f637fc46e0c9c20ab233f8686f7f38c3cb27d825c54dd95e93a59b", + "0xac3b8a7c2317ea967f229eddc3e23e279427f665c4705c7532ed33443f1243d33453c1088f57088d2ab1e3df690a9cc9", + "0xb787beeddfbfe36dd51ec4efd9cf83e59e84d354c3353cc9c447be53ae53d366ed1c59b686e52a92f002142c8652bfe0", + "0xb7a64198300cb6716aa7ac6b25621f8bdec46ad5c07a27e165b3f774cdf65bcfdbf31e9bae0c16b44de4b00ada7a4244", + "0xb8ae9f1452909e0c412c7a7fe075027691ea8df1347f65a5507bc8848f1d2c833d69748076db1129e5b4fb912f65c86c", + "0x9682e41872456b9fa67def89e71f06d362d6c8ca85c9c48536615bc401442711e1c9803f10ab7f8ab5feaec0f9df20a6", + "0x88889ff4e271dc1c7e21989cc39f73cde2f0475acd98078281591ff6c944fadeb9954e72334319050205d745d4df73df", + "0x8f79b5b8159e7fd0d93b0645f3c416464f39aec353b57d99ecf24f96272df8a068ad67a6c90c78d82c63b40bb73989bb", + "0x838c01a009a3d8558a3f0bdd5e22de21af71ca1aefc8423c91dc577d50920e9516880e87dce3e6d086e11cd45c9052d9", + "0xb97f1c6eee8a78f137c840667cc288256e39294268a3009419298a04a1d0087c9c9077b33c917c65caf76637702dda8a", + "0x972284ce72f96a61c899260203dfa06fc3268981732bef74060641c1a5068ead723e3399431c247ca034b0dae861e8df", + "0x945a8d52d6d3db6663dbd3110c6587f9e9c44132045eeffba15621576d178315cb52870fa5861669f84f0bee646183fe", + "0xa0a547b5f0967b1c3e5ec6c6a9a99f0578521489180dfdfbb5561f4d166baac43a2f06f950f645ce991664e167537eed", + "0xa0592cda5cdddf1340033a745fd13a6eff2021f2e26587116c61c60edead067e0f217bc2bef4172a3c9839b0b978ab35", + "0xb9c223b65a3281587fa44ec829e609154b32f801fd1de6950e01eafb07a8324243b960d5735288d0f89f0078b2c42b5b", + "0x99ebfc3b8f9f98249f4d37a0023149ed85edd7a5abe062c8fb30c8c84555258b998bdcdd1d400bc0fa2a4aaa8b224466", + "0x955b68526e6cb3937b26843270f4e60f9c6c8ece2fa9308fe3e23afa433309c068c66a4bc16ee2cf04220f095e9afce4", + "0xb766caeafcc00378135ae53397f8a67ed586f5e30795462c4a35853de6681b1f17401a1c40958de32b197c083b7279c1", + "0x921bf87cad947c2c33fa596d819423c10337a76fe5a63813c0a9dc78a728207ae7b339407a402fc4d0f7cba3af6da6fc", + "0xa74ba1f3bc3e6c025db411308f49b347ec91da1c916bda9da61e510ec8d71d25e0ac0f124811b7860e5204f93099af27", + "0xa29b4d144e0bf17a7e8353f2824cef0ce85621396babe8a0b873ca1e8a5f8d508b87866cf86da348470649fceefd735c", + "0xa8040e12ffc3480dd83a349d06741d1572ef91932c46f5cf03aee8454254156ee95786fd013d5654725e674c920cec32", + "0x8c4cf34ca60afd33923f219ffed054f90cd3f253ffeb2204a3b61b0183417e366c16c07fae860e362b0f2bfe3e1a1d35", + "0x8195eede4ddb1c950459df6c396b2e99d83059f282b420acc34220cadeed16ab65c856f2c52568d86d3c682818ed7b37", + "0x91fff19e54c15932260aa990c7fcb3c3c3da94845cc5aa8740ef56cf9f58d19b4c3c55596f8d6c877f9f4d22921d93aa", + "0xa3e0bf7e5d02a80b75cf75f2db7e66cb625250c45436e3c136d86297d652590ec97c2311bafe407ad357c79ab29d107b", + "0x81917ff87e5ed2ae4656b481a63ced9e6e5ff653b8aa6b7986911b8bc1ee5b8ef4f4d7882c3f250f2238e141b227e510", + "0x915fdbe5e7de09c66c0416ae14a8750db9412e11dc576cf6158755fdcaf67abdbf0fa79b554cac4fe91c4ec245be073f", + "0x8df27eafb5c3996ba4dc5773c1a45ca77e626b52e454dc1c4058aa94c2067c18332280630cc3d364821ee53bf2b8c130", + "0x934f8a17c5cbb827d7868f5c8ca00cb027728a841000a16a3428ab16aa28733f16b52f58c9c4fbf75ccc45df72d9c4df", + "0xb83f4da811f9183c25de8958bc73b504cf790e0f357cbe74ef696efa7aca97ad3b7ead1faf76e9f982c65b6a4d888fc2", + "0x87188213c8b5c268dc2b6da413f0501c95749e953791b727450af3e43714149c115b596b33b63a2f006a1a271b87efd0", + "0x83e9e888ab9c3e30761de635d9aabd31248cdd92f7675fc43e4b21fd96a03ec1dc4ad2ec94fec857ffb52683ac98e360", + "0xb4b9a1823fe2d983dc4ec4e3aaea297e581c3fc5ab4b4af5fa1370caa37af2d1cc7fc6bfc5e7da60ad8fdce27dfe4b24", + "0x856388bc78aef465dbcdd1f559252e028c9e9a2225c37d645c138e78f008f764124522705822a61326a6d1c79781e189", + "0xa6431b36db93c3b47353ba22e7c9592c9cdfb9cbdd052ecf2cc3793f5b60c1e89bc96e6bae117bfd047f2308da00dd2f", + "0xb619972d48e7e4291542dcde08f7a9cdc883c892986ded2f23ccb216e245cd8d9ad1d285347b0f9d7611d63bf4cee2bc", + "0x8845cca6ff8595955f37440232f8e61d5351500bd016dfadd182b9d39544db77a62f4e0102ff74dd4173ae2c181d24ef", + "0xb2f5f7fa26dcd3b6550879520172db2d64ee6aaa213cbef1a12befbce03f0973a22eb4e5d7b977f466ac2bf8323dcedd", + "0x858b7f7e2d44bdf5235841164aa8b4f3d33934e8cb122794d90e0c1cac726417b220529e4f896d7b77902ab0ccd35b3a", + "0x80b0408a092dae2b287a5e32ea1ad52b78b10e9c12f49282976cd738f5d834e03d1ad59b09c5ccaccc39818b87d06092", + "0xb996b0a9c6a2d14d984edcd6ab56bc941674102980d65b3ad9733455f49473d3f587c8cbf661228a7e125ddbe07e3198", + "0x90224fcebb36865293bd63af786e0c5ade6b67c4938d77eb0cbae730d514fdd0fe2d6632788e858afd29d46310cf86df", + "0xb71351fdfff7168b0a5ec48397ecc27ac36657a8033d9981e97002dcca0303e3715ce6dd3f39423bc8ef286fa2e9e669", + "0xae2a3f078b89fb753ce4ed87e0c1a58bb19b4f0cfb6586dedb9fcab99d097d659a489fb40e14651741e1375cfc4b6c5f", + "0x8ef476b118e0b868caed297c161f4231bbeb863cdfa5e2eaa0fc6b6669425ce7af50dc374abceac154c287de50c22307", + "0x92e46ab472c56cfc6458955270d3c72b7bde563bb32f7d4ab4d959db6f885764a3d864e1aa19802fefaa5e16b0cb0b54", + "0x96a3f68323d1c94e73d5938a18a377af31b782f56212de3f489d22bc289cf24793a95b37f1d6776edf88114b5c1fa695", + "0x962cc068cfce6faaa27213c4e43e44eeff0dfbb6d25b814e82c7da981fb81d7d91868fa2344f05fb552362f98cfd4a72", + "0x895d4e4c4ad670abf66d43d59675b1add7afad7438ada8f42a0360c704cee2060f9ac15b4d27e9b9d0996bb801276fe3", + "0xb3ad18d7ece71f89f2ef749b853c45dc56bf1c796250024b39a1e91ed11ca32713864049c9aaaea60cde309b47486bbf", + "0x8f05404e0c0258fdbae50e97ccb9b72ee17e0bd2400d9102c0dad981dac8c4c71585f03e9b5d50086d0a2d3334cb55d1", + "0x8bd877e9d4591d02c63c6f9fc9976c109de2d0d2df2bfa5f6a3232bab5b0b8b46e255679520480c2d7a318545efa1245", + "0x8d4c16b5d98957c9da13d3f36c46f176e64e5be879f22be3179a2c0e624fe4758a82bf8c8027410002f973a3b84cd55a", + "0x86e2a8dea86427b424fa8eada881bdff896907084a495546e66556cbdf070b78ba312bf441eb1be6a80006d25d5097a3", + "0x8608b0c117fd8652fdab0495b08fadbeba95d9c37068e570de6fddfef1ba4a1773b42ac2be212836141d1bdcdef11a17", + "0xa13d6febf5fb993ae76cae08423ca28da8b818d6ef0fde32976a4db57839cd45b085026b28ee5795f10a9a8e3098c683", + "0x8e261967fa6de96f00bc94a199d7f72896a6ad8a7bbb1d6187cca8fad824e522880e20f766620f4f7e191c53321d70f9", + "0x8b8e8972ac0218d7e3d922c734302803878ad508ca19f5f012bc047babd8a5c5a53deb5fe7c15a4c00fd6d1cb9b1dbd0", + "0xb5616b233fb3574a2717d125a434a2682ff68546dccf116dd8a3b750a096982f185614b9fb6c7678107ff40a451f56fa", + "0xaa6adf9b0c3334b0d0663f583a4914523b2ac2e7adffdb026ab9109295ff6af003ef8357026dbcf789896d2afded8d73", + "0xacb72df56a0b65496cd534448ed4f62950bb1e11e50873b6ed349c088ee364441821294ce0f7c61bd7d38105bea3b442", + "0xabae12df83e01ec947249fedd0115dc501d2b03ff7232092979eda531dbbca29ace1d46923427c7dde4c17bdf3fd7708", + "0x820b4fc2b63a9fda7964acf5caf19a2fc4965007cb6d6b511fcafcb1f71c3f673a1c0791d3f86e3a9a1eb6955b191cc0", + "0xaf277259d78c6b0f4f030a10c53577555df5e83319ddbad91afbd7c30bc58e7671c56d00d66ec3ab5ef56470cd910cee", + "0xad4a861c59f1f5ca1beedd488fb3d131dea924fffd8e038741a1a7371fad7370ca5cf80dc01f177fbb9576713bb9a5b3", + "0xb67a5162982ce6a55ccfb2f177b1ec26b110043cf18abd6a6c451cf140b5af2d634591eb4f28ad92177d8c7e5cd0a5e8", + "0x96176d0a83816330187798072d449cbfccff682561e668faf6b1220c9a6535b32a6e4f852e8abb00f79abb87493df16b", + "0xb0afe6e7cb672e18f0206e4423f51f8bd0017bf464c4b186d46332c5a5847647f89ff7fa4801a41c1b0b42f6135bcc92", + "0x8fc5e7a95ef20c1278c645892811f6fe3f15c431ebc998a32ec0da44e7213ea934ed2be65239f3f49b8ec471e9914160", + "0xb7793e41adda6c82ba1f2a31f656f6205f65bf8a3d50d836ee631bc7ce77c153345a2d0fc5c60edf8b37457c3729c4ec", + "0xa504dd7e4d6b2f4379f22cc867c65535079c75ccc575955f961677fa63ecb9f74026fa2f60c9fb6323c1699259e5e9c8", + "0xab899d00ae693649cc1afdf30fb80d728973d2177c006e428bf61c7be01e183866614e05410041bc82cb14a33330e69c", + "0x8a3bd8b0b1be570b65c4432a0f6dc42f48a2000e30ab089cf781d38f4090467b54f79c0d472fcbf18ef6a00df69cc6f3", + "0xb4d7028f7f76a96a3d7803fca7f507ae11a77c5346e9cdfccb120a833a59bda1f4264e425aa588e7a16f8e7638061d84", + "0xb9c7511a76ea5fb105de905d44b02edb17008335766ee357ed386b7b3cf19640a98b38785cb14603c1192bee5886c9b6", + "0x8563afb12e53aed71ac7103ab8602bfa8371ae095207cb0d59e8fd389b6ad1aff0641147e53cb6a7ca16c7f37c9c5e6b", + "0x8e108be614604e09974a9ed90960c28c4ea330a3d9a0cb4af6dd6f193f84ab282b243ecdf549b3131036bebc8905690c", + "0xb794d127fbedb9c5b58e31822361706ffac55ce023fbfe55716c3c48c2fd2f2c7660a67346864dfe588812d369cb50b6", + "0xb797a3442fc3b44f41baefd30346f9ac7f96e770d010d53c146ce74ce424c10fb62758b7e108b8abfdc5fafd89d745cb", + "0x993bb71e031e8096442e6205625e1bfddfe6dd6a83a81f3e2f84fafa9e5082ab4cad80a099f21eff2e81c83457c725c3", + "0x8711ab833fc03e37acf2e1e74cfd9133b101ff4144fe30260654398ae48912ab46549d552eb9d15d2ea57760d35ac62e", + "0xb21321fd2a12083863a1576c5930e1aecb330391ef83326d9d92e1f6f0d066d1394519284ddab55b2cb77417d4b0292f", + "0x877d98f731ffe3ee94b0b5b72d127630fa8a96f6ca4f913d2aa581f67732df6709493693053b3e22b0181632ac6c1e3b", + "0xae391c12e0eb8c145103c62ea64f41345973311c3bf7281fa6bf9b7faafac87bcf0998e5649b9ef81e288c369c827e07", + "0xb83a2842f36998890492ab1cd5a088d9423d192681b9a3a90ec518d4c541bce63e6c5f4df0f734f31fbfdd87785a2463", + "0xa21b6a790011396e1569ec5b2a423857b9bec16f543e63af28024e116c1ea24a3b96e8e4c75c6537c3e4611fd265e896", + "0xb4251a9c4aab3a495da7a42e684ba4860dbcf940ad1da4b6d5ec46050cbe8dab0ab9ae6b63b5879de97b905723a41576", + "0x8222f70aebfe6ac037f8543a08498f4cadb3edaac00336fc00437eb09f2cba758f6c38e887cc634b4d5b7112b6334836", + "0x86f05038e060594c46b5d94621a1d9620aa8ba59a6995baf448734e21f58e23c1ea2993d3002ad5250d6edd5ba59b34f", + "0xa7c0c749baef811ab31b973c39ceb1d94750e2bc559c90dc5eeb20d8bb6b78586a2b363c599ba2107d6be65cd435f24e", + "0x861d46a5d70b38d6c1cd72817a2813803d9f34c00320c8b62f8b9deb67f5b5687bc0b37c16d28fd017367b92e05da9ca", + "0xb3365d3dab639bffbe38e35383686a435c8c88b397b717cd4aeced2772ea1053ceb670f811f883f4e02975e5f1c4ac58", + "0xa5750285f61ab8f64cd771f6466e2c0395e01b692fd878f2ef2d5c78bdd8212a73a3b1dfa5e4c8d9e1afda7c84857d3b", + "0x835a10809ccf939bc46cf950a33b36d71be418774f51861f1cd98a016ade30f289114a88225a2c11e771b8b346cbe6ef", + "0xa4f59473a037077181a0a62f1856ec271028546ca9452b45cedfcb229d0f4d1aabfc13062b07e536cc8a0d4b113156a2", + "0x95cd14802180b224d44a73cc1ed599d6c4ca62ddcaa503513ccdc80aaa8be050cc98bd4b4f3b639549beb4587ac6caf9", + "0x973b731992a3e69996253d7f36dd7a0af1982b5ed21624b77a7965d69e9a377b010d6dabf88a8a97eec2a476259859cc", + "0xaf8a1655d6f9c78c8eb9a95051aa3baaf9c811adf0ae8c944a8d3fcba87b15f61021f3baf6996fa0aa51c81b3cb69de1", + "0x835aad5c56872d2a2d6c252507b85dd742bf9b8c211ccb6b25b52d15c07245b6d89b2a40f722aeb5083a47cca159c947", + "0xabf4e970b02bef8a102df983e22e97e2541dd3650b46e26be9ee394a3ea8b577019331857241d3d12b41d4eacd29a3ac", + "0xa13c32449dbedf158721c13db9539ae076a6ce5aeaf68491e90e6ad4e20e20d1cdcc4a89ed9fd49cb8c0dd50c17633c1", + "0x8c8f78f88b7e22dd7e9150ab1c000f10c28e696e21d85d6469a6fe315254740f32e73d81ab1f3c1cf8f544c86df506e8", + "0xb4b77f2acfe945abf81f2605f906c10b88fb4d28628487fb4feb3a09f17f28e9780445dfcee4878349d4c6387a9d17d4", + "0x8d255c235f3812c6ecc646f855fa3832be5cb4dbb9c9e544989fafdf3f69f05bfd370732eaf954012f0044aa013fc9c6", + "0xb982efd3f34b47df37c910148ac56a84e8116647bea24145a49e34e0a6c0176e3284d838dae6230cb40d0be91c078b85", + "0x983f365aa09bd85df2a6a2ad8e4318996b1e27d02090755391d4486144e40d80b1fbfe1c798d626db92f52e33aa634da", + "0x95fd1981271f3ea3a41d654cf497e6696730d9ff7369f26bc4d7d15c7adb4823dd0c42e4a005a810af12d234065e5390", + "0xa9f5219bd4b913c186ef30c02f995a08f0f6f1462614ea5f236964e02bdaa33db9d9b816c4aee5829947840a9a07ba60", + "0x9210e6ceb05c09b46fd09d036287ca33c45124ab86315e5d6911ff89054f1101faaa3e83d123b7805056d388bcec6664", + "0x8ed9cbf69c6ff3a5c62dd9fe0d7264578c0f826a29e614bc2fb4d621d90c8c9992438accdd7a614b1dca5d1bb73dc315", + "0x85cf2a8cca93e00da459e3cecd22c342d697eee13c74d5851634844fc215f60053cf84b0e03c327cb395f48d1c71a8a4", + "0x8818a18e9a2ec90a271b784400c1903089ffb0e0b40bc5abbbe12fbebe0f731f91959d98c5519ef1694543e31e2016d4", + "0x8dabc130f296fa7a82870bf9a8405aaf542b222ed9276bba9bd3c3555a0f473acb97d655ee7280baff766a827a8993f0", + "0xac7952b84b0dc60c4d858f034093b4d322c35959605a3dad2b806af9813a4680cb038c6d7f4485b4d6b2ff502aaeca25", + "0xad65cb6d57b48a2602568d2ec8010baed0eb440eec7638c5ec8f02687d764e9de5b5d42ad5582934e592b48471c22d26", + "0xa02ab8bd4c3d114ea23aebdd880952f9495912817da8c0c08eabc4e6755439899d635034413d51134c72a6320f807f1c", + "0x8319567764b8295402ec1ebef4c2930a138480b37e6d7d01c8b4c9cd1f2fc3f6e9a44ae6e380a0c469b25b06db23305f", + "0xafec53b2301dc0caa8034cd9daef78c48905e6068d692ca23d589b84a6fa9ddc2ed24a39480597e19cb3e83eec213b3f", + "0xac0b4ffdb5ae08e586a9cdb98f9fe56f4712af3a97065e89e274feacfb52b53c839565aee93c4cfaaccfe51432c4fab0", + "0x8972cbf07a738549205b1094c5987818124144bf187bc0a85287c94fdb22ce038c0f11df1aa16ec5992e91b44d1af793", + "0xb7267aa6f9e3de864179b7da30319f1d4cb2a3560f2ea980254775963f1523b44c680f917095879bebfa3dc2b603efcf", + "0x80f68f4bfc337952e29504ee5149f15093824ea7ab02507efd1317a670f6cbc3611201848560312e3e52e9d9af72eccf", + "0x8897fee93ce8fc1e1122e46b6d640bba309384dbd92e46e185e6364aa8210ebf5f9ee7e5e604b6ffba99aa80a10dd7d0", + "0xb58ea6c02f2360be60595223d692e82ee64874fda41a9f75930f7d28586f89be34b1083e03bbc1575bbfdda2d30db1ea", + "0x85a523a33d903280d70ac5938770453a58293480170c84926457ac2df45c10d5ff34322ab130ef4a38c916e70d81af53", + "0xa2cbf045e1bed38937492c1f2f93a5ba41875f1f262291914bc1fc40c60bd0740fb3fea428faf6da38b7c180fe8ac109", + "0x8c09328770ed8eb17afc6ac7ddd87bb476de18ed63cab80027234a605806895959990c47bd10d259d7f3e2ecb50074c9", + "0xb4b9e19edb4a33bde8b7289956568a5b6b6557404e0a34584b5721fe6f564821091013fbb158e2858c6d398293bb4b59", + "0x8a47377df61733a2aa5a0e945fce00267f8e950f37e109d4487d92d878fb8b573317bb382d902de515b544e9e233458d", + "0xb5804c9d97efeff5ca94f3689b8088c62422d92a1506fd1d8d3b1b30e8a866ad0d6dad4abfa051dfc4471250cac4c5d9", + "0x9084a6ee8ec22d4881e9dcc8a9eb3c2513523d8bc141942370fd191ad2601bf9537a0b1e84316f3209b3d8a54368051e", + "0x85447eea2fa26656a649f8519fa67279183044791d61cf8563d0783d46d747d96af31d0a93507bbb2242666aa87d3720", + "0x97566a84481027b60116c751aec552adfff2d9038e68d48c4db9811fb0cbfdb3f1d91fc176a0b0d988a765f8a020bce1", + "0xae87e5c1b9e86c49a23dceda4ecfd1dcf08567f1db8e5b6ec752ebd45433c11e7da4988573cdaebbb6f4135814fc059e", + "0xabee05cf9abdbc52897ac1ce9ed157f5466ed6c383d6497de28616238d60409e5e92619e528af8b62cc552bf09970dc2", + "0xae6d31cd7bf9599e5ee0828bab00ceb4856d829bba967278a73706b5f388465367aa8a6c7da24b5e5f1fdd3256ef8e63", + "0xac33e7b1ee47e1ee4af472e37ab9e9175260e506a4e5ce449788075da1b53c44cb035f3792d1eea2aa24b1f688cc6ed3", + "0x80f65b205666b0e089bb62152251c48c380a831e5f277f11f3ef4f0d52533f0851c1b612267042802f019ec900dc0e8f", + "0x858520ad7aa1c9fed738e3b583c84168f2927837ad0e1d326afe9935c26e9b473d7f8c382e82ef1fe37d2b39bb40a1ee", + "0xb842dd4af8befe00a97c2d0f0c33c93974761e2cb9e5ab8331b25170318ddd5e4bdbc02d8f90cbfdd5f348f4f371c1f7", + "0x8bf2cb79bc783cb57088aae7363320cbeaabd078ffdec9d41bc74ff49e0043d0dad0086a30e5112b689fd2f5a606365d", + "0x982eb03bbe563e8850847cd37e6a3306d298ab08c4d63ab6334e6b8c1fa13fce80cf2693b09714c7621d74261a0ff306", + "0xb143edb113dec9f1e5105d4a93fbe502b859e587640d3db2f628c09a17060e6aec9e900e2c8c411cda99bc301ff96625", + "0xaf472d9befa750dcebc5428fe1a024f18ec1c07bca0f95643ce6b5f4189892a910285afb03fd7ed7068fbe614e80d33c", + "0xa97e3bc57ede73ecd1bbf02de8f51b4e7c1a067da68a3cd719f4ba26a0156cbf1cef2169fd35a18c5a4cced50d475998", + "0xa862253c937cf3d75d7183e5f5be6a4385d526aeda5171c1c60a8381fea79f88f5f52a4fab244ecc70765d5765e6dfd5", + "0x90cb776f8e5a108f1719df4a355bebb04bf023349356382cae55991b31720f0fd03206b895fa10c56c98f52453be8778", + "0xa7614e8d0769dccd520ea4b46f7646e12489951efaef5176bc889e9eb65f6e31758df136b5bf1e9107e68472fa9b46ec", + "0xac3a9b80a3254c42e5ed3a090a0dd7aee2352f480de96ad187027a3bb6c791eddfc3074b6ffd74eea825188f107cda4d", + "0x82a01d0168238ef04180d4b6e0a0e39024c02c2d75b065017c2928039e154d093e1af4503f4d1f3d8a948917abb5d09f", + "0x8fab000a2b0eef851a483aec8d2dd85fe60504794411a2f73ed82e116960547ac58766cb73df71aea71079302630258d", + "0x872451a35c6db61c63e9b8bb9f16b217f985c20be4451c14282c814adb29d7fb13f201367c664435c7f1d4d9375d7a58", + "0x887d9ff54cc96b35d562df4a537ff972d7c4b3fd91ab06354969a4cfede0b9fc68bbffb61d0dbf1a58948dc701e54f5a", + "0x8cb5c2a6bd956875d88f41ae24574434f1308514d44057b55c9c70f13a3366ed054150eed0955a38fda3f757be73d55f", + "0x89ad0163cad93e24129d63f8e38422b7674632a8d0a9016ee8636184cab177659a676c4ee7efba3abe1a68807c656d60", + "0xb9ec01c7cab6d00359b5a0b4a1573467d09476e05ca51a9227cd16b589a9943d161eef62dcc73f0de2ec504d81f4d252", + "0x8031d17635d39dfe9705c485d2c94830b6fc9bc67b91300d9d2591b51e36a782e77ab5904662effa9382d9cca201f525", + "0x8be5a5f6bc8d680e5092d6f9a6585acbaaaa2ddc671da560dcf5cfa4472f4f184b9597b5b539438accd40dda885687cc", + "0xb1fc0f052fae038a2e3de3b3a96b0a1024b009de8457b8b3adb2d315ae68a89af905720108a30038e5ab8d0d97087785", + "0x8b8bdc77bd3a6bc7ca5492b6f8c614852c39a70d6c8a74916eaca0aeb4533b11898b8820a4c2620a97bf35e275480029", + "0xaf35f4dc538d4ad5cdf710caa38fd1eb496c3fa890a047b6a659619c5ad3054158371d1e88e0894428282eed9f47f76b", + "0x8166454a7089cc07758ad78724654f4e7a1a13e305bbf88ddb86f1a4b2904c4fc8ab872d7da364cdd6a6c0365239e2ad", + "0xab287c7d3addce74ce40491871c768abe01daaa0833481276ff2e56926b38a7c6d2681ffe837d2cc323045ad1a4414f9", + "0xb90317f4505793094d89365beb35537f55a6b5618904236258dd04ca61f21476837624a2f45fef8168acf732cab65579", + "0x98ae5ea27448e236b6657ab5ef7b1cccb5372f92ab25f5fa651fbac97d08353a1dae1b280b1cd42b17d2c6a70a63ab9d", + "0xadcf54e752d32cbaa6cb98fbca48d8cd087b1db1d131d465705a0d8042c8393c8f4d26b59006eb50129b21e6240f0c06", + "0xb591a3e4db18a7345fa935a8dd7994bbac5cc270b8ebd84c8304c44484c7a74afb45471fdbe4ab22156a30fae1149b40", + "0x806b53ac049a42f1dcc1d6335505371da0bf27c614f441b03bbf2e356be7b2fb4eed7117eabcce9e427a542eaa2bf7d8", + "0x800482e7a772d49210b81c4a907f5ce97f270b959e745621ee293cf8c71e8989363d61f66a98f2d16914439544ca84c7", + "0x99de9eafdad3617445312341644f2bb888680ff01ce95ca9276b1d2e5ef83fa02dab5e948ebf66c17df0752f1bd37b70", + "0x961ee30810aa4c93ae157fbe9009b8e443c082192bd36a73a6764ff9b2ad8b0948fe9a73344556e01399dd77badb4257", + "0xae0a361067c52efbe56c8adf982c00432cd478929459fc7f74052c8ee9531cd031fe1335418fde53f7c2ef34254eb7ac", + "0xa3503d16b6b27eb20c1b177bcf90d13706169220523a6271b85b2ce35a9a2b9c5bed088540031c0a4ebfdae3a4c6ab04", + "0x909420122c3e723289ca4e7b81c2df5aff312972a2203f4c45821b176e7c862bf9cac7f7df3adf1d59278f02694d06e7", + "0x989f42380ae904b982f85d0c6186c1aef5d6bcba29bcfbb658e811b587eb2749c65c6e4a8cc6409c229a107499a4f5d7", + "0x8037a6337195c8e26a27ea4ef218c6e7d79a9720aaab43932d343192abc2320fe72955f5e431c109093bda074103330a", + "0xb312e168663842099b88445e940249cc508f080ab0c94331f672e7760258dbd86be5267e4cf25ea25facb80bff82a7e9", + "0xaaa3ff8639496864fcdbfdda1ac97edc4f08e3c9288b768f6c8073038c9fbbf7e1c4bea169b4d45c31935cdf0680d45e", + "0x97dbd3df37f0b481a311dfc5f40e59227720f367912200d71908ef6650f32cc985cb05b981e3eea38958f7e48d10a15d", + "0xa89d49d1e267bb452d6cb621b9a90826fe55e9b489c0427b94442d02a16f390eed758e209991687f73f6b5a032321f42", + "0x9530dea4e0e19d6496f536f2e75cf7d814d65fde567055eb20db48fd8d20d501cd2a22fb506db566b94c9ee10f413d43", + "0x81a7009b9e67f1965fa7da6a57591c307de91bf0cd35ab4348dc4a98a4961e096d004d7e7ad318000011dc4342c1b809", + "0x83440a9402b766045d7aca61a58bba2aa29cac1cf718199e472ba086f5d48093d9dda4d135292ba51d049a23964eceae", + "0xa06c9ce5e802df14f6b064a3d1a0735d429b452f0e2e276042800b0a4f16df988fd94cf3945921d5dd3802ab2636f867", + "0xb1359e358b89936dee9e678a187aad3e9ab14ac40e96a0a68f70ee2583cdcf467ae03bef4215e92893f4e12f902adec8", + "0x835304f8619188b4d14674d803103d5a3fa594d48e96d9699e653115dd05fdc2dda6ba3641cf7ad53994d448da155f02", + "0x8327cba5a9ff0d3f5cd0ae55e77167448926d5fcf76550c0ad978092a14122723090c51c415e88e42a2b62eb07cc3981", + "0xb373dcdaea85f85ce9978b1426a7ef4945f65f2d3467a9f1cc551a99766aac95df4a09e2251d3f89ca8c9d1a7cfd7b0e", + "0xab1422dc41af2a227b973a6fd124dfcb2367e2a11a21faa1d381d404f51b7257e5bc82e9cf20cd7fe37d7ae761a2ab37", + "0xa93774a03519d2f20fdf2ef46547b0a5b77c137d6a3434b48d56a2cbef9e77120d1b85d0092cf8842909213826699477", + "0x8eb967a495a38130ea28711580b7e61bcd1d051cd9e4f2dbf62f1380bd86e0d60e978d72f6f31e909eb97b3b9a2b867c", + "0xae8213378da1287ba1fe4242e1acaec19b877b6fe872400013c6eac1084b8d03156792fa3020201725b08228a1e80f49", + "0xb143daf6893d674d607772b3b02d8ac48f294237e2f2c87963c0d4e26d9227d94a2a13512457c3d5883544bbc259f0ef", + "0xb343bd2aca8973888e42542218924e2dda2e938fd1150d06878af76f777546213912b7c7a34a0f94186817d80ffa185c", + "0xb188ebc6a8c3007001aa347ae72cc0b15d09bc6c19a80e386ee4b334734ec0cc2fe8b493c2422f38d1e6d133cc3db6fe", + "0xb795f6a8b9b826aaeee18ccd6baf6c5adeeec85f95eb5b6d19450085ec7217e95a2d9e221d77f583b297d0872073ba0e", + "0xb1c7dbd998ad32ae57bfa95deafa147024afd57389e98992c36b6e52df915d3d5a39db585141ec2423173e85d212fed8", + "0x812bcdeb9fe5f12d0e1df9964798056e1f1c3de3b17b6bd2919b6356c4b86d8e763c01933efbe0224c86a96d5198a4be", + "0xb19ebeda61c23d255cbf472ef0b8a441f4c55b70f0d8ed47078c248b1d3c7c62e076b43b95c00a958ec8b16d5a7cb0d7", + "0xb02adc9aaa20e0368a989c2af14ff48b67233d28ebee44ff3418bb0473592e6b681af1cc45450bd4b175df9051df63d9", + "0x8d87f0714acee522eb58cec00360e762adc411901dba46adc9227124fa70ee679f9a47e91a6306d6030dd4eb8de2f3c1", + "0x8be54cec21e74bcc71de29dc621444263737db15f16d0bb13670f64e42f818154e04b484593d19ef95f2ee17e4b3fe21", + "0xab8e20546c1db38d31493b5d5f535758afb17e459645c1b70813b1cf7d242fd5d1f4354a7c929e8f7259f6a25302e351", + "0x89f035a1ed8a1e302ac893349ba8ddf967580fcb6e73d44af09e3929cde445e97ff60c87dafe489e2c0ab9c9986cfa00", + "0x8b2b0851a795c19191a692af55f7e72ad2474efdc5401bc3733cfdd910e34c918aaebe69d5ea951bdddf3c01cabbfc67", + "0xa4edb52c2b51495ccd1ee6450fc14b7b3ede8b3d106808929d02fb31475bacb403e112ba9c818d2857651e508b3a7dd1", + "0x9569341fded45d19f00bcf3cbf3f20eb2b4d82ef92aba3c8abd95866398438a2387437e580d8b646f17cf6fde8c5af23", + "0xaa4b671c6d20f72f2f18a939a6ff21cc37e0084b44b4a717f1be859a80b39fb1be026b3205adec2a66a608ec2bcd578f", + "0x94902e980de23c4de394ad8aec91b46f888d18f045753541492bfbb92c59d3daa8de37ae755a6853744af8472ba7b72b", + "0xaf651ef1b2a0d30a7884557edfad95b6b5d445a7561caebdc46a485aedd25932c62c0798465c340a76f6feaa196dd712", + "0xb7b669b8e5a763452128846dd46b530dca4893ace5cc5881c7ddcd3d45969d7e73fbebdb0e78aa81686e5f7b22ec5759", + "0x82507fd4ebe9fa656a7f2e084d64a1fa6777a2b0bc106d686e2d9d2edafc58997e58cb6bfd0453b2bf415704aa82ae62", + "0xb40bce2b42b88678400ecd52955bbdadd15f8b9e1b3751a1a3375dc0efb5ca3ee258cf201e1140b3c09ad41217d1d49e", + "0xb0210d0cbb3fbf3b8cdb39e862f036b0ff941cd838e7aaf3a8354e24246e64778d22f3de34572e6b2a580614fb6425be", + "0x876693cba4301b251523c7d034108831df3ce133d8be5a514e7a2ca494c268ca0556fa2ad8310a1d92a16b55bcd99ea9", + "0x8660281406d22a4950f5ef050bf71dd3090edb16eff27fa29ef600cdea628315e2054211ed2cc6eaf8f2a1771ef689fd", + "0xa610e7e41e41ab66955b809ba4ade0330b8e9057d8efc9144753caed81995edeb1a42a53f93ce93540feca1fae708dac", + "0xa49e2c176a350251daef1218efaccc07a1e06203386ede59c136699d25ca5cb2ac1b800c25b28dd05678f14e78e51891", + "0x83e0915aa2b09359604566080d411874af8c993beba97d4547782fdbe1a68e59324b800ff1f07b8db30c71adcbd102a8", + "0xa19e84e3541fb6498e9bb8a099c495cbfcad113330e0262a7e4c6544495bb8a754b2208d0c2d895c93463558013a5a32", + "0x87f2bd49859a364912023aca7b19a592c60214b8d6239e2be887ae80b69ebdeb59742bdebcfa73a586ab23b2c945586c", + "0xb8e8fdddae934a14b57bc274b8dcd0d45ebb95ddbaabef4454e0f6ce7d3a5a61c86181929546b3d60c447a15134d08e1", + "0x87e0c31dcb736ea4604727e92dc1d9a3cf00adcff79df3546e02108355260f3dd171531c3c0f57be78d8b28058fcc8c0", + "0x9617d74e8f808a4165a8ac2e30878c349e1c3d40972006f0787b31ea62d248c2d9f3fc3da83181c6e57e95feedfd0e8c", + "0x8949e2cee582a2f8db86e89785a6e46bc1565c2d8627d5b6bf43ba71ffadfab7e3c5710f88dcb5fb2fc6edf6f4fae216", + "0xad3fa7b0edceb83118972a2935a09f409d09a8db3869f30be3a76f67aa9fb379cabb3a3aff805ba023a331cad7d7eb64", + "0x8c95718a4112512c4efbd496be38bf3ca6cdcaad8a0d128f32a3f9aae57f3a57bdf295a3b372a8c549fda8f4707cffed", + "0x88f3261d1e28a58b2dee3fcc799777ad1c0eb68b3560f9b4410d134672d9533532a91ea7be28a041784872632d3c9d80", + "0xb47472a41d72dd2e8b72f5c4f8ad626737dde3717f63d6bc776639ab299e564cbad0a2ad5452a07f02ff49a359c437e5", + "0x9896d21dc2e8aad87b76d6df1654f10cd7bceed4884159d50a818bea391f8e473e01e14684814c7780235f28e69dca6e", + "0x82d47c332bbd31bbe83b5eb44a23da76d4a7a06c45d7f80f395035822bc27f62f59281d5174e6f8e77cc9b5c3193d6f0", + "0x95c74cd46206e7f70c9766117c34c0ec45c2b0f927a15ea167901a160e1530d8522943c29b61e03568aa0f9c55926c53", + "0xa89d7757825ae73a6e81829ff788ea7b3d7409857b378ebccd7df73fdbe62c8d9073741cf038314971b39af6c29c9030", + "0x8c1cd212d0b010905d560688cfc036ae6535bc334fa8b812519d810b7e7dcf1bb7c5f43deaa40f097158358987324a7f", + "0xb86993c383c015ed8d847c6b795164114dd3e9efd25143f509da318bfba89389ea72a420699e339423afd68b6512fafb", + "0x8d06bd379c6d87c6ed841d8c6e9d2d0de21653a073725ff74be1934301cc3a79b81ef6dd0aad4e7a9dc6eac9b73019bc", + "0x81af4d2d87219985b9b1202d724fe39ef988f14fef07dfe3c3b11714e90ffba2a97250838e8535eb63f107abfe645e96", + "0x8c5e0af6330a8becb787e4b502f34f528ef5756e298a77dc0c7467433454347f3a2e0bd2641fbc2a45b95e231c6e1c02", + "0x8e2a8f0f04562820dc8e7da681d5cad9fe2e85dd11c785fb6fba6786c57a857e0b3bd838fb849b0376c34ce1665e4837", + "0xa39be8269449bfdfc61b1f62077033649f18dae9bef7c6163b9314ca8923691fb832f42776f0160b9e8abd4d143aa4e1", + "0x8c154e665706355e1cc98e0a4cabf294ab019545ba9c4c399d666e6ec5c869ca9e1faf8fb06cd9c0a5c2f51a7d51b70a", + "0xa046a7d4de879d3ebd4284f08f24398e9e3bf006cd4e25b5c67273ade248689c69affff92ae810c07941e4904296a563", + "0xafd94c1cb48758e5917804df03fb38a6da0e48cd9b6262413ea13b26973f9e266690a1b7d9d24bbaf7e82718e0e594b0", + "0x859e21080310c8d6a38e12e2ac9f90a156578cdeb4bb2e324700e97d9a5511cd6045dc39d1d0de3f94aeed043a24119d", + "0xa219fb0303c379d0ab50893264919f598e753aac9065e1f23ef2949abc992577ab43c636a1d2c089203ec9ddb941e27d", + "0xb0fdb639d449588a2ca730afcba59334e7c387342d56defdfb7ef79c493f7fd0e5277eff18e7203e756c7bdda5803047", + "0x87f9c3b7ed01f54368aca6dbcf2f6e06bff96e183c4b2c65f8baa23b377988863a0a125d5cdd41a072da8462ced4c070", + "0x99ef7a5d5ac2f1c567160e1f8c95f2f38d41881850f30c461a205f7b1b9fb181277311333839b13fb3ae203447e17727", + "0xaeaca9b1c2afd24e443326cc68de67b4d9cedb22ad7b501a799d30d39c85bb2ea910d4672673e39e154d699e12d9b3dc", + "0xa11675a1721a4ba24dd3d0e4c3c33a6edf4cd1b9f6b471070b4386c61f77452266eae6e3f566a40cfc885eada9a29f23", + "0xb228334445e37b9b49cb4f2cc56b454575e92173ddb01370a553bba665adadd52df353ad74470d512561c2c3473c7bb9", + "0xa18177087c996572d76f81178d18ed1ceebc8362a396348ce289f1d8bd708b9e99539be6fccd4acb1112381cfc5749b4", + "0x8e7b8bf460f0d3c99abb19803b9e43422e91507a1c0c22b29ee8b2c52d1a384da4b87c292e28eff040db5be7b1f8641f", + "0xb03d038d813e29688b6e6f444eb56fec3abba64c3d6f890a6bcf2e916507091cdb2b9d2c7484617be6b26552ed1c56cb", + "0xa1c88ccd30e934adfc5494b72655f8afe1865a84196abfb376968f22ddc07761210b6a9fb7638f1413d1b4073d430290", + "0x961b714faebf172ad2dbc11902461e286e4f24a99a939152a53406117767682a571057044decbeb3d3feef81f4488497", + "0xa03dc4059b46effdd786a0a03cc17cfee8585683faa35bb07936ded3fa3f3a097f518c0b8e2db92fd700149db1937789", + "0xadf60180c99ca574191cbcc23e8d025b2f931f98ca7dfcebfc380226239b6329347100fcb8b0fcb12db108c6ad101c07", + "0x805d4f5ef24d46911cbf942f62cb84b0346e5e712284f82b0db223db26d51aabf43204755eb19519b00e665c7719fcaa", + "0x8dea7243e9c139662a7fe3526c6c601eee72fd8847c54c8e1f2ad93ef7f9e1826b170afe58817dac212427164a88e87f", + "0xa2ba42356606d651b077983de1ad643650997bb2babb188c9a3b27245bb65d2036e46667c37d4ce02cb1be5ae8547abe", + "0xaf2ae50b392bdc013db2d12ce2544883472d72424fc767d3f5cb0ca2d973fc7d1f425880101e61970e1a988d0670c81b", + "0x98e6bec0568d3939b31d00eb1040e9b8b2a35db46ddf4369bdaee41bbb63cc84423d29ee510a170fb5b0e2df434ba589", + "0x822ff3cd12fbef4f508f3ca813c04a2e0b9b799c99848e5ad3563265979e753ee61a48f6adc2984a850f1b46c1a43d35", + "0x891e8b8b92a394f36653d55725ef514bd2e2a46840a0a2975c76c2a935577f85289026aaa74384da0afe26775cbddfb9", + "0xb2a3131a5d2fe7c8967047aa66e4524babae941d90552171cc109527f345f42aa0df06dcbb2fa01b33d0043917bbed69", + "0x80c869469900431f3eeefafdbe07b8afd8cee7739e659e6d0109b397cacff85a88247698f87dc4e2fe39a592f250ac64", + "0x9091594f488b38f9d2bb5df49fd8b4f8829d9c2f11a197dd1431ed5abbc5c954bbde3387088f9ee3a5a834beb7619bce", + "0xb472e241e6956146cca57b97a8a204668d050423b4e76f857bad5b47f43b203a04c8391ba9d9c3e95093c071f9d376a1", + "0xb7dd2de0284844392f7dfb56fe7ca3ede41e27519753ffc579a0a8d2d65ceb8108d06b6b0d4c3c1a2588951297bd1a1e", + "0x902116ce70d0a079ac190321c1f48701318c05f8e69ee09694754885d33a835a849cafe56f499a2f49f6cda413ddf9a7", + "0xb18105cc736787fafaf7c3c11c448bce9466e683159dff52723b7951dff429565e466e4841d982e3aaa9ee2066838666", + "0x97ab9911f3f659691762d568ae0b7faa1047b0aed1009c319fa79d15d0db8db9f808fc385dc9a68fa388c10224985379", + "0xb2a2cba65f5b927e64d2904ba412e2bac1cf18c9c3eda9c72fb70262497ecf505b640827e2afebecf10eebbcf48ccd3e", + "0xb36a3fd677baa0d3ef0dac4f1548ff50a1730286b8c99d276a0a45d576e17b39b3cbadd2fe55e003796d370d4be43ce3", + "0xa5dfec96ca3c272566e89dc453a458909247e3895d3e44831528130bc47cc9d0a0dac78dd3cad680a4351d399d241967", + "0x8029382113909af6340959c3e61db27392531d62d90f92370a432aec3eb1e4c36ae1d4ef2ba8ec6edb4d7320c7a453f6", + "0x971d85121ea108e6769d54f9c51299b0381ece8b51d46d49c89f65bedc123bab4d5a8bc14d6f67f4f680077529cbae4c", + "0x98ff6afc01d0bec80a278f25912e1b1ebff80117adae72e31d5b9fa4d9624db4ba2065b444df49b489b0607c45e26c4c", + "0x8fa29be10fb3ab30ce25920fec0187e6e91e458947009dabb869aade7136c8ba23602682b71e390c251f3743164cbdaa", + "0xb3345c89eb1653418fe3940cf3e56a9a9c66526389b98f45ca02dd62bfb37baa69a4baaa7132d7320695f8ea6ad1fd94", + "0xb72c7f5541c9ac6b60a7ec9f5415e7fb14da03f7164ea529952a29399f3a071576608dbbcc0d45994f21f92ddbeb1e19", + "0xaa3450bb155a5f9043d0ef95f546a2e6ade167280bfb75c9f09c6f9cdb1fffb7ce8181436161a538433afa3681c7a141", + "0x92a18fecaded7854b349f441e7102b638ababa75b1b0281dd0bded6541abe7aa37d96693595be0b01fe0a2e2133d50f9", + "0x980756ddf9d2253cfe6c94960b516c94889d09e612810935150892627d2ecee9a2517e04968eea295d0106850c04ca44", + "0xae68c6ccc454318cdd92f32b11d89116a3b8350207a36d22a0f626718cad671d960090e054c0c77ac3162ae180ecfd4b", + "0x99f31f66eaaa551749ad91d48a0d4e3ff4d82ef0e8b28f3184c54e852422ba1bdafd53b1e753f3a070f3b55f3c23b6a2", + "0xa44eaeaa6589206069e9c0a45ff9fc51c68da38d4edff1d15529b7932e6f403d12b9387019c44a1488a5d5f27782a51f", + "0xb80b5d54d4b344840e45b79e621bd77a3f83fb4ce6d8796b7d6915107b3f3c34d2e7d95bdafd120f285669e5acf2437a", + "0xb36c069ec085a612b5908314d6b84c00a83031780261d1c77a0384c406867c9847d5b0845deddfa512cc04a8df2046fb", + "0xb09dbe501583220f640d201acea7ee3e39bf9eda8b91aa07b5c50b7641d86d71acb619b38d27835ce97c3759787f08e9", + "0x87403d46a2bf63170fff0b857acacf42ee801afe9ccba8e5b4aea967b68eac73a499a65ca46906c2eb4c8f27bc739faa", + "0x82b93669f42a0a2aa5e250ffe6097269da06a9c02fcd1801abbad415a7729a64f830754bafc702e64600ba47671c2208", + "0x8e3a3029be7edb8dd3ab1f8216664c8dc50d395f603736061d802cef77627db7b859ef287ed850382c13b4d22d6a2d80", + "0x968e9ec7194ff424409d182ce0259acd950c384c163c04463bc8700a40b79beba6146d22b7fa7016875a249b7b31c602", + "0x8b42c984bbe4996e0c20862059167c6bdc5164b1ffcd928f29512664459212d263e89f0f0e30eed4e672ffa5ed0b01b5", + "0x96bac54062110dada905363211133f1f15dc7e4fd80a4c6e4a83bc9a0bcbbaba11cd2c7a13debcf0985e1a954c1da66b", + "0xa16dc8a653d67a7cd7ae90b2fffac0bf1ca587005430fe5ba9403edd70ca33e38ba5661d2ed6e9d2864400d997626a62", + "0xa68ab11a570a27853c8d67e491591dcba746bfbee08a2e75ae0790399130d027ed387f41ef1d7de8df38b472df309161", + "0x92532b74886874447c0300d07eda9bbe4b41ed25349a3da2e072a93fe32c89d280f740d8ff70d5816793d7f2b97373cc", + "0x88e35711b471e89218fd5f4d0eadea8a29405af1cd81974427bc4a5fb26ed60798daaf94f726c96e779b403a2cd82820", + "0xb5c72aa4147c19f8c4f3a0a62d32315b0f4606e0a7025edc5445571eaf4daff64f4b7a585464821574dd50dbe1b49d08", + "0x9305d9b4095258e79744338683fd93f9e657367b3ab32d78080e51d54eec331edbc224fad5093ebf8ee4bd4286757eb8", + "0xb2a17abb3f6a05bcb14dc7b98321fa8b46d299626c73d7c6eb12140bf4c3f8e1795250870947af817834f033c88a59d6", + "0xb3477004837dbd8ba594e4296f960fc91ab3f13551458445e6c232eb04b326da803c4d93e2e8dcd268b4413305ff84da", + "0x924b4b2ebaafdcfdfedb2829a8bf46cd32e1407d8d725a5bd28bdc821f1bafb3614f030ea4352c671076a63494275a3f", + "0x8b81b9ef6125c82a9bece6fdcb9888a767ac16e70527753428cc87c56a1236e437da8be4f7ecfe57b9296dc3ae7ba807", + "0x906e19ec8b8edd58bdf9ae05610a86e4ea2282b1bbc1e8b00b7021d093194e0837d74cf27ac9916bdb8ec308b00da3da", + "0xb41c5185869071760ac786078a57a2ab4e2af60a890037ac0c0c28d6826f15c2cf028fddd42a9b6de632c3d550bfbc14", + "0xa646e5dec1b713ae9dfdf7bdc6cd474d5731a320403c7dfcfd666ffc9ae0cff4b5a79530e8df3f4aa9cb80568cb138e9", + "0xb0efad22827e562bd3c3e925acbd0d9425d19057868608d78c2209a531cccd0f2c43dc5673acf9822247428ffa2bb821", + "0xa94c19468d14b6f99002fc52ac06bbe59e5c472e4a0cdb225144a62f8870b3f10593749df7a2de0bd3c9476ce682e148", + "0x803864a91162f0273d49271dafaab632d93d494d1af935aefa522768af058fce52165018512e8d6774976d52bd797e22", + "0xa08711c2f7d45c68fb340ac23597332e1bcaec9198f72967b9921204b9d48a7843561ff318f87908c05a44fc35e3cc9d", + "0x91c3cad94a11a3197ae4f9461faab91a669e0dddb0371d3cab3ed9aeb1267badc797d8375181130e461eadd05099b2a2", + "0x81bdaaf48aae4f7b480fc13f1e7f4dd3023a41439ba231760409ce9292c11128ab2b0bdbbf28b98af4f97b3551f363af", + "0x8d60f9df9fd303f625af90e8272c4ecb95bb94e6efc5da17b8ab663ee3b3f673e9f6420d890ccc94acf4d2cae7a860d8", + "0xa7b75901520c06e9495ab983f70b61483504c7ff2a0980c51115d11e0744683ce022d76e3e09f4e99e698cbd21432a0d", + "0x82956072df0586562fda7e7738226f694e1c73518dd86e0799d2e820d7f79233667192c9236dcb27637e4c65ef19d493", + "0xa586beb9b6ffd06ad200957490803a7cd8c9bf76e782734e0f55e04a3dc38949de75dc607822ec405736c576cf83bca3", + "0xa179a30d00def9b34a7e85607a447eea0401e32ab5abeee1a281f2acd1cf6ec81a178020666f641d9492b1bdf66f05a3", + "0x83e129705c538787ed8e0fdc1275e6466a3f4ee21a1e6abedd239393b1df72244723b92f9d9d9339a0cab6ebf28f5a16", + "0x811bd8d1e3722b64cd2f5b431167e7f91456e8bba2cc669d3fbbce7d553e29c3c19f629fcedd2498bc26d33a24891d17", + "0xa243c030c858f1f60cccd26b45b024698cc6d9d9e6198c1ed4964a235d9f8d0baf9cde10c8e63dfaa47f8e74e51a6e85", + "0xab839eb82e23ca52663281f863b55b0a3d6d4425c33ffb4eeb1d7979488ab068bf99e2a60e82cea4dc42c56c26cbfebe", + "0x8b896f9bb21d49343e67aec6ad175b58c0c81a3ca73d44d113ae4354a0065d98eb1a5cafedaf232a2bb9cdc62152f309", + "0xaf6230340cc0b66f5bf845540ed4fc3e7d6077f361d60762e488d57834c3e7eb7eacc1b0ed73a7d134f174a01410e50c", + "0x88975e1b1af678d1b5179f72300a30900736af580dd748fd9461ef7afccc91ccd9bed33f9da55c8711a7635b800e831f", + "0xa97486bb9047391661718a54b8dd5a5e363964e495eae6c692730264478c927cf3e66dd3602413189a3699fbeae26e15", + "0xa5973c161ab38732885d1d2785fd74bf156ba34881980cba27fe239caef06b24a533ffe6dbbbeca5e6566682cc00300a", + "0xa24776e9a840afda0003fa73b415d5bd6ecd9b5c2cc842b643ee51b8c6087f4eead4d0bfbd987eb174c489a7b952ff2a", + "0xa8a6ee06e3af053b705a12b59777267c546f33ba8a0f49493af8e6df4e15cf8dd2d4fb4daf7e84c6b5d3a7363118ff03", + "0xa28e59ce6ad02c2ce725067c0123117e12ac5a52c8f5af13eec75f4a9efc4f696777db18a374fa33bcae82e0734ebd16", + "0x86dfc3b78e841c708aff677baa8ee654c808e5d257158715097c1025d46ece94993efe12c9d188252ad98a1e0e331fec", + "0xa88d0275510f242eab11fdb0410ff6e1b9d7a3cbd3658333539815f1b450a84816e6613d15aa8a8eb15d87cdad4b27a2", + "0x8440acea2931118a5b481268ff9f180ee4ede85d14a52c026adc882410825b8275caa44aff0b50c2b88d39f21b1a0696", + "0xa7c3182eab25bd6785bacf12079d0afb0a9b165d6ed327814e2177148539f249eb9b5b2554538f54f3c882d37c0a8abe", + "0x85291fbe10538d7da38efdd55a7acebf03b1848428a2f664c3ce55367aece60039f4f320b1771c9c89a35941797f717c", + "0xa2c6414eeb1234728ab0de94aa98fc06433a58efa646ca3fcbd97dbfb8d98ae59f7ce6d528f669c8149e1e13266f69c9", + "0x840c8462785591ee93aee2538d9f1ec44ba2ca61a569ab51d335ac873f5d48099ae8d7a7efa0725d9ff8f9475bfa4f56", + "0xa7065a9d02fb3673acf7702a488fbc01aa69580964932f6f40b6c2d1c386b19e50b0e104fcac24ea26c4e723611d0238", + "0xb72db6d141267438279e032c95e6106c2ccb3164b842ba857a2018f3a35f4b040da92680881eb17cd61d0920d5b8f006", + "0xa8005d6c5960e090374747307ef0be2871a7a43fa4e76a16c35d2baab808e9777b496e9f57a4218b23390887c33a0b55", + "0x8e152cea1e00a451ca47c20a1e8875873419700af15a5f38ee2268d3fbc974d4bd5f4be38008fa6f404dbdedd6e6e710", + "0xa3391aed1fcd68761f06a7d1008ec62a09b1cb3d0203cd04e300a0c91adfed1812d8bc1e4a3fd7976dc0aae0e99f52f1", + "0x967eb57bf2aa503ee0c6e67438098149eac305089c155f1762cf5e84e31f0fbf27c34a9af05621e34645c1ec96afaec8", + "0x88af97ddc4937a95ec0dcd25e4173127260f91c8db2f6eac84afb789b363705fb3196235af631c70cafd09411d233589", + "0xa32df75b3f2c921b8767638fd289bcfc61e08597170186637a7128ffedd52c798c434485ac2c7de07014f9e895c2c3d8", + "0xb0a783832153650aa0d766a3a73ec208b6ce5caeb40b87177ffc035ab03c7705ecdd1090b6456a29f5fb7e90e2fa8930", + "0xb59c8e803b4c3486777d15fc2311b97f9ded1602fa570c7b0200bada36a49ee9ef4d4c1474265af8e1c38a93eb66b18b", + "0x982f2c85f83e852022998ff91bafbb6ff093ef22cf9d5063e083a48b29175ccbd51b9c6557151409e439096300981a6c", + "0x939e3b5989fefebb9d272a954659a4eb125b98c9da6953f5e628d26266bd0525ec38304b8d56f08d65abc4d6da4a8dbb", + "0x8898212fe05bc8de7d18503cb84a1c1337cc2c09d1eeef2b475aa79185b7322bf1f8e065f1bf871c0c927dd19faf1f6d", + "0x94b0393a41cd00f724aee2d4bc72103d626a5aecb4b5486dd1ef8ac27528398edf56df9db5c3d238d8579af368afeb09", + "0x96ac564450d998e7445dd2ea8e3fc7974d575508fa19e1c60c308d83b645864c029f2f6b7396d4ff4c1b24e92e3bac37", + "0x8adf6638e18aff3eb3b47617da696eb6c4bdfbecbbc3c45d3d0ab0b12cbad00e462fdfbe0c35780d21aa973fc150285e", + "0xb53f94612f818571b5565bbb295e74bada9b5f9794b3b91125915e44d6ddcc4da25510eab718e251a09c99534d6042d9", + "0x8b96462508d77ee083c376cd90807aebad8de96bca43983c84a4a6f196d5faf6619a2351f43bfeec101864c3bf255519", + "0xaeadf34657083fc71df33bd44af73bf5281c9ca6d906b9c745536e1819ea90b56107c55e2178ebad08f3ba75b3f81c86", + "0x9784ba29b2f0057b5af1d3ab2796d439b8753f1f749c73e791037461bdfc3f7097394283105b8ab01788ea5255a96710", + "0x8756241bda159d4a33bf74faba0d4594d963c370fb6a18431f279b4a865b070b0547a6d1613cf45b8cfb5f9236bbf831", + "0xb03ebfd6b71421dfd49a30460f9f57063eebfe31b9ceaa2a05c37c61522b35bdc09d7db3ad75c76c253c00ba282d3cd2", + "0xb34e7e6341fa9d854b2d3153bdda0c4ae2b2f442ab7af6f99a0975d45725aa48e36ae5f7011edd249862e91f499687d4", + "0xb462ee09dc3963a14354244313e3444de5cc37ea5ccfbf14cd9aca8027b59c4cb2a949bc30474497cab8123e768460e6", + "0xaea753290e51e2f6a21a9a0ee67d3a2713f95c2a5c17fe41116c87d3aa77b1683761264d704df1ac34f8b873bc88ef7b", + "0x98430592afd414394f98ddfff9f280fcb1c322dbe3510f45e1e9c4bb8ee306b3e0cf0282c0ee73ebb8ba087d4d9e0858", + "0xb95d3b5aaf54ffca11f4be8d57f76e14afdb20afc859dc7c7471e0b42031e8f3d461b726ecb979bdb2f353498dfe95ea", + "0x984d17f9b11a683132e0b5a9ee5945e3ff7054c2d5c716be73b29078db1d36f54c6e652fd2f52a19da313112e97ade07", + "0xab232f756b3fff3262be418a1af61a7e0c95ceebbc775389622a8e10610508cd6784ab7960441917a83cc191c58829ea", + "0xa28f41678d6e60de76b0e36ab10e4516e53e02e9c77d2b5af3cfeee3ce94cfa30c5797bd1daab20c98e1cad83ad0f633", + "0xb55395fca84dd3ccc05dd480cb9b430bf8631ff06e24cb51d54519703d667268c2f8afcde4ba4ed16bece8cc7bc8c6e0", + "0x8a8a5392a0e2ea3c7a8c51328fab11156004e84a9c63483b64e8f8ebf18a58b6ffa8fe8b9d95af0a2f655f601d096396", + "0xab480000fe194d23f08a7a9ec1c392334e9c687e06851f083845121ce502c06b54dda8c43092bcc1035df45cc752fe9b", + "0xb265644c29f628d1c7e8e25a5e845cabb21799371814730a41a363e1bda8a7be50fee7c3996a365b7fcba4642add10db", + "0xb8a915a3c685c2d4728f6931c4d29487cad764c5ce23c25e64b1a3259ac27235e41b23bfe7ae982921b4cb84463097df", + "0x8efa7338442a4b6318145a5440fc213b97869647eeae41b9aa3c0a27ee51285b73e3ae3b4a9423df255e6add58864aa9", + "0x9106d65444f74d217f4187dfc8fcf3810b916d1e4275f94f6a86d1c4f3565b131fd6cde1fa708bc05fe183c49f14941a", + "0x948252dac8026bbbdb0a06b3c9d66ec4cf9532163bab68076fda1bd2357b69e4b514729c15aaa83b5618b1977bbc60c4", + "0xae6596ccfdf5cbbc5782efe3bb0b101bb132dbe1d568854ca24cacc0b2e0e9fabcb2ca7ab42aecec412efd15cf8cb7a2", + "0x84a0b6c198ff64fd7958dfd1b40eac9638e8e0b2c4cd8cf5d8cdf80419baee76a05184bce6c5b635f6bf2d30055476a7", + "0x8893118be4a055c2b3da593dbca51b1ae2ea2469911acfb27ee42faf3e6c3ad0693d3914c508c0b05b36a88c8b312b76", + "0xb097479e967504deb6734785db7e60d1d8034d6ca5ba9552887e937f5e17bb413fccac2c1d1082154ed76609127860ad", + "0xa0294e6b9958f244d29943debf24b00b538b3da1116269b6e452bb12dc742226712fd1a15b9c88195afeb5d2415f505c", + "0xb3cc15f635080bc038f61b615f62b5b5c6f2870586191f59476e8368a73641d6ac2f7d0c1f54621982defdb318020230", + "0x99856f49b9fe1604d917c94d09cc0ed753d13d015d30587a94e6631ffd964b214e607deb8a69a8b5e349a7edf4309206", + "0xa8571e113ea22b4b4fce41a094da8c70de37830ae32e62c65c2fa5ad06a9bc29e884b945e73d448c72b176d6ecebfb58", + "0xa9e9c6e52beb0013273c29844956b3ce291023678107cdc785f7b44eff5003462841ad8780761b86aefc6b734adde7cf", + "0x80a784b0b27edb51ef2bad3aee80e51778dcaa0f3f5d3dcb5dc5d4f4b2cf7ae35b08de6680ea9dac53f8438b92eb09ef", + "0x827b543e609ea328e97e373f70ad72d4915a2d1daae0c60d44ac637231070e164c43a2a58db80a64df1c624a042b38f9", + "0xb449c65e8195202efdcb9bdb4e869a437313b118fef8b510cbbf8b79a4e99376adb749b37e9c20b51b31ed3310169e27", + "0x8ea3028f4548a79a94c717e1ed28ad4d8725b8d6ab18b021063ce46f665c79da3c49440c6577319dab2d036b7e08f387", + "0x897798431cfb17fe39f08f5f854005dc37b1c1ec1edba6c24bc8acb3b88838d0534a75475325a5ea98b326ad47dbad75", + "0x89cf232e6303b0751561960fd4dea5754a28c594daf930326b4541274ffb03c7dd75938e411eb9a375006a70ce38097f", + "0x9727c6ae7f0840f0b6c8bfb3a1a5582ceee705e0b5c59b97def7a7a2283edd4d3f47b7971e902a3a2079e40b53ff69b8", + "0xb76ed72b122c48679d221072efc0eeea063cb205cbf5f9ef0101fd10cb1075b8628166c83577cced654e1c001c7882f7", + "0xae908c42d208759da5ee9b405df85a6532ea35c6f0f6a1288d22870f59d98edc896841b8ac890a538e6c8d1e8b02d359", + "0x809d12fe4039a0ec80dc9be6a89acaab7797e5f7f9b163378f52f9a75a1d73b2e9ae6e3dd49e32ced439783c1cabbef5", + "0xa4149530b7f85d1098ba534d69548c6c612c416e8d35992fc1f64f4deeb41e09e49c6cf7aadbed7e846b91299358fe2d", + "0xa49342eacd1ec1148b8df1e253b1c015f603c39de11fa0a364ccb86ea32d69c34fd7aa6980a1fadcd8e785a57fa46f60", + "0x87d43eff5a006dc4dddcf76cc96c656a1f3a68f19f124181feab86c6cc9a52cb9189cdbb423414defdd9bb0ca8ff1ddc", + "0x861367e87a9aa2f0f68296ba50aa5dbc5713008d260cc2c7e62d407c2063064749324c4e8156dc21b749656cfebce26b", + "0xb5303c2f72e84e170e66ae1b0fbd51b8c7a6f27476eaf5694b64e8737d5c84b51fe90100b256465a4c4156dd873cddb0", + "0xb62849a4f891415d74f434cdc1d23c4a69074487659ca96e1762466b2b7a5d8525b056b891d0feea6fe6845cba8bc7fb", + "0x923dd9e0d6590a9307e8c4c23f13bae3306b580e297a937711a8b13e8de85e41a61462f25b7d352b682e8437bf2b4ab3", + "0x9147379860cd713cd46c94b8cdf75125d36c37517fbecf81ace9680b98ce6291cd1c3e472f84249cc3b2b445e314b1b6", + "0xa808a4f17ac21e3fb5cfef404e61fae3693ca3e688d375f99b6116779696059a146c27b06de3ac36da349b0649befd56", + "0x87787e9322e1b75e66c1f0d9ea0915722a232770930c2d2a95e9478c4b950d15ab767e30cea128f9ed65893bfc2d0743", + "0x9036a6ee2577223be105defe1081c48ea7319e112fff9110eb9f61110c319da25a6cea0464ce65e858635b079691ef1f", + "0xaf5548c7c24e1088c23b57ee14d26c12a83484c9fd9296edf1012d8dcf88243f20039b43c8c548c265ef9a1ffe9c1c88", + "0xa0fff520045e14065965fb8accd17e878d3fcaf9e0af2962c8954e50be6683d31fa0bf4816ab68f08630dbac6bfce52a", + "0xb4c1b249e079f6ae1781af1d97a60b15855f49864c50496c09c91fe1946266915b799f0406084d7783f5b1039116dd8b", + "0x8b0ffa5e7c498cb3879dddca34743b41eee8e2dea3d4317a6e961b58adb699ef0c92400c068d5228881a2b08121226bf", + "0x852ae8b19a1d80aa8ae5382e7ee5c8e7670ceb16640871c56b20b96b66b3b60e00015a3dde039446972e57b49a999ddd", + "0xa49942f04234a7d8492169da232cfff8051df86e8e1ba3db46aede02422c689c87dc1d99699c25f96cb763f5ca0983e5", + "0xb04b597b7760cf5dcf411ef896d1661e6d5b0db3257ac2cf64b20b60c6cc18fa10523bb958a48d010b55bac7b02ab3b1", + "0xa494591b51ea8285daecc194b5e5bd45ae35767d0246ac94fae204d674ee180c8e97ff15f71f28b7aeb175b8aea59710", + "0x97d2624919e78406e7460730680dea8e71c8571cf988e11441aeea54512b95bd820e78562c99372d535d96f7e200d20d", + "0xac693ddb00e48f76e667243b9b6a7008424043fb779e4f2252330285232c3fccac4da25cbd6d95fe9ad959ff305a91f6", + "0x8d20ca0a71a64a3f702a0825bb46bd810d03bebfb227683680d474a52f965716ff99e19a165ebaf6567987f4f9ee3c94", + "0xa5c516a438f916d1d68ca76996404792e0a66e97b7f18fc54c917bf10cf3211b62387932756e39e67e47b0bd6e88385a", + "0xb089614d830abc0afa435034cec7f851f2f095d479cacf1a3fb57272da826c499a52e7dcbc0eb85f4166fb94778e18e9", + "0xa8dacc943765d930848288192f4c69e2461c4b9bc6e79e30eeef9a543318cf9ae9569d6986c65c5668a89d49993f8e07", + "0xab5a9361fa339eec8c621bdad0a58078983abd8942d4282b22835d7a3a47e132d42414b7c359694986f7db39386c2e19", + "0x94230517fb57bd8eb26c6f64129b8b2abd0282323bf7b94b8bac7fab27b4ecc2c4290c294275e1a759de19f2216134f3", + "0xb8f158ea5006bc3b90b285246625faaa6ac9b5f5030dc69701b12f3b79a53ec7e92eeb5a63bbd1f9509a0a3469ff3ffc", + "0x8b6944fd8cb8540957a91a142fdcda827762aa777a31e8810ca6d026e50370ee1636fc351724767e817ca38804ebe005", + "0x82d1ee40fe1569c29644f79fa6c4033b7ed45cd2c3b343881f6eb0de2e79548fded4787fae19bed6ee76ed76ff9f2f11", + "0xa8924c7035e99eaed244ca165607e7e568b6c8085510dcdbaf6ebdbed405af2e6c14ee27d94ffef10d30aa52a60bf66d", + "0x956f82a6c2ae044635e85812581e4866c5fa2f427b01942047d81f6d79a14192f66fbbe77c9ffeaef4e6147097fdd2b5", + "0xb1100255a1bcf5e05b6aff1dfeb6e1d55b5d68d43a7457ba10cc76b61885f67f4d0d5179abda786e037ae95deb8eea45", + "0x99510799025e3e5e8fbf06dedb14c060c6548ba2bda824f687d3999dc395e794b1fb6514b9013f3892b6cf65cb0d65aa", + "0x8f9091cebf5e9c809aab415942172258f894e66e625d7388a05289183f01b8d994d52e05a8e69f784fba41db9ea357f0", + "0xa13d2eeb0776bdee9820ecb6693536720232848c51936bb4ef4fe65588d3f920d08a21907e1fdb881c1ad70b3725e726", + "0xa68b8f18922d550284c5e5dc2dda771f24c21965a6a4d5e7a71678178f46df4d8a421497aad8fcb4c7e241aba26378a0", + "0x8b7601f0a3c6ad27f03f2d23e785c81c1460d60100f91ea9d1cab978aa03b523150206c6d52ce7c7769c71d2c8228e9e", + "0xa8e02926430813caa851bb2b46de7f0420f0a64eb5f6b805401c11c9091d3b6d67d841b5674fa2b1dce0867714124cd8", + "0xb7968ecba568b8193b3058400af02c183f0a6df995a744450b3f7e0af7a772454677c3857f99c140bbdb2a09e832e8e0", + "0x8f20b1e9ba87d0a3f35309b985f3c18d2e8800f1ca7f0c52cadef773f1496b6070c936eea48c4a1cae83fd2524e9d233", + "0x88aef260042db0d641a51f40639dbeeefa9e9811df30bee695f3791f88a2f84d318f04e8926b7f47bf25956cb9e3754f", + "0x9725345893b647e9ba4e6a29e12f96751f1ae25fcaec2173e9a259921a1a7edb7a47159b3c8767e44d9e2689f5aa0f72", + "0x8c281e6f72752cb11e239e4df9341c45106eb7993c160e54423c2bffe10bc39d42624b45a1f673936ef2e1a02fc92f1a", + "0x90aba2f68bddb2fcce6c51430dacdfeec43ea8dc379660c99095df11017691ccf5faa27665cf4b9f0eea7728ae53c327", + "0xb7022695c16521c5704f49b7ddbdbec9b5f57ce0ceebe537bc0ebb0906d8196cc855a9afeb8950a1710f6a654464d93f", + "0x8fe1b9dd3c6a258116415d36e08374e094b22f0afb104385a5da48be17123e86fb8327baacc4f0d9ebae923d55d99bb5", + "0x817e85d8e3d19a4cbc1dec31597142c2daa4871bda89c2177fa719c00eda3344eb08b82eb92d4aa91a9eaacb3fc09783", + "0xb59053e1081d2603f1ca0ba553804d6fa696e1fd996631db8f62087b26a40dfef02098b0326bb75f99ec83b9267ca738", + "0x990a173d857d3ba81ff3789b931bfc9f5609cde0169b7f055fa3cb56451748d593d62d46ba33f80f9cafffe02b68dd14", + "0xb0c538dbba4954b809ab26f9f94a3cf1dcb77ce289eaec1d19f556c0ae4be1fa03af4a9b7057837541c3cc0a80538736", + "0xac3ba42f5f44f9e1fc453ce49c4ab79d0e1d5c42d3b30b1e098f3ab3f414c4c262fa12fb2be249f52d4aaf3c5224beb9", + "0xaf47467eb152e59870e21f0d4da2f43e093daf40180ab01438030684b114d025326928eaab12c41b81a066d94fce8436", + "0x98d1b58ba22e7289b1c45c79a24624f19b1d89e00f778eef327ec4856a9a897278e6f1a9a7e673844b31dde949153000", + "0x97ccb15dfadc7c59dca08cfe0d22df2e52c684cf97de1d94bc00d7ba24e020025130b0a39c0f4d46e4fc872771ee7875", + "0xb699e4ed9a000ff96ca296b2f09dce278832bc8ac96851ff3cff99ed3f6f752cfc0fea8571be28cd9b5a7ec36f1a08ee", + "0xb9f49f0edb7941cc296435ff0a912e3ad16848ee8765ab5f60a050b280d6ea585e5b34051b15f6b8934ef01ceb85f648", + "0xac3893df7b4ceab23c6b9054e48e8ba40d6e5beda8fbe90b814f992f52494186969b35d8c4cdc3c99890a222c9c09008", + "0xa41293ad22fae81dea94467bc1488c3707f3d4765059173980be93995fa4fcc3c9340796e3eed0beeb0ba0d9bb4fa3aa", + "0xa0543e77acd2aeecde13d18d258aeb2c7397b77f17c35a1992e8666ea7abcd8a38ec6c2741bd929abba2f766138618cc", + "0x92e79b22bc40e69f6527c969500ca543899105837b6b1075fa1796755c723462059b3d1b028e0b3df2559fa440e09175", + "0xa1fa1eac8f41a5197a6fb4aa1eae1a031c89f9c13ff9448338b222780cf9022e0b0925d930c37501a0ef7b2b00fdaf83", + "0xb3cb29ff73229f0637335f28a08ad8c5f166066f27c6c175164d0f26766a927f843b987ee9b309ed71cbf0a65d483831", + "0x84d4ab787f0ac00f104f4a734dc693d62d48c2aeb03913153da62c2ae2c27d11b1110dcef8980368dd84682ea2c1a308", + "0xab6a8e4bbc78d4a7b291ad3e9a8fe2d65f640524ba3181123b09d2d18a9e300e2509ccf7000fe47e75b65f3e992a2e7e", + "0xb7805ebe4f1a4df414003dc10bca805f2ab86ca75820012653e8f9b79c405196b0e2cab099f2ab953d67f0d60d31a0f9", + "0xb12c582454148338ea605d22bd00a754109063e22617f1f8ac8ddf5502c22a181c50c216c3617b9852aa5f26af56b323", + "0x86333ad9f898947e31ce747728dc8c887479e18d36ff3013f69ebef807d82c6981543b5c3788af93c4d912ba084d3cba", + "0xb514efa310dc4ad1258add138891e540d8c87142a881b5f46563cc58ecd1488e6d3a2fca54c0b72a929f3364ca8c333e", + "0xaa0a30f92843cf2f484066a783a1d75a7aa6f41f00b421d4baf20a6ac7886c468d0eea7ca8b17dd22f4f74631b62b640", + "0xb3b7dc63baec9a752e8433c0cdee4d0f9bc41f66f2b8d132faf925eef9cf89aae756fc132c45910f057122462605dc10", + "0xb9b8190dac5bfdeb59fd44f4da41a57e7f1e7d2c21faba9da91fa45cbeca06dcf299c9ae22f0c89ece11ac46352d619f", + "0x89f8cf36501ad8bdfeab863752a9090e3bfda57cf8fdeca2944864dc05925f501e252c048221bcc57136ab09a64b64b2", + "0xb0cbfaf317f05f97be47fc9d69eda2dd82500e00d42612f271a1fe24626408c28881f171e855bd5bd67409f9847502b4", + "0xa7c21a8fcede581bfd9847b6835eda62ba250bea81f1bb17372c800a19c732abe03064e64a2f865d974fb636cab4b859", + "0x95f9df524ba7a4667351696c4176b505d8ea3659f5ff2701173064acc624af69a0fad4970963736383b979830cb32260", + "0x856a74fe8b37a2e3afeac858c8632200485d438422a16ae3b29f359e470e8244995c63ad79c7e007ed063f178d0306fd", + "0xb37faa4d78fdc0bb9d403674dbea0176c2014a171c7be8527b54f7d1a32a76883d3422a3e7a5f5fcc5e9b31b57822eeb", + "0x8d37234d8594ec3fe75670b5c9cc1ec3537564d4739b2682a75b18b08401869a4264c0f264354219d8d896cded715db4", + "0xb5289ee5737f0e0bde485d32096d23387d68dab8f01f47821ab4f06cc79a967afe7355e72dc0c751d96b2747b26f6255", + "0x9085e1fdf9f813e9c3b8232d3c8863cd84ab30d45e8e0d3d6a0abd9ebc6fd70cdf749ff4d04390000e14c7d8c6655fc7", + "0x93a388c83630331eca4da37ea4a97b3b453238af474817cc0a0727fd3138dcb4a22de38c04783ec829c22cb459cb4e8e", + "0xa5377116027c5d061dbe24c240b891c08cdd8cd3f0899e848d682c873aff5b8132c1e7cfe76d2e5ed97ee0eb1d42cb68", + "0xa274c84b04338ed28d74683e2a7519c2591a3ce37c294d6f6e678f7d628be2db8eff253ede21823e2df7183e6552f622", + "0x8bc201147a842453a50bec3ac97671397bc086d6dfc9377fa38c2124cdc286abda69b7324f47d64da094ae011d98d9d9", + "0x9842d0c066c524592b76fbec5132bc628e5e1d21c424bec4555efca8619cc1fd8ea3161febcb8b9e8ab54702f4e815e2", + "0xa19191b713a07efe85c266f839d14e25660ee74452e6c691cd9997d85ae4f732052d802d3deb018bdd847caa298a894b", + "0xa24f71fc0db504da4e287dd118a4a74301cbcd16033937ba2abc8417956fcb4ae19b8e63b931795544a978137eff51cb", + "0xa90eec4a6a3a4b8f9a5b93d978b5026fcf812fe65585b008d7e08c4aaf21195a1d0699f12fc16f79b6a18a369af45771", + "0x8b551cf89737d7d06d9b3b9c4c1c73b41f2ea0af4540999c70b82dabff8580797cf0a3caf34c86c59a7069eb2e38f087", + "0xb8d312e6c635e7a216a1cda075ae77ba3e1d2fd501dc31e83496e6e81ed5d9c7799f8e578869c2e0e256fb29f5de10a7", + "0x8d144bdb8cae0b2cdb5b33d44bbc96984a5925202506a8cc65eb67ac904b466f5a7fe3e1cbf04aa785bbb7348c4bb73c", + "0xa101b3d58b7a98659244b88de0b478b3fb87dc5fc6031f6e689b99edf498abd43e151fd32bd4bbd240e0b3e59c440359", + "0x907453abca7d8e7151a05cc3d506c988007692fe7401395dc93177d0d07d114ab6cca0cc658eb94c0223fe8658295cad", + "0x825329ffbe2147ddb68f63a0a67f32d7f309657b8e5d9ab5bb34b3730bfa2c77a23eaaadb05def7d9f94a9e08fdc1e96", + "0x88ee923c95c1dac99ae7ed6067906d734d793c5dc5d26339c1bb3314abe201c5dccb33b9007351885eb2754e9a8ea06c", + "0x98bc9798543f5f1adc9f2cfcfa72331989420e9c3f6598c45269f0dc9b7c8607bbeaf03faa0aea2ddde2b8f17fdceff5", + "0x8ee87877702a79aef923ab970db6fa81561b3c07d5bf1a072af0a7bad765b4cbaec910afe1a91703feacc7822fa38a94", + "0x8060b9584aa294fe8adc2b22f67e988bc6da768eae91e429dcc43ddc53cfcc5d6753fdc1b420b268c7eb2fb50736a970", + "0xb344a5524d80a2f051870c7001f74fcf348a70fcf78dbd20c6ff9ca85d81567d2318c8b8089f2c4f195d6aec9fc15fa6", + "0x8f5a5d893e1936ed062149d20eb73d98b62b7f50ab5d93a6429c03656b36688d1c80cb5010e4977491e51fa0d7dd35d5", + "0x86fa32ebbf97328c5f5f15564e1238297e289ec3219b9a741724e9f3ae8d5c15277008f555863a478b247ba5dc601d44", + "0x9557e55377e279f4b6b5e0ffe01eca037cc13aac242d67dfcd0374a1e775c5ed5cb30c25fe21143fee54e3302d34a3ea", + "0x8cb6bcbc39372d23464a416ea7039f57ba8413cf3f00d9a7a5b356ab20dcb8ed11b3561f7bce372b8534d2870c7ee270", + "0xb5d59075cb5abde5391f64b6c3b8b50adc6e1f654e2a580b6d6d6eff3f4fbdd8fffc92e06809c393f5c8eab37f774c4b", + "0xafcfb6903ef13e493a1f7308675582f15af0403b6553e8c37afb8b2808ad21b88b347dc139464367dc260df075fea1ad", + "0x810fbbe808375735dd22d5bc7fc3828dc49fdd22cc2d7661604e7ac9c4535c1df578780affb3b895a0831640a945bcad", + "0x8056b0c678803b416f924e09a6299a33cf9ad7da6fe1ad7accefe95c179e0077da36815fde3716711c394e2c5ea7127f", + "0x8b67403702d06979be19f1d6dc3ec73cc2e81254d6b7d0cc49cd4fdda8cd51ab0835c1d2d26fc0ecab5df90585c2f351", + "0x87f97f9e6d4be07e8db250e5dd2bffdf1390665bc5709f2b631a6fa69a7fca958f19bd7cc617183da1f50ee63e9352b5", + "0xae151310985940471e6803fcf37600d7fa98830613e381e00dab943aec32c14162d51c4598e8847148148000d6e5af5c", + "0x81eb537b35b7602c45441cfc61b27fa9a30d3998fad35a064e05bc9479e9f10b62eba2b234b348219eea3cadcaac64bb", + "0x8a441434934180ab6f5bc541f86ebd06eadbee01f438836d797e930fa803a51510e005c9248cecc231a775b74d12b5e9", + "0x81f3c250a27ba14d8496a5092b145629eb2c2e6a5298438670375363f57e2798207832c8027c3e9238ad94ecdadfc4df", + "0xa6217c311f2f3db02ceaa5b6096849fe92b6f4b6f1491535ef8525f6ccee6130bed2809e625073ecbaddd4a3eb3df186", + "0x82d1c396f0388b942cf22b119d7ef1ad03d3dad49a74d9d01649ee284f377c8daddd095d596871669e16160299a210db", + "0xa40ddf7043c5d72a7246bd727b07f7fff1549f0e443d611de6f9976c37448b21664c5089c57f20105102d935ab82f27b", + "0xb6c03c1c97adf0c4bf4447ec71366c6c1bff401ba46236cd4a33d39291e7a1f0bb34bd078ba3a18d15c98993b153a279", + "0x8a94f5f632068399c359c4b3a3653cb6df2b207379b3d0cdace51afdf70d6d5cce6b89a2b0fee66744eba86c98fb21c2", + "0xb2f19e78ee85073f680c3bba1f07fd31b057c00b97040357d97855b54a0b5accb0d3b05b2a294568fcd6a4be6f266950", + "0xa74632d13bbe2d64b51d7a9c3ae0a5a971c19f51cf7596a807cea053e6a0f3719700976d4e394b356c0329a2dced9aa2", + "0xafef616d341a9bc94393b8dfba68ff0581436aa3a3adb7c26a1bbf2cf19fa877066191681f71f17f3cd6f9cf6bf70b5a", + "0x8ce96d93ae217408acf7eb0f9cbb9563363e5c7002e19bbe1e80760bc9d449daee2118f3878b955163ed664516b97294", + "0x8414f79b496176bc8b8e25f8e4cfee28f4f1c2ddab099d63d2aca1b6403d26a571152fc3edb97794767a7c4686ad557c", + "0xb6c61d01fd8ce087ef9f079bf25bf10090db483dd4f88c4a786d31c1bdf52065651c1f5523f20c21e75cea17df69ab73", + "0xa5790fd629be70545093631efadddc136661f63b65ec682609c38ef7d3d7fa4e56bdf94f06e263bc055b90cb1c6bcefe", + "0xb515a767e95704fb7597bca9e46f1753abacdc0e56e867ee3c6f4cd382643c2a28e65312c05ad040eaa3a8cbe7217a65", + "0x8135806a02ead6aa92e9adb6fefb91349837ab73105aaa7be488ef966aa8dfaafdfa64bbae30fcbfa55dd135a036a863", + "0x8f22435702716d76b1369750694540742d909d5e72b54d0878245fab7c269953b1c6f2b29c66f08d5e0263ca3a731771", + "0x8e0f8a8e8753e077dac95848212aeffd51c23d9b6d611df8b102f654089401954413ecbedc6367561ca599512ae5dda7", + "0x815a9084e3e2345f24c5fa559deec21ee1352fb60f4025c0779be65057f2d528a3d91593bd30d3a185f5ec53a9950676", + "0x967e6555ccba395b2cc1605f8484c5112c7b263f41ce8439a99fd1c71c5ed14ad02684d6f636364199ca48afbbde13be", + "0x8cd0ccf17682950b34c796a41e2ea7dd5367aba5e80a907e01f4cdc611e4a411918215e5aebf4292f8b24765d73314a6", + "0xa58bf1bbb377e4b3915df6f058a0f53b8fb8130fdec8c391f6bc82065694d0be59bb67ffb540e6c42cc8b380c6e36359", + "0x92af3151d9e6bfb3383d85433e953c0160859f759b0988431ec5893542ba40288f65db43c78a904325ef8d324988f09d", + "0x8011bbb05705167afb47d4425065630f54cb86cd462095e83b81dfebf348f846e4d8fbcf1c13208f5de1931f81da40b9", + "0x81c743c104fc3cb047885c9fa0fb9705c3a83ee24f690f539f4985509c3dafd507af3f6a2128276f45d5939ef70c167f", + "0xa2c9679b151c041aaf5efeac5a737a8f70d1631d931609fca16be1905682f35e291292874cb3b03f14994f98573c6f44", + "0xa4949b86c4e5b1d5c82a337e5ce6b2718b1f7c215148c8bfb7e7c44ec86c5c9476048fc5c01f57cb0920876478c41ad6", + "0x86c2495088bd1772152e527a1da0ef473f924ea9ab0e5b8077df859c28078f73c4e22e3a906b507fdf217c3c80808b5c", + "0x892e0a910dcf162bcea379763c3e2349349e4cda9402949255ac4a78dd5a47e0bf42f5bd0913951576b1d206dc1e536a", + "0xa7009b2c6b396138afe4754b7cc10dee557c51c7f1a357a11486b3253818531f781ea8107360c8d4c3b1cd96282353c0", + "0x911763ef439c086065cc7b4e57484ed6d693ea44acee4b18c9fd998116da55fbe7dcb8d2a0f0f9b32132fca82d73dff6", + "0xa722000b95a4a2d40bed81870793f15ba2af633f9892df507f2842e52452e02b5ea8dea6a043c2b2611d82376e33742a", + "0x9387ac49477bd719c2f92240d0bdfcf9767aad247ca93dc51e56106463206bc343a8ec855eb803471629a66fffb565d6", + "0x92819a1fa48ab4902939bb72a0a4e6143c058ea42b42f9bc6cea5df45f49724e2530daf3fc4f097cceefa2a8b9db0076", + "0x98eac7b04537653bc0f4941aae732e4b1f84bd276c992c64a219b8715eb1fb829b5cbd997d57feb15c7694c468f95f70", + "0xb275e7ba848ce21bf7996e12dbeb8dadb5d0e4f1cb5a0248a4f8f9c9fe6c74e3c93f4b61edbcb0a51af5a141e1c14bc7", + "0x97243189285aba4d49c53770c242f2faf5fd3914451da4931472e3290164f7663c726cf86020f8f181e568c72fd172d1", + "0x839b0b3c25dd412bee3dc24653b873cc65454f8f16186bb707bcd58259c0b6765fa4c195403209179192a4455c95f3b8", + "0x8689d1a870514568a074a38232e2ceb4d7df30fabeb76cff0aed5b42bf7f02baea12c5fadf69f4713464dbd52aafa55f", + "0x8958ae7b290f0b00d17c3e9fdb4dbf168432b457c7676829299dd428984aba892de1966fc106cfc58a772862ecce3976", + "0xa422bc6bd68b8870cfa5bc4ce71781fd7f4368b564d7f1e0917f6013c8bbb5b240a257f89ecfdbecb40fe0f3aa31d310", + "0xaa61f78130cebe09bc9a2c0a37f0dd57ed2d702962e37d38b1df7f17dc554b1d4b7a39a44182a452ce4c5eb31fa4cfcc", + "0xb7918bd114f37869bf1a459023386825821bfadce545201929d13ac3256d92a431e34f690a55d944f77d0b652cefeffc", + "0x819bba35fb6ace1510920d4dcff30aa682a3c9af9022e287751a6a6649b00c5402f14b6309f0aeef8fce312a0402915e", + "0x8b7c9ad446c6f63c11e1c24e24014bd570862b65d53684e107ba9ad381e81a2eaa96731b4b33536efd55e0f055071274", + "0x8fe79b53f06d33386c0ec7d6d521183c13199498594a46d44a8a716932c3ec480c60be398650bbfa044fa791c4e99b65", + "0x9558e10fb81250b9844c99648cf38fa05ec1e65d0ccbb18aa17f2d1f503144baf59d802c25be8cc0879fff82ed5034ad", + "0xb538a7b97fbd702ba84645ca0a63725be1e2891c784b1d599e54e3480e4670d0025526674ef5cf2f87dddf2290ba09f0", + "0x92eafe2e869a3dd8519bbbceb630585c6eb21712b2f31e1b63067c0acb5f9bdbbcbdb612db4ea7f9cc4e7be83d31973f", + "0xb40d21390bb813ab7b70a010dff64c57178418c62685761784e37d327ba3cb9ef62df87ecb84277c325a637fe3709732", + "0xb349e6fbf778c4af35fbed33130bd8a7216ed3ba0a79163ebb556e8eb8e1a7dad3456ddd700dad9d08d202491c51b939", + "0xa8fdaedecb251f892b66c669e34137f2650509ade5d38fbe8a05d9b9184bb3b2d416186a3640429bd1f3e4b903c159dd", + "0xac6167ebfee1dbab338eff7642f5e785fc21ef0b4ddd6660333fe398068cbd6c42585f62e81e4edbb72161ce852a1a4f", + "0x874b1fbf2ebe140c683bd7e4e0ab017afa5d4ad38055aaa83ee6bbef77dbc88a6ce8eb0dcc48f0155244af6f86f34c2d", + "0x903c58e57ddd9c446afab8256a6bb6c911121e6ccfb4f9b4ed3e2ed922a0e500a5cb7fa379d5285bc16e11dac90d1fda", + "0x8dae7a0cffa2fd166859cd1bf10ff82dd1932e488af377366b7efc0d5dec85f85fe5e8150ff86a79a39cefc29631733a", + "0xaa047857a47cc4dfc08585f28640420fcf105b881fd59a6cf7890a36516af0644d143b73f3515ab48faaa621168f8c31", + "0x864508f7077c266cc0cb3f7f001cb6e27125ebfe79ab57a123a8195f2e27d3799ff98413e8483c533b46a816a3557f1f", + "0x8bcd45ab1f9cbab36937a27e724af819838f66dfeb15923f8113654ff877bd8667c54f6307aaf0c35027ca11b6229bfd", + "0xb21aa34da9ab0a48fcfdd291df224697ce0c1ebc0e9b022fdee8750a1a4b5ba421c419541ed5c98b461eecf363047471", + "0xa9a18a2ab2fae14542dc336269fe612e9c1af6cf0c9ac933679a2f2cb77d3c304114f4d219ca66fe288adde30716775b", + "0xb5205989b92c58bdda71817f9a897e84100b5c4e708de1fced5c286f7a6f01ae96b1c8d845f3a320d77c8e2703c0e8b1", + "0xa364059412bbcc17b8907d43ac8e5df90bc87fd1724b5f99832d0d24559fae6fa76a74cff1d1eac8cbac6ec80b44af20", + "0xae709f2c339886b31450834cf29a38b26eb3b0779bd77c9ac269a8a925d1d78ea3837876c654b61a8fe834b3b6940808", + "0x8802581bba66e1952ac4dab36af371f66778958f4612901d95e5cac17f59165e6064371d02de8fb6fccf89c6dc8bd118", + "0xa313252df653e29c672cbcfd2d4f775089cb77be1077381cf4dc9533790e88af6cedc8a119158e7da5bf6806ad9b91a1", + "0x992a065b4152c7ef11515cd54ba9d191fda44032a01aed954acff3443377ee16680c7248d530b746b8c6dee2d634e68c", + "0xb627b683ee2b32c1ab4ccd27b9f6cce2fe097d96386fa0e5c182ad997c4c422ab8dfc03870cd830b8c774feb66537282", + "0xb823cf8a9aee03dadd013eb9efe40a201b4b57ef67efaae9f99683005f5d1bf55e950bf4af0774f50859d743642d3fea", + "0xb8a7449ffac0a3f206677097baf7ce00ca07a4d2bd9b5356fbcb83f3649b0fda07cfebad220c1066afba89e5a52abf4b", + "0xb2dd1a2f986395bb4e3e960fbbe823dbb154f823284ebc9068502c19a7609790ec0073d08bfa63f71e30c7161b6ef966", + "0x98e5236de4281245234f5d40a25b503505af140b503a035fc25a26159a9074ec81512b28f324c56ea2c9a5aa7ce90805", + "0x89070847dc8bbf5bc4ed073aa2e2a1f699cf0c2ca226f185a0671cecc54e7d3e14cd475c7752314a7a8e7476829da4bc", + "0xa9402dc9117fdb39c4734c0688254f23aed3dce94f5f53f5b7ef2b4bf1b71a67f85ab1a38ec224a59691f3bee050aeb3", + "0x957288f9866a4bf56a4204218ccc583f717d7ce45c01ea27142a7e245ad04a07f289cc044f8cf1f21d35e67e39299e9c", + "0xb2fb31ccb4e69113763d7247d0fc8edaae69b550c5c56aecacfd780c7217dc672f9fb7496edf4aba65dacf3361268e5b", + "0xb44a4526b2f1d6eb2aa8dba23bfa385ff7634572ab2afddd0546c3beb630fbfe85a32f42dd287a7fec069041411537f7", + "0x8db5a6660c3ac7fd7a093573940f068ee79a82bc17312af900b51c8c439336bc86ca646c6b7ab13aaaa008a24ca508ab", + "0x8f9899a6d7e8eb4367beb5c060a1f8e94d8a21099033ae582118477265155ba9e72176a67f7f25d7bad75a152b56e21a", + "0xa67de0e91ade8d69a0e00c9ff33ee2909b8a609357095fa12319e6158570c232e5b6f4647522efb7345ce0052aa9d489", + "0x82eb2414898e9c3023d57907a2b17de8e7eea5269029d05a94bfd7bf5685ac4a799110fbb375eb5e0e2bd16acf6458ae", + "0x94451fc7fea3c5a89ba701004a9693bab555cb622caf0896b678faba040409fdfd14a978979038b2a81e8f0abc4994d2", + "0xac879a5bb433998e289809a4a966bd02b4bf6a9c1cc276454e39c886efcf4fc68baebed575826bde577ab5aa71d735a9", + "0x880c0f8f49c875dfd62b4ddedde0f5c8b19f5687e693717f7e5c031bc580e58e13ab497d48b4874130a18743c59fdce3", + "0xb582af8d8ff0bf76f0a3934775e0b54c0e8fed893245d7d89cae65b03c8125b7237edc29dc45b4fe1a3fe6db45d280ee", + "0x89f337882ed3ae060aaee98efa20d79b6822bde9708c1c5fcee365d0ec9297f694cae37d38fd8e3d49717c1e86f078e7", + "0x826d2c1faea54061848b484e288a5f4de0d221258178cf87f72e14baaa4acc21322f8c9eab5dde612ef497f2d2e1d60b", + "0xa5333d4f227543e9cd741ccf3b81db79f2f03ca9e649e40d6a6e8ff9073e06da83683566d3b3c8d7b258c62970fb24d1", + "0xa28f08c473db06aaf4c043a2fae82b3c8cfaa160bce793a4c208e4e168fb1c65115ff8139dea06453c5963d95e922b94", + "0x8162546135cc5e124e9683bdfaa45833c18553ff06a0861c887dc84a5b12ae8cd4697f6794c7ef6230492c32faba7014", + "0xb23f0d05b74c08d6a7df1760792be83a761b36e3f8ae360f3c363fb196e2a9dd2de2e492e49d36561366e14daa77155c", + "0xb6f70d6c546722d3907c708d630dbe289771d2c8bf059c2e32b77f224696d750b4dda9b3a014debda38e7d02c9a77585", + "0x83bf4c4a9f3ca022c631017e7a30ea205ba97f7f5927cba8fc8489a4646eac6712cb821c5668c9ffe94d69d524374a27", + "0xb0371475425a8076d0dd5f733f55aabbe42d20a7c8ea7da352e736d4d35a327b2beb370dfcb05284e22cfd69c5f6c4cc", + "0xa0031ba7522c79211416c2cca3aa5450f96f8fee711552a30889910970ba13608646538781a2c08b834b140aadd7166f", + "0x99d273c80c7f2dc6045d4ed355d9fc6f74e93549d961f4a3b73cd38683f905934d359058cd1fc4da8083c7d75070487f", + "0xb0e4b0efa3237793e9dcce86d75aafe9879c5fa23f0d628649aef2130454dcf72578f9bf227b9d2b9e05617468e82588", + "0xa5ab076fa2e1c5c51f3ae101afdd596ad9d106bba7882b359c43d8548b64f528af19afa76cd6f40da1e6c5fca4def3fa", + "0x8ce2299e570331d60f6a6eff1b271097cd5f1c0e1113fc69b89c6a0f685dabea3e5bc2ac6bd789aa492ab189f89be494", + "0x91b829068874d911a310a5f9dee001021f97471307b5a3de9ec336870ec597413e1d92010ce320b619f38bed7c4f7910", + "0xb14fe91f4b07bf33b046e9285b66cb07927f3a8da0af548ac2569b4c4fb1309d3ced76d733051a20814e90dd5b75ffd1", + "0xabaab92ea6152d40f82940277c725aa768a631ee0b37f5961667f82fb990fc11e6d3a6a2752b0c6f94563ed9bb28265c", + "0xb7fe28543eca2a716859a76ab9092f135337e28109544f6bd2727728d0a7650428af5713171ea60bfc273d1c821d992c", + "0x8a4917b2ab749fc7343fc64bdf51b6c0698ff15d740cc7baf248c030475c097097d5a473bcc00d8c25817563fe0447b4", + "0xaa96156d1379553256350a0a3250166add75948fb9cde62aa555a0a9dc0a9cb7f2f7b8428aff66097bf6bfedaf14bbe2", + "0xae4ffeb9bdc76830d3eca2b705f30c1bdede6412fa064260a21562c8850c7fb611ec62bc68479fe48f692833e6f66d8d", + "0xb96543caaba9d051600a14997765d49e4ab10b07c7a92cccf0c90b309e6da334fdd6d18c96806cbb67a7801024fbd3c7", + "0x97b2b9ad76f19f500fcc94ca8e434176249f542ac66e5881a3dccd07354bdab6a2157018b19f8459437a68d8b86ba8e0", + "0xa8d206f6c5a14c80005849474fde44b1e7bcf0b2d52068f5f97504c3c035b09e65e56d1cf4b5322791ae2c2fdbd61859", + "0x936bad397ad577a70cf99bf9056584a61bd7f02d2d5a6cf219c05d770ae30a5cd902ba38366ce636067fc1dd10108d31", + "0xa77e30195ee402b84f3882e2286bf5380c0ed374a112dbd11e16cef6b6b61ab209d4635e6f35cdaaa72c1a1981d5dabe", + "0xa46ba4d3947188590a43c180757886a453a0503f79cc435322d92490446f37419c7b999fdf868a023601078070e03346", + "0x80d8d4c5542f223d48240b445d4d8cf6a75d120b060bc08c45e99a13028b809d910b534d2ac47fb7068930c54efd8da9", + "0x803be9c68c91b42b68e1f55e58917a477a9a6265e679ca44ee30d3eb92453f8c89c64eafc04c970d6831edd33d066902", + "0xb14b2b3d0dfe2bb57cee4cd72765b60ac33c1056580950be005790176543826c1d4fbd737f6cfeada6c735543244ab57", + "0xa9e480188bba1b8fb7105ff12215706665fd35bf1117bacfb6ab6985f4dbc181229873b82e5e18323c2b8f5de03258e0", + "0xa66a0f0779436a9a3999996d1e6d3000f22c2cac8e0b29cddef9636393c7f1457fb188a293b6c875b05d68d138a7cc4a", + "0x848397366300ab40c52d0dbbdafbafef6cd3dadf1503bb14b430f52bb9724188928ac26f6292a2412bc7d7aa620763c8", + "0x95466cc1a78c9f33a9aaa3829a4c8a690af074916b56f43ae46a67a12bb537a5ac6dbe61590344a25b44e8512355a4a7", + "0x8b5f7a959f818e3baf0887f140f4575cac093d0aece27e23b823cf421f34d6e4ff4bb8384426e33e8ec7b5eed51f6b5c", + "0x8d5e1368ec7e3c65640d216bcc5d076f3d9845924c734a34f3558ac0f16e40597c1a775a25bf38b187213fbdba17c93b", + "0xb4647c1b823516880f60d20c5cc38c7f80b363c19d191e8992226799718ee26b522a12ecb66556ed3d483aa4824f3326", + "0xac3abaea9cd283eb347efda4ed9086ea3acf495043e08d0d19945876329e8675224b685612a6badf8fd72fb6274902b1", + "0x8eae1ce292d317aaa71bcf6e77e654914edd5090e2e1ebab78b18bb41b9b1bc2e697439f54a44c0c8aa0d436ebe6e1a9", + "0x94dc7d1aec2c28eb43d93b111fa59aaa0d77d5a09501220bd411768c3e52208806abf973c6a452fd8292ff6490e0c9e2", + "0x8fd8967f8e506fef27d17b435d6b86b232ec71c1036351f12e6fb8a2e12daf01d0ee04451fb944d0f1bf7fd20e714d02", + "0x824e6865be55d43032f0fec65b3480ea89b0a2bf860872237a19a54bc186a85d2f8f9989cc837fbb325b7c72d9babe2c", + "0x8bd361f5adb27fd6f4e3f5de866e2befda6a8454efeb704aacc606f528c03f0faae888f60310e49440496abd84083ce2", + "0xb098a3c49f2aaa28b6b3e85bc40ce6a9cdd02134ee522ae73771e667ad7629c8d82c393fba9f27f5416986af4c261438", + "0xb385f5ca285ff2cfe64dcaa32dcde869c28996ed091542600a0b46f65f3f5a38428cca46029ede72b6cf43e12279e3d3", + "0x8196b03d011e5be5288196ef7d47137d6f9237a635ab913acdf9c595fa521d9e2df722090ec7eb0203544ee88178fc5f", + "0x8ed1270211ef928db18e502271b7edf24d0bbd11d97f2786aee772d70c2029e28095cf8f650b0328cc8a4c38d045316d", + "0xa52ab60e28d69b333d597a445884d44fd2a7e1923dd60f763951e1e45f83e27a4dac745f3b9eff75977b3280e132c15d", + "0x91e9fe78cdac578f4a4687f71b800b35da54b824b1886dafec073a3c977ce7a25038a2f3a5b1e35c2c8c9d1a7312417c", + "0xa42832173f9d9491c7bd93b21497fbfa4121687cd4d2ab572e80753d7edcbb42cfa49f460026fbde52f420786751a138", + "0x97b947126d84dcc70c97be3c04b3de3f239b1c4914342fa643b1a4bb8c4fe45c0fcb585700d13a7ed50784790c54bef9", + "0x860e407d353eac070e2418ef6cb80b96fc5f6661d6333e634f6f306779651588037be4c2419562c89c61f9aa2c4947f5", + "0xb2c9d93c3ba4e511b0560b55d3501bf28a510745fd666b3cb532db051e6a8617841ea2f071dda6c9f15619c7bfd2737f", + "0x8596f4d239aeeac78311207904d1bd863ef68e769629cc379db60e019aaf05a9d5cd31dc8e630b31e106a3a93e47cbc5", + "0x8b26e14e2e136b65c5e9e5c2022cee8c255834ea427552f780a6ca130a6446102f2a6f334c3f9a0308c53df09e3dba7e", + "0xb54724354eb515a3c8bed0d0677ff1db94ac0a07043459b4358cb90e3e1aa38ac23f2caa3072cf9647275d7cd61d0e80", + "0xb7ce9fe0e515e7a6b2d7ddcb92bc0196416ff04199326aea57996eef8c5b1548bd8569012210da317f7c0074691d01b7", + "0xa1a13549c82c877253ddefa36a29ea6a23695ee401fdd48e65f6f61e5ebd956d5e0edeff99484e9075cb35071fec41e2", + "0x838ba0c1e5bd1a6da05611ff1822b8622457ebd019cb065ece36a2d176bd2d889511328120b8a357e44569e7f640c1e6", + "0xb916eccff2a95519400bbf76b5f576cbe53cf200410370a19d77734dc04c05b585cfe382e8864e67142d548cd3c4c2f4", + "0xa610447cb7ca6eea53a6ff1f5fe562377dcb7f4aaa7300f755a4f5e8eba61e863c51dc2aa9a29b35525b550fbc32a0fe", + "0x9620e8f0f0ee9a4719aa9685eeb1049c5c77659ba6149ec4c158f999cfd09514794b23388879931fe26fea03fa471fd3", + "0xa9dcf8b679e276583cf5b9360702a185470d09aea463dc474ee9c8aee91ef089dacb073e334e47fbc78ec5417c90465c", + "0x8c9adee8410bdd99e5b285744cee61e2593b6300ff31a8a83b0ec28da59475a5c6fb9346fe43aadea2e6c3dad2a8e30a", + "0x97d5afe9b3897d7b8bb628b7220cf02d8ee4e9d0b78f5000d500aaf4c1df9251aaaabfd1601626519f9d66f00a821d4e", + "0x8a382418157b601ce4c3501d3b8409ca98136a4ef6abcbf62885e16e215b76b035c94d149cc41ff92e42ccd7c43b9b3d", + "0xb64b8d11fb3b01abb2646ac99fdb9c02b804ce15d98f9fe0fbf1c9df8440c71417487feb6cdf51e3e81d37104b19e012", + "0x849d7d044f9d8f0aab346a9374f0b3a5d14a9d1faa83dbacccbdc629ad1ef903a990940255564770537f8567521d17f0", + "0x829dbb0c76b996c2a91b4cbbe93ba455ca0d5729755e5f0c92aaee37dff7f36fcdc06f33aca41f1b609c784127b67d88", + "0x85a7c0069047b978422d264d831ab816435f63938015d2e977222b6b5746066c0071b7f89267027f8a975206ed25c1b0", + "0x84b9fbc1cfb302df1acdcf3dc5d66fd1edfe7839f7a3b2fb3a0d5548656249dd556104d7c32b73967bccf0f5bdcf9e3b", + "0x972220ac5b807f53eac37dccfc2ad355d8b21ea6a9c9b011c09fe440ddcdf7513e0b43d7692c09ded80d7040e26aa28f", + "0x855885ed0b21350baeca890811f344c553cf9c21024649c722453138ba29193c6b02c4b4994cd414035486f923472e28", + "0x841874783ae6d9d0e59daea03e96a01cbbe4ecaced91ae4f2c8386e0d87b3128e6d893c98d17c59e4de1098e1ad519dd", + "0x827e50fc9ce56f97a4c3f2f4cbaf0b22f1c3ce6f844ff0ef93a9c57a09b8bf91ebfbd2ba9c7f83c442920bffdaf288cc", + "0xa441f9136c7aa4c08d5b3534921b730e41ee91ab506313e1ba5f7c6f19fd2d2e1594e88c219834e92e6fb95356385aa7", + "0x97d75b144471bf580099dd6842b823ec0e6c1fb86dd0da0db195e65524129ea8b6fd4a7a9bbf37146269e938a6956596", + "0xa4b6fa87f09d5a29252efb2b3aaab6b3b6ea9fab343132a651630206254a25378e3e9d6c96c3d14c150d01817d375a8e", + "0xa31a671876d5d1e95fe2b8858dc69967231190880529d57d3cab7f9f4a2b9b458ac9ee5bdaa3289158141bf18f559efb", + "0x90bee6fff4338ba825974021b3b2a84e36d617e53857321f13d2b3d4a28954e6de3b3c0e629d61823d18a9763313b3bf", + "0x96b622a63153f393bb419bfcf88272ea8b3560dbd46b0aa07ada3a6223990d0abdd6c2adb356ef4be5641688c8d83941", + "0x84c202adeaff9293698022bc0381adba2cd959f9a35a4e8472288fd68f96f6de8be9da314c526d88e291c96b1f3d6db9", + "0x8ca01a143b8d13809e5a8024d03e6bc9492e22226073ef6e327edf1328ef4aff82d0bcccee92cb8e212831fa35fe1204", + "0xb2f970dbad15bfbefb38903c9bcc043d1367055c55dc1100a850f5eb816a4252c8c194b3132c929105511e14ea10a67d", + "0xa5e36556472a95ad57eb90c3b6623671b03eafd842238f01a081997ffc6e2401f76e781d049bb4aa94d899313577a9cf", + "0x8d1057071051772f7c8bedce53a862af6fd530dd56ae6321eaf2b9fc6a68beff5ed745e1c429ad09d5a118650bfd420a", + "0x8aadc4f70ace4fcb8d93a78610779748dcffc36182d45b932c226dc90e48238ea5daa91f137c65ed532352c4c4d57416", + "0xa2ea05ae37e673b4343232ae685ee14e6b88b867aef6dfac35db3589cbcd76f99540fed5c2641d5bb5a4a9f808e9bf0d", + "0x947f1abad982d65648ae4978e094332b4ecb90f482c9be5741d5d1cf5a28acf4680f1977bf6e49dd2174c37f11e01296", + "0xa27b144f1565e4047ba0e3f4840ef19b5095d1e281eaa463c5358f932114cbd018aa6dcf97546465cf2946d014d8e6d6", + "0x8574e1fc3acade47cd4539df578ce9205e745e161b91e59e4d088711a7ab5aa3b410d517d7304b92109924d9e2af8895", + "0xa48ee6b86b88015d6f0d282c1ae01d2a5b9e8c7aa3d0c18b35943dceb1af580d08a65f54dc6903cde82fd0d73ce94722", + "0x8875650cec543a7bf02ea4f2848a61d167a66c91ffaefe31a9e38dc8511c6a25bde431007eefe27a62af3655aca208dc", + "0x999b0a6e040372e61937bf0d68374e230346b654b5a0f591a59d33a4f95bdb2f3581db7c7ccb420cd7699ed709c50713", + "0x878c9e56c7100c5e47bbe77dc8da5c5fe706cec94d37fa729633bca63cace7c40102eee780fcdabb655f5fa47a99600e", + "0x865006fb5b475ada5e935f27b96f9425fc2d5449a3c106aa366e55ebed3b4ee42adc3c3f0ac19fd129b40bc7d6bc4f63", + "0xb7a7da847f1202e7bc1672553e68904715e84fd897d529243e3ecda59faa4e17ba99c649a802d53f6b8dfdd51f01fb74", + "0x8b2fb4432c05653303d8c8436473682933a5cb604da10c118ecfcd2c8a0e3132e125afef562bdbcc3df936164e5ce4f2", + "0x808d95762d33ddfa5d0ee3d7d9f327de21a994d681a5f372e2e3632963ea974da7f1f9e5bac8ccce24293509d1f54d27", + "0x932946532e3c397990a1df0e94c90e1e45133e347a39b6714c695be21aeb2d309504cb6b1dde7228ff6f6353f73e1ca2", + "0x9705e7c93f0cdfaa3fa96821f830fe53402ad0806036cd1b48adc2f022d8e781c1fbdab60215ce85c653203d98426da3", + "0xaa180819531c3ec1feb829d789cb2092964c069974ae4faad60e04a6afcce5c3a59aec9f11291e6d110a788d22532bc6", + "0x88f755097f7e25cb7dd3c449520c89b83ae9e119778efabb54fbd5c5714b6f37c5f9e0346c58c6ab09c1aef2483f895d", + "0x99fc03ab7810e94104c494f7e40b900f475fde65bdec853e60807ffd3f531d74de43335c3b2646b5b8c26804a7448898", + "0xaf2dea9683086bed1a179110efb227c9c00e76cd00a2015b089ccbcee46d1134aa18bda5d6cab6f82ae4c5cd2461ac21", + "0xa500f87ba9744787fdbb8e750702a3fd229de6b8817594348dec9a723b3c4240ddfa066262d002844b9e38240ce55658", + "0x924d0e45c780f5bc1c1f35d15dfc3da28036bdb59e4c5440606750ecc991b85be18bc9a240b6c983bc5430baa4c68287", + "0x865b11e0157b8bf4c5f336024b016a0162fc093069d44ac494723f56648bc4ded13dfb3896e924959ea11c96321afefc", + "0x93672d8607d4143a8f7894f1dcca83fb84906dc8d6dd7dd063bb0049cfc20c1efd933e06ca7bd03ea4cb5a5037990bfe", + "0x826891efbdff0360446825a61cd1fa04326dd90dae8c33dfb1ed97b045e165766dd070bd7105560994d0b2044bdea418", + "0x93c4a4a8bcbc8b190485cc3bc04175b7c0ed002c28c98a540919effd6ed908e540e6594f6db95cd65823017258fb3b1c", + "0xaeb2a0af2d2239fda9aa6b8234b019708e8f792834ff0dd9c487fa09d29800ddceddd6d7929faa9a3edcb9e1b3aa0d6b", + "0x87f11de7236d387863ec660d2b04db9ac08143a9a2c4dfff87727c95b4b1477e3bc473a91e5797313c58754905079643", + "0x80dc1db20067a844fe8baceca77f80db171a5ca967acb24e2d480eae9ceb91a3343c31ad1c95b721f390829084f0eae6", + "0x9825c31f1c18da0de3fa84399c8b40f8002c3cae211fb6a0623c76b097b4d39f5c50058f57a16362f7a575909d0a44a2", + "0xa99fc8de0c38dbf7b9e946de83943a6b46a762167bafe2a603fb9b86f094da30d6de7ed55d639aafc91936923ee414b3", + "0xad594678b407db5d6ea2e90528121f84f2b96a4113a252a30d359a721429857c204c1c1c4ff71d8bb5768c833f82e80e", + "0xb33d985e847b54510b9b007e31053732c8a495e43be158bd2ffcea25c6765bcbc7ca815f7c60b36ad088b955dd6e9350", + "0x815f8dfc6f90b3342ca3fbd968c67f324dae8f74245cbf8bc3bef10e9440c65d3a2151f951e8d18959ba01c1b50b0ec1", + "0x94c608a362dd732a1abc56e338637c900d59013db8668e49398b3c7a0cae3f7e2f1d1bf94c0299eeafe6af7f76c88618", + "0x8ebd8446b23e5adfcc393adc5c52fe172f030a73e63cd2d515245ca0dd02782ceed5bcdd9ccd9c1b4c5953dfac9c340c", + "0x820437f3f6f9ad0f5d7502815b221b83755eb8dc56cd92c29e9535eb0b48fb8d08c9e4fcc26945f9c8cca60d89c44710", + "0x8910e4e8a56bf4be9cc3bbf0bf6b1182a2f48837a2ed3c2aaec7099bfd7f0c83e14e608876b17893a98021ff4ab2f20d", + "0x9633918fde348573eec15ce0ad53ac7e1823aac86429710a376ad661002ae6d049ded879383faaa139435122f64047c6", + "0xa1f5e3fa558a9e89318ca87978492f0fb4f6e54a9735c1b8d2ecfb1d1c57194ded6e0dd82d077b2d54251f3bee1279e1", + "0xb208e22d04896abfd515a95c429ff318e87ff81a5d534c8ac2c33c052d6ffb73ef1dccd39c0bbe0734b596c384014766", + "0x986d5d7d2b5bde6d16336f378bd13d0e671ad23a8ec8a10b3fc09036faeeb069f60662138d7a6df3dfb8e0d36180f770", + "0xa2d4e6c5f5569e9cef1cddb569515d4b6ace38c8aed594f06da7434ba6b24477392cc67ba867c2b079545ca0c625c457", + "0xb5ac32b1d231957d91c8b7fc43115ce3c5c0d8c13ca633374402fa8000b6d9fb19499f9181844f0c10b47357f3f757ce", + "0x96b8bf2504b4d28fa34a4ec378e0e0b684890c5f44b7a6bb6e19d7b3db2ab27b1e2686389d1de9fbd981962833a313ea", + "0x953bfd7f6c3a0469ad432072b9679a25486f5f4828092401eff494cfb46656c958641a4e6d0d97d400bc59d92dba0030", + "0x876ab3cea7484bbfd0db621ec085b9ac885d94ab55c4bb671168d82b92e609754b86aaf472c55df3d81421d768fd108a", + "0x885ff4e67d9ece646d02dd425aa5a087e485c3f280c3471b77532b0db6145b69b0fbefb18aa2e3fa5b64928b43a94e57", + "0xb91931d93f806d0b0e6cc62a53c718c099526140f50f45d94b8bbb57d71e78647e06ee7b42aa5714aed9a5c05ac8533f", + "0xa0313eeadd39c720c9c27b3d671215331ab8d0a794e71e7e690f06bcd87722b531d6525060c358f35f5705dbb7109ccb", + "0x874c0944b7fedc6701e53344100612ddcb495351e29305c00ec40a7276ea5455465ffb7bded898886c1853139dfb1fc7", + "0x8dc31701a01ee8137059ca1874a015130d3024823c0576aa9243e6942ec99d377e7715ed1444cd9b750a64b85dcaa3e5", + "0x836d2a757405e922ec9a2dfdcf489a58bd48b5f9683dd46bf6047688f778c8dee9bc456de806f70464df0b25f3f3d238", + "0xb30b0a1e454a503ea3e2efdec7483eaf20b0a5c3cefc42069e891952b35d4b2c955cf615f3066285ed8fafd9fcfbb8f6", + "0x8e6d4044b55ab747e83ec8762ea86845f1785cc7be0279c075dadf08aca3ccc5a096c015bb3c3f738f647a4eadea3ba5", + "0xad7735d16ab03cbe09c029610aa625133a6daecfc990b297205b6da98eda8c136a7c50db90f426d35069708510d5ae9c", + "0x8d62d858bbb59ec3c8cc9acda002e08addab4d3ad143b3812098f3d9087a1b4a1bb255dcb1635da2402487d8d0249161", + "0x805beec33238b832e8530645a3254aeef957e8f7ea24bcfc1054f8b9c69421145ebb8f9d893237e8a001c857fedfc77e", + "0xb1005644be4b085e3f5775aa9bd3e09a283e87ddada3082c04e7a62d303dcef3b8cf8f92944c200c7ae6bb6bdf63f832", + "0xb4ba0e0790dc29063e577474ffe3b61f5ea2508169f5adc1e394934ebb473e356239413a17962bc3e5d3762d72cce8c2", + "0xa157ba9169c9e3e6748d9f1dd67fbe08b9114ade4c5d8fc475f87a764fb7e6f1d21f66d7905cd730f28a1c2d8378682a", + "0x913e52b5c93989b5d15e0d91aa0f19f78d592bc28bcfdfddc885a9980c732b1f4debb8166a7c4083c42aeda93a702898", + "0x90fbfc1567e7cd4e096a38433704d3f96a2de2f6ed3371515ccc30bc4dd0721a704487d25a97f3c3d7e4344472702d8d", + "0x89646043028ffee4b69d346907586fd12c2c0730f024acb1481abea478e61031966e72072ff1d5e65cb8c64a69ad4eb1", + "0xb125a45e86117ee11d2fb42f680ab4a7894edd67ff927ae2c808920c66c3e55f6a9d4588eee906f33a05d592e5ec3c04", + "0xaad47f5b41eae9be55fb4f67674ff1e4ae2482897676f964a4d2dcb6982252ee4ff56aac49578b23f72d1fced707525e", + "0xb9ddff8986145e33851b4de54d3e81faa3352e8385895f357734085a1616ef61c692d925fe62a5ed3be8ca49f5d66306", + "0xb3cb0963387ed28c0c0adf7fe645f02606e6e1780a24d6cecef5b7c642499109974c81a7c2a198b19862eedcea2c2d8c", + "0xac9c53c885457aaf5cb36c717a6f4077af701e0098eebd7aa600f5e4b14e6c1067255b3a0bc40e4a552025231be7de60", + "0x8e1a8d823c4603f6648ec21d064101094f2a762a4ed37dd2f0a2d9aa97b2d850ce1e76f4a4b8cae58819b058180f7031", + "0xb268b73bf7a179b6d22bd37e5e8cb514e9f5f8968c78e14e4f6d5700ca0d0ca5081d0344bb73b028970eebde3cb4124e", + "0xa7f57d71940f0edbd29ed8473d0149cae71d921dd15d1ff589774003e816b54b24de2620871108cec1ab9fa956ad6ce6", + "0x8053e6416c8b120e2b999cc2fc420a6a55094c61ac7f2a6c6f0a2c108a320890e389af96cbe378936132363c0d551277", + "0xb3823f4511125e5aa0f4269e991b435a0d6ceb523ebd91c04d7add5534e3df5fc951c504b4fd412a309fd3726b7f940b", + "0xae6eb04674d04e982ca9a6add30370ab90e303c71486f43ed3efbe431af1b0e43e9d06c11c3412651f304c473e7dbf39", + "0x96ab55e641ed2e677591f7379a3cd126449614181fce403e93e89b1645d82c4af524381ff986cae7f9cebe676878646d", + "0xb52423b4a8c37d3c3e2eca8f0ddbf7abe0938855f33a0af50f117fab26415fb0a3da5405908ec5fdc22a2c1f2ca64892", + "0x82a69ce1ee92a09cc709d0e3cd22116c9f69d28ea507fe5901f5676000b5179b9abe4c1875d052b0dd42d39925e186bb", + "0xa84c8cb84b9d5cfb69a5414f0a5283a5f2e90739e9362a1e8c784b96381b59ac6c18723a4aa45988ee8ef5c1f45cc97d", + "0xafd7efce6b36813082eb98257aae22a4c1ae97d51cac7ea9c852d4a66d05ef2732116137d8432e3f117119725a817d24", + "0xa0f5fe25af3ce021b706fcff05f3d825384a272284d04735574ce5fb256bf27100fad0b1f1ba0e54ae9dcbb9570ecad3", + "0x8751786cb80e2e1ff819fc7fa31c2833d25086534eb12b373d31f826382430acfd87023d2a688c65b5e983927e146336", + "0x8cf5c4b17fa4f3d35c78ce41e1dc86988fd1135cd5e6b2bb0c108ee13538d0d09ae7102609c6070f39f937b439b31e33", + "0xa9108967a2fedd7c322711eca8159c533dd561bedcb181b646de98bf5c3079449478eab579731bee8d215ae8852c7e21", + "0xb54c5171704f42a6f0f4e70767cdb3d96ffc4888c842eece343a01557da405961d53ffdc34d2f902ea25d3e1ed867cad", + "0xae8d4b764a7a25330ba205bf77e9f46182cd60f94a336bbd96773cf8064e3d39caf04c310680943dc89ed1fbad2c6e0d", + "0xaa5150e911a8e1346868e1b71c5a01e2a4bb8632c195861fb6c3038a0e9b85f0e09b3822e9283654a4d7bb17db2fc5f4", + "0x9685d3756ce9069bf8bb716cf7d5063ebfafe37e15b137fc8c3159633c4e006ff4887ddd0ae90360767a25c3f90cba7f", + "0x82155fd70f107ab3c8e414eadf226c797e07b65911508c76c554445422325e71af8c9a8e77fd52d94412a6fc29417cd3", + "0xabfae52f53a4b6e00760468d973a267f29321997c3dbb5aee36dc1f20619551229c0c45b9d9749f410e7f531b73378e8", + "0x81a76d921f8ef88e774fd985e786a4a330d779b93fad7def718c014685ca0247379e2e2a007ad63ee7f729cd9ed6ce1b", + "0x81947c84bc5e28e26e2e533af5ae8fe10407a7b77436dbf8f1d5b0bbe86fc659eae10f974659dc7c826c6dabd03e3a4b", + "0x92b8c07050d635b8dd4fd09df9054efe4edae6b86a63c292e73cc819a12a21dd7d104ce51fa56af6539dedf6dbe6f7b6", + "0xb44c579e3881f32b32d20c82c207307eca08e44995dd2aac3b2692d2c8eb2a325626c80ac81c26eeb38c4137ff95add5", + "0x97efab8941c90c30860926dea69a841f2dcd02980bf5413b9fd78d85904588bf0c1021798dbc16c8bbb32cce66c82621", + "0x913363012528b50698e904de0588bf55c8ec5cf6f0367cfd42095c4468fcc64954fbf784508073e542fee242d0743867", + "0x8ed203cf215148296454012bd10fddaf119203db1919a7b3d2cdc9f80e66729464fdfae42f1f2fc5af1ed53a42b40024", + "0xab84312db7b87d711e9a60824f4fe50e7a6190bf92e1628688dfcb38930fe87b2d53f9e14dd4de509b2216856d8d9188", + "0x880726def069c160278b12d2258eac8fa63f729cd351a710d28b7e601c6712903c3ac1e7bbd0d21e4a15f13ca49db5aa", + "0x980699cd51bac6283959765f5174e543ed1e5f5584b5127980cbc2ef18d984ecabba45042c6773b447b8e694db066028", + "0xaeb019cb80dc4cb4207430d0f2cd24c9888998b6f21d9bf286cc638449668d2eec0018a4cf3fe6448673cd6729335e2b", + "0xb29852f6aa6c60effdffe96ae88590c88abae732561d35cc19e82d3a51e26cb35ea00986193e07f90060756240f5346e", + "0xa0fa855adc5ba469f35800c48414b8921455950a5c0a49945d1ef6e8f2a1881f2e2dfae47de6417270a6bf49deeb091d", + "0xb6c7332e3b14813641e7272d4f69ecc7e09081df0037d6dab97ce13a9e58510f5c930d300633f208181d9205c5534001", + "0x85a6c050f42fce560b5a8d54a11c3bbb8407abbadd859647a7b0c21c4b579ec65671098b74f10a16245dc779dff7838e", + "0x8f3eb34bb68759d53c6677de4de78a6c24dd32c8962a7fb355ed362572ef8253733e6b52bc21c9f92ecd875020a9b8de", + "0xa17dd44181e5dab4dbc128e1af93ec22624b57a448ca65d2d9e246797e4af7d079e09c6e0dfb62db3a9957ce92f098d5", + "0xa56a1b854c3183082543a8685bb34cae1289f86cfa8123a579049dbd059e77982886bfeb61bf6e05b4b1fe4e620932e7", + "0xaedae3033cb2fb7628cb4803435bdd7757370a86f808ae4cecb9a268ad0e875f308c048c80cbcac523de16b609683887", + "0x9344905376aa3982b1179497fac5a1d74b14b7038fd15e3b002db4c11c8bfc7c39430db492cdaf58b9c47996c9901f28", + "0xa3bfafdae011a19f030c749c3b071f83580dee97dd6f949e790366f95618ca9f828f1daaeabad6dcd664fcef81b6556d", + "0x81c03d8429129e7e04434dee2c529194ddb01b414feda3adee2271eb680f6c85ec872a55c9fa9d2096f517e13ed5abcc", + "0x98205ef3a72dff54c5a9c82d293c3e45d908946fa74bb749c3aabe1ab994ea93c269bcce1a266d2fe67a8f02133c5985", + "0x85a70aeed09fda24412fadbafbbbf5ba1e00ac92885df329e147bfafa97b57629a3582115b780d8549d07d19b7867715", + "0xb0fbe81c719f89a57d9ea3397705f898175808c5f75f8eb81c2193a0b555869ba7bd2e6bc54ee8a60cea11735e21c68c", + "0xb03a0bd160495ee626ff3a5c7d95bc79d7da7e5a96f6d10116600c8fa20bedd1132f5170f25a22371a34a2d763f2d6d0", + "0xa90ab04091fbca9f433b885e6c1d60ab45f6f1daf4b35ec22b09909d493a6aab65ce41a6f30c98239cbca27022f61a8b", + "0xb66f92aa3bf2549f9b60b86f99a0bd19cbdd97036d4ae71ca4b83d669607f275260a497208f6476cde1931d9712c2402", + "0xb08e1fdf20e6a9b0b4942f14fa339551c3175c1ffc5d0ab5b226b6e6a322e9eb0ba96adc5c8d59ca4259e2bdd04a7eb0", + "0xa2812231e92c1ce74d4f5ac3ab6698520288db6a38398bb38a914ac9326519580af17ae3e27cde26607e698294022c81", + "0xabfcbbcf1d3b9e84c02499003e490a1d5d9a2841a9e50c7babbef0b2dd20d7483371d4dc629ba07faf46db659459d296", + "0xb0fe9f98c3da70927c23f2975a9dc4789194d81932d2ad0f3b00843dd9cbd7fb60747a1da8fe5a79f136a601becf279d", + "0xb130a6dba7645165348cb90f023713bed0eefbd90a976b313521c60a36d34f02032e69a2bdcf5361e343ed46911297ec", + "0x862f0cffe3020cea7a5fd4703353aa1eb1be335e3b712b29d079ff9f7090d1d8b12013011e1bdcbaa80c44641fd37c9f", + "0x8c6f11123b26633e1abb9ed857e0bce845b2b3df91cc7b013b2fc77b477eee445da0285fc6fc793e29d5912977f40916", + "0x91381846126ea819d40f84d3005e9fb233dc80071d1f9bb07f102bf015f813f61e5884ffffb4f5cd333c1b1e38a05a58", + "0x8add7d908de6e1775adbd39c29a391f06692b936518db1f8fde74eb4f533fc510673a59afb86e3a9b52ade96e3004c57", + "0x8780e086a244a092206edcde625cafb87c9ab1f89cc3e0d378bc9ee776313836160960a82ec397bc3800c0a0ec3da283", + "0xa6cb4cd9481e22870fdd757fae0785edf4635e7aacb18072fe8dc5876d0bab53fb99ce40964a7d3e8bcfff6f0ab1332f", + "0xaf30ff47ecc5b543efba1ba4706921066ca8bb625f40e530fb668aea0551c7647a9d126e8aba282fbcce168c3e7e0130", + "0x91b0bcf408ce3c11555dcb80c4410b5bc2386d3c05caec0b653352377efdcb6bab4827f2018671fc8e4a0e90d772acc1", + "0xa9430b975ef138b6b2944c7baded8fe102d31da4cfe3bd3d8778bda79189c99d38176a19c848a19e2d1ee0bddd9a13c1", + "0xaa5a4eef849d7c9d2f4b018bd01271c1dd83f771de860c4261f385d3bdcc130218495860a1de298f14b703ec32fa235f", + "0xb0ce79e7f9ae57abe4ff366146c3b9bfb38b0dee09c28c28f5981a5d234c6810ad4d582751948affb480d6ae1c8c31c4", + "0xb75122748560f73d15c01a8907d36d06dc068e82ce22b84b322ac1f727034493572f7907dec34ebc3ddcc976f2f89ed7", + "0xb0fc7836369a3e4411d34792d6bd5617c14f61d9bba023dda64e89dc5fb0f423244e9b48ee64869258931daa9753a56f", + "0x8956d7455ae9009d70c6e4a0bcd7610e55f37494cf9897a8f9e1b904cc8febc3fd2d642ebd09025cfff4609ad7e3bc52", + "0xad741efe9e472026aa49ae3d9914cb9c1a6f37a54f1a6fe6419bebd8c7d68dca105a751c7859f4389505ede40a0de786", + "0xb52f418797d719f0d0d0ffb0846788b5cba5d0454a69a2925de4b0b80fa4dd7e8c445e5eac40afd92897ed28ca650566", + "0xa0ab65fb9d42dd966cd93b1de01d7c822694669dd2b7a0c04d99cd0f3c3de795f387b9c92da11353412f33af5c950e9a", + "0xa0052f44a31e5741a331f7cac515a08b3325666d388880162d9a7b97598fde8b61f9ff35ff220df224eb5c4e40ef0567", + "0xa0101cfdc94e42b2b976c0d89612a720e55d145a5ef6ef6f1f78cf6de084a49973d9b5d45915349c34ce712512191e3c", + "0xa0dd99fcf3f5cead5aaf08e82212df3a8bb543c407a4d6fab88dc5130c1769df3f147e934a46f291d6c1a55d92b86917", + "0xa5939153f0d1931bbda5cf6bdf20562519ea55fbfa978d6dbc6828d298260c0da7a50c37c34f386e59431301a96c2232", + "0x9568269f3f5257200f9ca44afe1174a5d3cf92950a7f553e50e279c239e156a9faaa2a67f288e3d5100b4142efe64856", + "0xb746b0832866c23288e07f24991bbf687cad794e7b794d3d3b79367566ca617d38af586cdc8d6f4a85a34835be41d54f", + "0xa871ce28e39ab467706e32fec1669fda5a4abba2f8c209c6745df9f7a0fa36bbf1919cf14cb89ea26fa214c4c907ae03", + "0xa08dacdd758e523cb8484f6bd070642c0c20e184abdf8e2a601f61507e93952d5b8b0c723c34fcbdd70a8485eec29db2", + "0x85bdb78d501382bb95f1166b8d032941005661aefd17a5ac32df9a3a18e9df2fc5dc2c1f07075f9641af10353cecc0c9", + "0x98d730c28f6fa692a389e97e368b58f4d95382fad8f0baa58e71a3d7baaea1988ead47b13742ce587456f083636fa98e", + "0xa557198c6f3d5382be9fb363feb02e2e243b0c3c61337b3f1801c4a0943f18e38ce1a1c36b5c289c8fa2aa9d58742bab", + "0x89174f79201742220ac689c403fc7b243eed4f8e3f2f8aba0bf183e6f5d4907cb55ade3e238e3623d9885f03155c4d2b", + "0xb891d600132a86709e06f3381158db300975f73ea4c1f7c100358e14e98c5fbe792a9af666b85c4e402707c3f2db321e", + "0xb9e5b2529ef1043278c939373fc0dbafe446def52ddd0a8edecd3e4b736de87e63e187df853c54c28d865de18a358bb6", + "0x8589b2e9770340c64679062c5badb7bbef68f55476289b19511a158a9a721f197da03ece3309e059fc4468b15ac33aa3", + "0xaad8c6cd01d785a881b446f06f1e9cd71bca74ba98674c2dcddc8af01c40aa7a6d469037498b5602e76e9c91a58d3dbd", + "0xabaccb1bd918a8465f1bf8dbe2c9ad4775c620b055550b949a399f30cf0d9eb909f3851f5b55e38f9e461e762f88f499", + "0xae62339d26db46e85f157c0151bd29916d5cc619bd4b832814b3fd2f00af8f38e7f0f09932ffe5bba692005dab2d9a74", + "0x93a6ff30a5c0edf8058c89aba8c3259e0f1b1be1b80e67682de651e5346f7e1b4b4ac3d87cbaebf198cf779524aff6bf", + "0x8980a2b1d8f574af45b459193c952400b10a86122b71fca2acb75ee0dbd492e7e1ef5b959baf609a5172115e371f3177", + "0x8c2f49f3666faee6940c75e8c7f6f8edc3f704cca7a858bbb7ee5e96bba3b0cf0993996f781ba6be3b0821ef4cb75039", + "0xb14b9e348215b278696018330f63c38db100b0542cfc5be11dc33046e3bca6a13034c4ae40d9cef9ea8b34fef0910c4e", + "0xb59bc3d0a30d66c16e6a411cb641f348cb1135186d5f69fda8b0a0934a5a2e7f6199095ba319ec87d3fe8f1ec4a06368", + "0x8874aca2a3767aa198e4c3fec2d9c62d496bc41ff71ce242e9e082b7f38cdf356089295f80a301a3cf1182bde5308c97", + "0xb1820ebd61376d91232423fc20bf008b2ba37e761199f4ef0648ea2bd70282766799b4de814846d2f4d516d525c8daa7", + "0xa6b202e5dedc16a4073e04a11af3a8509b23dfe5a1952f899adeb240e75c3f5bde0c424f811a81ea48d343591faffe46", + "0xa69becee9c93734805523b92150a59a62eed4934f66056b645728740d42223f2925a1ad38359ba644da24d9414f4cdda", + "0xad72f0f1305e37c7e6b48c272323ee883320994cb2e0d850905d6655fafc9f361389bcb9c66b3ff8d2051dbb58c8aa96", + "0xb563600bd56fad7c8853af21c6a02a16ed9d8a8bbeea2c31731d63b976d83cb05b9779372d898233e8fd597a75424797", + "0xb0abb78ce465bf7051f563c62e8be9c57a2cc997f47c82819300f36e301fefd908894bb2053a9d27ce2d0f8c46d88b5b", + "0xa071a85fb8274bac2202e0cb8e0e2028a5e138a82d6e0374d39ca1884a549c7c401312f00071b91f455c3a2afcfe0cda", + "0xb931c271513a0f267b9f41444a5650b1918100b8f1a64959c552aff4e2193cc1b9927906c6fa7b8a8c68ef13d79aaa52", + "0xa6a1bb9c7d32cb0ca44d8b75af7e40479fbce67d216b48a2bb680d3f3a772003a49d3cd675fc64e9e0f8fabeb86d6d61", + "0xb98d609858671543e1c3b8564162ad828808bb50ded261a9f8690ded5b665ed8368c58f947365ed6e84e5a12e27b423d", + "0xb3dca58cd69ec855e2701a1d66cad86717ff103ef862c490399c771ad28f675680f9500cb97be48de34bcdc1e4503ffd", + "0xb34867c6735d3c49865e246ddf6c3b33baf8e6f164db3406a64ebce4768cb46b0309635e11be985fee09ab7a31d81402", + "0xacb966c554188c5b266624208f31fab250b3aa197adbdd14aee5ab27d7fb886eb4350985c553b20fdf66d5d332bfd3fe", + "0x943c36a18223d6c870d54c3b051ef08d802b85e9dd6de37a51c932f90191890656c06adfa883c87b906557ae32d09da0", + "0x81bca7954d0b9b6c3d4528aadf83e4bc2ef9ea143d6209bc45ae9e7ae9787dbcd8333c41f12c0b6deee8dcb6805e826a", + "0xaba176b92256efb68f574e543479e5cf0376889fb48e3db4ebfb7cba91e4d9bcf19dcfec444c6622d9398f06de29e2b9", + "0xb9f743691448053216f6ece7cd699871fff4217a1409ceb8ab7bdf3312d11696d62c74b0664ba0a631b1e0237a8a0361", + "0xa383c2b6276fa9af346b21609326b53fb14fdf6f61676683076e80f375b603645f2051985706d0401e6fbed7eb0666b6", + "0xa9ef2f63ec6d9beb8f3d04e36807d84bda87bdd6b351a3e4a9bf7edcb5618c46c1f58cfbf89e64b40f550915c6988447", + "0xa141b2d7a82f5005eaea7ae7d112c6788b9b95121e5b70b7168d971812f3381de8b0082ac1f0a82c7d365922ebd2d26a", + "0xb1b76ef8120e66e1535c17038b75255a07849935d3128e3e99e56567b842fb1e8d56ef932d508d2fb18b82f7868fe1a9", + "0x8e2e234684c81f21099f5c54f6bbe2dd01e3b172623836c77668a0c49ce1fe218786c3827e4d9ae2ea25c50a8924fb3c", + "0xa5caf5ff948bfd3c4ca3ffbdfcd91eec83214a6c6017235f309a0bbf7061d3b0b466307c00b44a1009cf575163898b43", + "0x986415a82ca16ebb107b4c50b0c023c28714281db0bcdab589f6cb13d80e473a3034b7081b3c358e725833f6d845cb14", + "0xb94836bf406ac2cbacb10e6df5bcdfcc9d9124ae1062767ca4e322d287fd5e353fdcebd0e52407cb3cd68571258a8900", + "0x83c6d70a640b33087454a4788dfd9ef3ed00272da084a8d36be817296f71c086b23b576f98178ab8ca6a74f04524b46b", + "0xad4115182ad784cfe11bcfc5ce21fd56229cc2ce77ac82746e91a2f0aa53ca6593a22efd2dc4ed8d00f84542643d9c58", + "0xab1434c5e5065da826d10c2a2dba0facccab0e52b506ce0ce42fbe47ced5a741797151d9ecc99dc7d6373cfa1779bbf6", + "0x8a8b591d82358d55e6938f67ea87a89097ab5f5496f7260adb9f649abb289da12b498c5b2539c2f9614fb4e21b1f66b0", + "0x964f355d603264bc1f44c64d6d64debca66f37dff39c971d9fc924f2bc68e6c187b48564a6dc82660a98b035f8addb5d", + "0xb66235eaaf47456bc1dc4bde454a028e2ce494ece6b713a94cd6bf27cf18c717fd0c57a5681caaa2ad73a473593cdd7a", + "0x9103e3bb74304186fa4e3e355a02da77da4aca9b7e702982fc2082af67127ebb23a455098313c88465bc9b7d26820dd5", + "0xb6a42ff407c9dd132670cdb83cbad4b20871716e44133b59a932cd1c3f97c7ac8ff7f61acfaf8628372508d8dc8cad7c", + "0x883a9c21c16a167a4171b0f084565c13b6f28ba7c4977a0de69f0a25911f64099e7bbb4da8858f2e93068f4155d04e18", + "0x8dbb3220abc6a43220adf0331e3903d3bfd1d5213aadfbd8dfcdf4b2864ce2e96a71f35ecfb7a07c3bbabf0372b50271", + "0xb4ad08aee48e176bda390b7d9acf2f8d5eb008f30d20994707b757dc6a3974b2902d29cd9b4d85e032810ad25ac49e97", + "0x865bb0f33f7636ec501bb634e5b65751c8a230ae1fa807a961a8289bbf9c7fe8c59e01fbc4c04f8d59b7f539cf79ddd5", + "0x86a54d4c12ad1e3605b9f93d4a37082fd26e888d2329847d89afa7802e815f33f38185c5b7292293d788ad7d7da1df97", + "0xb26c8615c5e47691c9ff3deca3021714662d236c4d8401c5d27b50152ce7e566266b9d512d14eb63e65bc1d38a16f914", + "0x827639d5ce7db43ba40152c8a0eaad443af21dc92636cc8cc2b35f10647da7d475a1e408901cd220552fddad79db74df", + "0xa2b79a582191a85dbe22dc384c9ca3de345e69f6aa370aa6d3ff1e1c3de513e30b72df9555b15a46586bd27ea2854d9d", + "0xae0d74644aba9a49521d3e9553813bcb9e18f0b43515e4c74366e503c52f47236be92dfbd99c7285b3248c267b1de5a0", + "0x80fb0c116e0fd6822a04b9c25f456bdca704e2be7bdc5d141dbf5d1c5eeb0a2c4f5d80db583b03ef3e47517e4f9a1b10", + "0xac3a1fa3b4a2f30ea7e0a114cdc479eb51773573804c2a158d603ad9902ae8e39ffe95df09c0d871725a5d7f9ba71a57", + "0xb56b2b0d601cba7f817fa76102c68c2e518c6f20ff693aad3ff2e07d6c4c76203753f7f91686b1801e8c4659e4d45c48", + "0x89d50c1fc56e656fb9d3915964ebce703cb723fe411ab3c9eaa88ccc5d2b155a9b2e515363d9c600d3c0cee782c43f41", + "0xb24207e61462f6230f3cd8ccf6828357d03e725769f7d1de35099ef9ee4dca57dbce699bb49ed994462bee17059d25ce", + "0xb886f17fcbcbfcd08ac07f04bb9543ef58510189decaccea4b4158c9174a067cb67d14b6be3c934e6e2a18c77efa9c9c", + "0xb9c050ad9cafd41c6e2e192b70d080076eed59ed38ea19a12bd92fa17b5d8947d58d5546aaf5e8e27e1d3b5481a6ce51", + "0xaaf7a34d3267e3b1ddbc54c641e3922e89303f7c86ebebc7347ebca4cffad5b76117dac0cbae1a133053492799cd936f", + "0xa9ee604ada50adef82e29e893070649d2d4b7136cc24fa20e281ce1a07bd736bf0de7c420369676bcbcecff26fb6e900", + "0x9855315a12a4b4cf80ab90b8bd13003223ba25206e52fd4fe6a409232fbed938f30120a3db23eab9c53f308bd8b9db81", + "0x8cd488dd7a24f548a3cf03c54dec7ff61d0685cb0f6e5c46c2d728e3500d8c7bd6bba0156f4bf600466fda53e5b20444", + "0x890ad4942ebac8f5b16c777701ab80c68f56fa542002b0786f8fea0fb073154369920ac3dbfc07ea598b82f4985b8ced", + "0x8de0cf9ddc84c9b92c59b9b044387597799246b30b9f4d7626fc12c51f6e423e08ee4cbfe9289984983c1f9521c3e19d", + "0xb474dfb5b5f4231d7775b3c3a8744956b3f0c7a871d835d7e4fd9cc895222c7b868d6c6ce250de568a65851151fac860", + "0x86433b6135d9ed9b5ee8cb7a6c40e5c9d30a68774cec04988117302b8a02a11a71a1e03fd8e0264ef6611d219f103007", + "0x80b9ed4adbe9538fb1ef69dd44ec0ec5b57cbfea820054d8d445b4261962624b4c70ac330480594bc5168184378379c3", + "0x8b2e83562ccd23b7ad2d17f55b1ab7ef5fbef64b3a284e6725b800f3222b8bdf49937f4a873917ada9c4ddfb090938c2", + "0xabe78cebc0f5a45d754140d1f685e387489acbfa46d297a8592aaa0d676a470654f417a4f7d666fc0b2508fab37d908e", + "0xa9c5f8ff1f8568e252b06d10e1558326db9901840e6b3c26bbd0cd5e850cb5fb3af3f117dbb0f282740276f6fd84126f", + "0x975f8dc4fb55032a5df3b42b96c8c0ffecb75456f01d4aef66f973cb7270d4eff32c71520ceefc1adcf38d77b6b80c67", + "0xb043306ed2c3d8a5b9a056565afd8b5e354c8c4569fda66b0d797a50a3ce2c08cffbae9bbe292da69f39e89d5dc7911e", + "0x8d2afc36b1e44386ba350c14a6c1bb31ff6ea77128a0c5287584ac3584282d18516901ce402b4644a53db1ed8e7fa581", + "0x8c294058bed53d7290325c363fe243f6ec4f4ea2343692f4bac8f0cb86f115c069ccb8334b53d2e42c067691ad110dba", + "0xb92157b926751aaf7ef82c1aa8c654907dccab6376187ee8b3e8c0c82811eae01242832de953faa13ebaff7da8698b3e", + "0xa780c4bdd9e4ba57254b09d745075cecab87feda78c88ffee489625c5a3cf96aa6b3c9503a374a37927d9b78de9bd22b", + "0x811f548ef3a2e6a654f7dcb28ac9378de9515ed61e5a428515d9594a83e80b35c60f96a5cf743e6fab0d3cb526149f49", + "0x85a4dccf6d90ee8e094731eec53bd00b3887aec6bd81a0740efddf812fd35e3e4fe4f983afb49a8588691c202dabf942", + "0xb152c2da6f2e01c8913079ae2b40a09b1f361a80f5408a0237a8131b429677c3157295e11b365b1b1841924b9efb922e", + "0x849b9efee8742502ffd981c4517c88ed33e4dd518a330802caff168abae3cd09956a5ee5eda15900243bc2e829016b74", + "0x955a933f3c18ec0f1c0e38fa931e4427a5372c46a3906ebe95082bcf878c35246523c23f0266644ace1fa590ffa6d119", + "0x911989e9f43e580c886656377c6f856cdd4ff1bd001b6db3bbd86e590a821d34a5c6688a29b8d90f28680e9fdf03ba69", + "0xb73b8b4f1fd6049fb68d47cd96a18fcba3f716e0a1061aa5a2596302795354e0c39dea04d91d232aec86b0bf2ba10522", + "0x90f87456d9156e6a1f029a833bf3c7dbed98ca2f2f147a8564922c25ae197a55f7ea9b2ee1f81bf7383197c4bad2e20c", + "0x903cba8b1e088574cb04a05ca1899ab00d8960580c884bd3c8a4c98d680c2ad11410f2b75739d6050f91d7208cac33a5", + "0x9329987d42529c261bd15ecedd360be0ea8966e7838f32896522c965adfc4febf187db392bd441fb43bbd10c38fdf68b", + "0x8178ee93acf5353baa349285067b20e9bb41aa32d77b5aeb7384fe5220c1fe64a2461bd7a83142694fe673e8bbf61b7c", + "0xa06a8e53abcff271b1394bcc647440f81fb1c1a5f29c27a226e08f961c3353f4891620f2d59b9d1902bf2f5cc07a4553", + "0xaaf5fe493b337810889e777980e6bbea6cac39ac66bc0875c680c4208807ac866e9fda9b5952aa1d04539b9f4a4bec57", + "0xaa058abb1953eceac14ccfa7c0cc482a146e1232905dcecc86dd27f75575285f06bbae16a8c9fe8e35d8713717f5f19f", + "0x8f15dd732799c879ca46d2763453b359ff483ca33adb1d0e0a57262352e0476c235987dc3a8a243c74bc768f93d3014c", + "0xa61cc8263e9bc03cce985f1663b8a72928a607121005a301b28a278e9654727fd1b22bc8a949af73929c56d9d3d4a273", + "0x98d6dc78502d19eb9f921225475a6ebcc7b44f01a2df6f55ccf6908d65b27af1891be2a37735f0315b6e0f1576c1f8d8", + "0x8bd258b883f3b3793ec5be9472ad1ff3dc4b51bc5a58e9f944acfb927349ead8231a523cc2175c1f98e7e1e2b9f363b8", + "0xaeacc2ecb6e807ad09bedd99654b097a6f39840e932873ace02eabd64ccfbb475abdcb62939a698abf17572d2034c51e", + "0xb8ccf78c08ccd8df59fd6eda2e01de328bc6d8a65824d6f1fc0537654e9bc6bf6f89c422dd3a295cce628749da85c864", + "0x8f91fd8cb253ba2e71cc6f13da5e05f62c2c3b485c24f5d68397d04665673167fce1fc1aec6085c69e87e66ec555d3fd", + "0xa254baa10cb26d04136886073bb4c159af8a8532e3fd36b1e9c3a2e41b5b2b6a86c4ebc14dbe624ee07b7ccdaf59f9ab", + "0x94e3286fe5cd68c4c7b9a7d33ae3d714a7f265cf77cd0e9bc19fc51015b1d1c34ad7e3a5221c459e89f5a043ee84e3a9", + "0xa279da8878af8d449a9539bec4b17cea94f0242911f66fab275b5143ab040825f78c89cb32a793930609415cfa3a1078", + "0xac846ceb89c9e5d43a2991c8443079dc32298cd63e370e64149cec98cf48a6351c09c856f2632fd2f2b3d685a18bbf8b", + "0xa847b27995c8a2e2454aaeb983879fb5d3a23105c33175839f7300b7e1e8ec3efd6450e9fa3f10323609dee7b98c6fd5", + "0xa2f432d147d904d185ff4b2de8c6b82fbea278a2956bc406855b44c18041854c4f0ecccd472d1d0dff1d8aa8e281cb1d", + "0x94a48ad40326f95bd63dff4755f863a1b79e1df771a1173b17937f9baba57b39e651e7695be9f66a472f098b339364fc", + "0xa12a0ccd8f96e96e1bc6494341f7ebce959899341b3a084aa1aa87d1c0d489ac908552b7770b887bb47e7b8cbc3d8e66", + "0x81a1f1681bda923bd274bfe0fbb9181d6d164fe738e54e25e8d4849193d311e2c4253614ed673c98af2c798f19a93468", + "0xabf71106a05d501e84cc54610d349d7d5eae21a70bd0250f1bebbf412a130414d1c8dbe673ffdb80208fd72f1defa4d4", + "0x96266dc2e0df18d8136d79f5b59e489978eee0e6b04926687fe389d4293c14f36f055c550657a8e27be4118b64254901", + "0x8df5dcbefbfb4810ae3a413ca6b4bf08619ca53cd50eb1dde2a1c035efffc7b7ac7dff18d403253fd80104bd83dc029e", + "0x9610b87ff02e391a43324a7122736876d5b3af2a137d749c52f75d07b17f19900b151b7f439d564f4529e77aa057ad12", + "0xa90a5572198b40fe2fcf47c422274ff36c9624df7db7a89c0eb47eb48a73a03c985f4ac5016161c76ca317f64339bce1", + "0x98e5e61a6ab6462ba692124dba7794b6c6bde4249ab4fcc98c9edd631592d5bc2fb5e38466691a0970a38e48d87c2e43", + "0x918cefb8f292f78d4db81462c633daf73b395e772f47b3a7d2cea598025b1d8c3ec0cbff46cdb23597e74929981cde40", + "0xa98918a5dc7cf610fe55f725e4fd24ce581d594cb957bb9b4e888672e9c0137003e1041f83e3f1d7b9caab06462c87d4", + "0xb92b74ac015262ca66c33f2d950221e19d940ba3bf4cf17845f961dc1729ae227aa9e1f2017829f2135b489064565c29", + "0xa053ee339f359665feb178b4e7ee30a85df37debd17cacc5a27d6b3369d170b0114e67ad1712ed26d828f1df641bcd99", + "0x8c3c8bad510b35da5ce5bd84b35c958797fbea024ad1c97091d2ff71d9b962e9222f65a9b776e5b3cc29c36e1063d2ee", + "0xaf99dc7330fe7c37e850283eb47cc3257888e7c197cb0d102edf94439e1e02267b6a56306d246c326c4c79f9dc8c6986", + "0xafecb2dc34d57a725efbd7eb93d61eb29dbe8409b668ab9ea040791f5b796d9be6d4fc10d7f627bf693452f330cf0435", + "0x93334fedf19a3727a81a6b6f2459db859186227b96fe7a391263f69f1a0884e4235de64d29edebc7b99c44d19e7c7d7a", + "0x89579c51ac405ad7e9df13c904061670ce4b38372492764170e4d3d667ed52e5d15c7cd5c5991bbfa3a5e4e3fa16363e", + "0x9778f3e8639030f7ef1c344014f124e375acb8045bd13d8e97a92c5265c52de9d1ffebaa5bc3e1ad2719da0083222991", + "0x88f77f34ee92b3d36791bdf3326532524a67d544297dcf1a47ff00b47c1b8219ff11e34034eab7d23b507caa2fd3c6b9", + "0xa699c1e654e7c484431d81d90657892efeb4adcf72c43618e71ca7bd7c7a7ebbb1db7e06e75b75dc4c74efd306b5df3f", + "0x81d13153baebb2ef672b5bdb069d3cd669ce0be96b742c94e04038f689ff92a61376341366b286eee6bf3ae85156f694", + "0x81efb17de94400fdacc1deec2550cbe3eecb27c7af99d8207e2f9be397e26be24a40446d2a09536bb5172c28959318d9", + "0x989b21ebe9ceab02488992673dc071d4d5edec24bff0e17a4306c8cb4b3c83df53a2063d1827edd8ed16d6e837f0d222", + "0x8d6005d6536825661b13c5fdce177cb37c04e8b109b7eb2b6d82ea1cb70efecf6a0022b64f84d753d165edc2bba784a3", + "0xa32607360a71d5e34af2271211652d73d7756d393161f4cf0da000c2d66a84c6826e09e759bd787d4fd0305e2439d342", + "0xaaad8d6f6e260db45d51b2da723be6fa832e76f5fbcb77a9a31e7f090dd38446d3b631b96230d78208cae408c288ac4e", + "0xabcfe425255fd3c5cffd3a818af7650190c957b6b07b632443f9e33e970a8a4c3bf79ac9b71f4d45f238a04d1c049857", + "0xaeabf026d4c783adc4414b5923dbd0be4b039cc7201219f7260d321f55e9a5b166d7b5875af6129c034d0108fdc5d666", + "0xaf49e740c752d7b6f17048014851f437ffd17413c59797e5078eaaa36f73f0017c3e7da020310cfe7d3c85f94a99f203", + "0x8854ca600d842566e3090040cd66bb0b3c46dae6962a13946f0024c4a8aca447e2ccf6f240045f1ceee799a88cb9210c", + "0xb6c03b93b1ab1b88ded8edfa1b487a1ed8bdce8535244dddb558ffb78f89b1c74058f80f4db2320ad060d0c2a9c351cc", + "0xb5bd7d17372faff4898a7517009b61a7c8f6f0e7ed4192c555db264618e3f6e57fb30a472d169fea01bf2bf0362a19a8", + "0x96eb1d38319dc74afe7e7eb076fcd230d19983f645abd14a71e6103545c01301b31c47ae931e025f3ecc01fb3d2f31fa", + "0xb55a8d30d4403067def9b65e16f867299f8f64c9b391d0846d4780bc196569622e7e5b64ce799b5aefac8f965b2a7a7b", + "0x8356d199a991e5cbbff608752b6291731b6b6771aed292f8948b1f41c6543e4ab1bedc82dd26d10206c907c03508df06", + "0x97f4137445c2d98b0d1d478049de952610ad698c91c9d0f0e7227d2aae690e9935e914ec4a2ea1fbf3fc1dddfeeacebb", + "0xaf5621707e0938320b15ddfc87584ab325fbdfd85c30efea36f8f9bd0707d7ec12c344eff3ec21761189518d192df035", + "0x8ac7817e71ea0825b292687928e349da7140285d035e1e1abff0c3704fa8453faaae343a441b7143a74ec56539687cc4", + "0x8a5e0a9e4758449489df10f3386029ada828d1762e4fb0a8ffe6b79e5b6d5d713cb64ed95960e126398b0cdb89002bc9", + "0x81324be4a71208bbb9bca74b77177f8f1abb9d3d5d9db195d1854651f2cf333cd618d35400da0f060f3e1b025124e4b2", + "0x849971d9d095ae067525b3cbc4a7dfae81f739537ade6d6cec1b42fb692d923176197a8770907c58069754b8882822d6", + "0x89f830825416802477cc81fdf11084885865ee6607aa15aa4eb28e351c569c49b8a1b9b5e95ddc04fa0ebafe20071313", + "0x9240aeeaff37a91af55f860b9badd466e8243af9e8c96a7aa8cf348cd270685ab6301bc135b246dca9eda696f8b0e350", + "0xacf74db78cc33138273127599eba35b0fb4e7b9a69fe02dae18fc6692d748ca332bd00b22afa8e654ed587aab11833f3", + "0xb091e6d37b157b50d76bd297ad752220cd5c9390fac16dc838f8557aed6d9833fc920b61519df21265406216315e883f", + "0xa6446c429ebf1c7793c622250e23594c836b2fbcaf6c5b3d0995e1595a37f50ea643f3e549b0be8bbdadd69044d72ab9", + "0x93e675353bd60e996bf1c914d5267eeaa8a52fc3077987ccc796710ef9becc6b7a00e3d82671a6bdfb8145ee3c80245a", + "0xa2f731e43251d04ed3364aa2f072d05355f299626f2d71a8a38b6f76cf08c544133f7d72dd0ab4162814b674b9fc7fa6", + "0x97a8b791a5a8f6e1d0de192d78615d73d0c38f1e557e4e15d15adc663d649e655bc8da3bcc499ef70112eafe7fb45c7a", + "0x98cd624cbbd6c53a94469be4643c13130916b91143425bcb7d7028adbbfede38eff7a21092af43b12d4fab703c116359", + "0x995783ce38fd5f6f9433027f122d4cf1e1ff3caf2d196ce591877f4a544ce9113ead60de2de1827eaff4dd31a20d79a8", + "0x8cf251d6f5229183b7f3fe2f607a90b4e4b6f020fb4ba2459d28eb8872426e7be8761a93d5413640a661d73e34a5b81f", + "0xb9232d99620652a3aa7880cad0876f153ff881c4ed4c0c2e7b4ea81d5d42b70daf1a56b869d752c3743c6d4c947e6641", + "0x849716f938f9d37250cccb1bf77f5f9fde53096cdfc6f2a25536a6187029a8f1331cdbed08909184b201f8d9f04b792f", + "0x80c7c4de098cbf9c6d17b14eba1805e433b5bc905f6096f8f63d34b94734f2e4ebf4bce8a177efd1186842a61204a062", + "0xb790f410cf06b9b8daadceeb4fd5ff40a2deda820c8df2537e0a7554613ae3948e149504e3e79aa84889df50c8678eeb", + "0x813aab8bd000299cd37485b73cd7cba06e205f8efb87f1efc0bae8b70f6db2bc7702eb39510ad734854fb65515fe9d0f", + "0x94f0ab7388ac71cdb67f6b85dfd5945748afb2e5abb622f0b5ad104be1d4d0062b651f134ba22385c9e32c2dfdcccce1", + "0xab6223dca8bd6a4f969e21ccd9f8106fc5251d321f9e90cc42cea2424b3a9c4e5060a47eeef6b23c7976109b548498e8", + "0x859c56b71343fce4d5c5b87814c47bf55d581c50fd1871a17e77b5e1742f5af639d0e94d19d909ec7dfe27919e954e0c", + "0xaae0d632b6191b8ad71b027791735f1578e1b89890b6c22e37de0e4a6074886126988fe8319ae228ac9ef3b3bcccb730", + "0x8ca9f32a27a024c3d595ecfaf96b0461de57befa3b331ab71dc110ec3be5824fed783d9516597537683e77a11d334338", + "0xa061df379fb3f4b24816c9f6cd8a94ecb89b4c6dc6cd81e4b8096fa9784b7f97ab3540259d1de9c02eb91d9945af4823", + "0x998603102ac63001d63eb7347a4bb2bf4cf33b28079bb48a169076a65c20d511ccd3ef696d159e54cc8e772fb5d65d50", + "0x94444d96d39450872ac69e44088c252c71f46be8333a608a475147752dbb99db0e36acfc5198f158509401959c12b709", + "0xac1b51b6c09fe055c1d7c9176eea9adc33f710818c83a1fbfa073c8dc3a7eb3513cbdd3f5960b7845e31e3e83181e6ba", + "0x803d530523fc9e1e0f11040d2412d02baef3f07eeb9b177fa9bfa396af42eea898a4276d56e1db998dc96ae47b644cb2", + "0x85a3c9fc7638f5bf2c3e15ba8c2fa1ae87eb1ceb44c6598c67a2948667a9dfa41e61f66d535b4e7fda62f013a5a8b885", + "0xa961cf5654c46a1a22c29baf7a4e77837a26b7f138f410e9d1883480ed5fa42411d522aba32040b577046c11f007388e", + "0xad1154142344f494e3061ef45a34fab1aaacf5fdf7d1b26adbb5fbc3d795655fa743444e39d9a4119b4a4f82a6f30441", + "0xb1d6c30771130c77806e7ab893b73d4deb590b2ff8f2f8b5e54c2040c1f3e060e2bd99afc668cf706a2df666a508bbf6", + "0xa00361fd440f9decabd98d96c575cd251dc94c60611025095d1201ef2dedde51cb4de7c2ece47732e5ed9b3526c2012c", + "0xa85c5ab4d17d328bda5e6d839a9a6adcc92ff844ec25f84981e4f44a0e8419247c081530f8d9aa629c7eb4ca21affba6", + "0xa4ddd3eab4527a2672cf9463db38bc29f61460e2a162f426b7852b7a7645fbd62084fd39a8e4d60e1958cce436dd8f57", + "0x811648140080fe55b8618f4cf17f3c5a250adb0cd53d885f2ddba835d2b4433188e41fc0661faac88e4ff910b16278c0", + "0xb85c7f1cfb0ed29addccf7546023a79249e8f15ac2d14a20accbfef4dd9dc11355d599815fa09d2b6b4e966e6ea8cff1", + "0xa10b5d8c260b159043b020d5dd62b3467df2671afea6d480ca9087b7e60ed170c82b121819d088315902842d66c8fb45", + "0x917e191df1bcf3f5715419c1e2191da6b8680543b1ba41fe84ed07ef570376e072c081beb67b375fca3565a2565bcabb", + "0x881fd967407390bfd7badc9ab494e8a287559a01eb07861f527207c127eadea626e9bcc5aa9cca2c5112fbac3b3f0e9c", + "0x959fd71149af82cc733619e0e5bf71760ca2650448c82984b3db74030d0e10f8ab1ce1609a6de6f470fe8b5bd90df5b3", + "0xa3370898a1c5f33d15adb4238df9a6c945f18b9ada4ce2624fc32a844f9ece4c916a64e9442225b6592afa06d2e015f2", + "0x817efb8a791435e4236f7d7b278181a5fa34587578c629dbc14fbf9a5c26772290611395eecd20222a4c58649fc256d8", + "0xa04c9876acf2cfdc8ef96de4879742709270fa1d03fe4c8511fbef2d59eb0aaf0336fa2c7dfe41a651157377fa217813", + "0x81e15875d7ea7f123e418edf14099f2e109d4f3a6ce0eb65f67fe9fb10d2f809a864a29f60ad3fc949f89e2596b21783", + "0xb49f529975c09e436e6bc202fdc16e3fdcbe056db45178016ad6fdece9faad4446343e83aed096209690b21a6910724f", + "0x879e8eda589e1a279f7f49f6dd0580788c040d973748ec4942dbe51ea8fbd05983cc919b78f0c6b92ef3292ae29db875", + "0x81a2b74b2118923f34139a102f3d95e7eee11c4c2929c2576dee200a5abfd364606158535a6c9e4178a6a83dbb65f3c4", + "0x8913f281d8927f2b45fc815d0f7104631cb7f5f7278a316f1327d670d15868daadd2a64e3eb98e1f53fe7e300338cc80", + "0xa6f815fba7ef9af7fbf45f93bc952e8b351f5de6568a27c7c47a00cb39a254c6b31753794f67940fc7d2e9cc581529f4", + "0xb3722a15c66a0014ce4d082de118def8d39190c15678a472b846225585f3a83756ae1b255b2e3f86a26168878e4773b2", + "0x817ae61ab3d0dd5b6e24846b5a5364b1a7dc2e77432d9fed587727520ae2f307264ea0948c91ad29f0aea3a11ff38624", + "0xb3db467464415fcad36dc1de2d6ba7686772a577cc2619242ac040d6734881a45d3b40ed4588db124e4289cfeec4bbf6", + "0xad66a14f5a54ac69603b16e5f1529851183da77d3cc60867f10aea41339dd5e06a5257982e9e90a352cdd32750f42ee4", + "0xadafa3681ef45d685555601a25a55cf23358319a17f61e2179e704f63df83a73bdd298d12cf6cef86db89bd17119e11d", + "0xa379dc44cb6dd3b9d378c07b2ec654fec7ca2f272de6ba895e3d00d20c9e4c5550498a843c8ac67e4221db2115bedc1c", + "0xb7bf81c267a78efc6b9e5a904574445a6487678d7ef70054e3e93ea6a23f966c2b68787f9164918e3b16d2175459ed92", + "0xb41d66a13a4afafd5760062b77f79de7e6ab8ccacde9c6c5116a6d886912fb491dc027af435b1b44aacc6af7b3c887f2", + "0x9904d23a7c1c1d2e4bab85d69f283eb0a8e26d46e8b7b30224438015c936729b2f0af7c7c54c03509bb0500acb42d8a4", + "0xae30d65e9e20c3bfd603994ae2b175ff691d51f3e24b2d058b3b8556d12ca4c75087809062dddd4aaac81c94d15d8a17", + "0x9245162fab42ac01527424f6013310c3eb462982518debef6c127f46ba8a06c705d7dc9f0a41e796ba8d35d60ae6cc64", + "0x87fab853638d7a29a20f3ba2b1a7919d023e9415bfa78ebb27973d8cbc7626f584dc5665d2e7ad71f1d760eba9700d88", + "0x85aac46ecd330608e5272430970e6081ff02a571e8ea444f1e11785ea798769634a22a142d0237f67b75369d3c484a8a", + "0x938c85ab14894cc5dfce3d80456f189a2e98eddbc8828f4ff6b1df1dcb7b42b17ca2ff40226a8a1390a95d63dca698dd", + "0xa18ce1f846e3e3c4d846822f60271eecf0f5d7d9f986385ac53c5ace9589dc7c0188910448c19b91341a1ef556652fa9", + "0x8611608a9d844f0e9d7584ad6ccf62a5087a64f764caf108db648a776b5390feb51e5120f0ef0e9e11301af3987dd7dc", + "0x8106333ba4b4de8d1ae43bc9735d3fea047392e88efd6a2fa6f7b924a18a7a265ca6123c3edc0f36307dd7fb7fe89257", + "0xa91426fa500951ff1b051a248c050b7139ca30dde8768690432d597d2b3c4357b11a577be6b455a1c5d145264dcf81fc", + "0xb7f9f90e0e450f37b081297f7f651bad0496a8b9afd2a4cf4120a2671aaaa8536dce1af301258bfbfdb122afa44c5048", + "0x84126da6435699b0c09fa4032dec73d1fca21d2d19f5214e8b0bea43267e9a8dd1fc44f8132d8315e734c8e2e04d7291", + "0xaff064708103884cb4f1a3c1718b3fc40a238d35cf0a7dc24bdf9823693b407c70da50df585bf5bc4e9c07d1c2d203e8", + "0xa8b40fc6533752983a5329c31d376c7a5c13ce6879cc7faee648200075d9cd273537001fb4c86e8576350eaac6ba60c2", + "0xa02db682bdc117a84dcb9312eb28fcbde12d49f4ce915cc92c610bb6965ec3cc38290f8c5b5ec70afe153956692cda95", + "0x86decd22b25d300508472c9ce75d3e465b737e7ce13bc0fcce32835e54646fe12322ba5bc457be18bfd926a1a6ca4a38", + "0xa18666ef65b8c2904fd598791f5627207165315a85ee01d5fb0e6b2e10bdd9b00babc447da5bd63445e3337de33b9b89", + "0x89bb0c06effadefdaf34ffe4b123e1678a90d4451ee856c863df1e752eef41fd984689ded8f0f878bf8916d5dd8e8024", + "0x97cfcba08ebec05d0073992a66b1d7d6fb9d95871f2cdc36db301f78bf8069294d1c259efef5c93d20dc937eedae3a1a", + "0xac2643b14ece79dcb2e289c96776a47e2bebd40dd6dc74fd035df5bb727b5596f40e3dd2d2202141e69b0993717ede09", + "0xa5e6fd88a2f9174d9bd4c6a55d9c30974be414992f22aa852f552c7648f722ed8077acf5aba030abd47939bb451b2c60", + "0x8ad40a612824a7994487731a40b311b7349038c841145865539c6ada75c56de6ac547a1c23df190e0caaafecddd80ccc", + "0x953a7cea1d857e09202c438c6108060961f195f88c32f0e012236d7a4b39d840c61b162ec86436e8c38567328bea0246", + "0x80d8b47a46dae1868a7b8ccfe7029445bbe1009dad4a6c31f9ef081be32e8e1ac1178c3c8fb68d3e536c84990cc035b1", + "0x81ecd99f22b3766ce0aca08a0a9191793f68c754fdec78b82a4c3bdc2db122bbb9ebfd02fc2dcc6e1567a7d42d0cc16a", + "0xb1dd0446bccc25846fb95d08c1c9cc52fb51c72c4c5d169ffde56ecfe800f108dc1106d65d5c5bd1087c656de3940b63", + "0xb87547f0931e164e96de5c550ca5aa81273648fe34f6e193cd9d69cf729cb432e17aa02e25b1c27a8a0d20a3b795e94e", + "0x820a94e69a927e077082aae66f6b292cfbe4589d932edf9e68e268c9bd3d71ef76cf7d169dd445b93967c25db11f58f1", + "0xb0d07ddf2595270c39adfa0c8cf2ab1322979b0546aa4d918f641be53cd97f36c879bb75d205e457c011aca3bbd9f731", + "0x8700b876b35b4b10a8a9372c5230acecd39539c1bb87515640293ad4464a9e02929d7d6a6a11112e8a29564815ac0de4", + "0xa61a601c5bb27dcb97e37c8e2b9ce479c6b192a5e04d9ed5e065833c5a1017ee5f237b77d1a17be5d48f8e7cc0bcacf6", + "0x92fb88fe774c1ba1d4a08cae3c0e05467ad610e7a3f1d2423fd47751759235fe0a3036db4095bd6404716aa03820f484", + "0xb274f140d77a3ce0796f5e09094b516537ccaf27ae1907099bff172e6368ba85e7c3ef8ea2a07457cac48ae334da95b3", + "0xb2292d9181f16581a9a9142490b2bdcdfb218ca6315d1effc8592100d792eb89d5356996c890441f04f2b4a95763503e", + "0x8897e73f576d86bc354baa3bd96e553107c48cf5889dcc23c5ba68ab8bcd4e81f27767be2233fdfa13d39f885087e668", + "0xa29eac6f0829791c728d71abc49569df95a4446ecbfc534b39f24f56c88fe70301838dfc1c19751e7f3c5c1b8c6af6a0", + "0x9346dc3720adc5df500a8df27fd9c75ef38dc5c8f4e8ed66983304750e66d502c3c59b8e955be781b670a0afc70a2167", + "0x9566d534e0e30a5c5f1428665590617e95fd05d45f573715f58157854ad596ece3a3cfec61356aee342308d623e029d5", + "0xa464fb8bffe6bd65f71938c1715c6e296cc6d0311a83858e4e7eb5873b7f2cf0c584d2101e3407b85b64ca78b2ac93ce", + "0xb54088f7217987c87e9498a747569ac5b2f8afd5348f9c45bf3fd9fbf713a20f495f49c8572d087efe778ac7313ad6d3", + "0x91fa9f5f8000fe050f5b224d90b59fcce13c77e903cbf98ded752e5b3db16adb2bc1f8c94be48b69f65f1f1ad81d6264", + "0x92d04a5b0ac5d8c8e313709b432c9434ecd3e73231f01e9b4e7952b87df60cbfa97b5dedd2200bd033b4b9ea8ba45cc1", + "0xa94b90ad3c3d6c4bbe169f8661a790c40645b40f0a9d1c7220f01cf7fc176e04d80bab0ced9323fcafb93643f12b2760", + "0x94d86149b9c8443b46196f7e5a3738206dd6f3be7762df488bcbb9f9ee285a64c997ed875b7b16b26604fa59020a8199", + "0x82efe4ae2c50a2d7645240c173a047f238536598c04a2c0b69c96e96bd18e075a99110f1206bc213f39edca42ba00cc1", + "0xab8667685f831bc14d4610f84a5da27b4ea5b133b4d991741a9e64dceb22cb64a3ce8f1b6e101d52af6296df7127c9ad", + "0x83ba433661c05dcc5d562f4a9a261c8110dac44b8d833ae1514b1fc60d8b4ee395b18804baea04cb10adb428faf713c3", + "0xb5748f6f660cc5277f1211d2b8649493ed8a11085b871cd33a5aea630abd960a740f08c08be5f9c21574600ac9bf5737", + "0xa5c8dd12af48fb710642ad65ebb97ca489e8206741807f7acfc334f8035d3c80593b1ff2090c9bb7bd138f0c48714ca8", + "0xa2b382fd5744e3babf454b1d806cc8783efeb4761bc42b6914ea48a46a2eae835efbe0a18262b6bc034379e03cf1262b", + "0xb3145ffaf603f69f15a64936d32e3219eea5ed49fdfd2f5bf40ea0dfd974b36fb6ff12164d4c2282d892db4cf3ff3ce1", + "0x87a316fb213f4c5e30c5e3face049db66be4f28821bd96034714ec23d3e97849d7b301930f90a4323c7ccf53de23050c", + "0xb9de09a919455070fed6220fc179c8b7a4c753062bcd27acf28f5b9947a659c0b364298daf7c85c4ca6fca7f945add1f", + "0x806fbd98d411b76979464c40ad88bc07a151628a27fcc1012ba1dfbaf5b5cc9d962fb9b3386008978a12515edce934bc", + "0xa15268877fae0d21610ae6a31061ed7c20814723385955fac09fdc9693a94c33dea11db98bb89fdfe68f933490f5c381", + "0x8d633fb0c4da86b2e0b37d8fad5972d62bff2ac663c5ec815d095cd4b7e1fe66ebef2a2590995b57eaf941983c7ad7a4", + "0x8139e5dd9cf405e8ef65f11164f0440827d98389ce1b418b0c9628be983a9ddd6cf4863036ccb1483b40b8a527acd9ed", + "0x88b15fa94a08eac291d2b94a2b30eb851ff24addf2cc30b678e72e32cfcb3424cf4b33aa395d741803f3e578ddf524de", + "0xb5eaf0c8506e101f1646bcf049ee38d99ea1c60169730da893fd6020fd00a289eb2f415947e44677af49e43454a7b1be", + "0x8489822ad0647a7e06aa2aa5595960811858ddd4542acca419dd2308a8c5477648f4dd969a6740bb78aa26db9bfcc555", + "0xb1e9a7b9f3423c220330d45f69e45fa03d7671897cf077f913c252e3e99c7b1b1cf6d30caad65e4228d5d7b80eb86e5e", + "0xb28fe9629592b9e6a55a1406903be76250b1c50c65296c10c5e48c64b539fb08fe11f68cf462a6edcbba71b0cee3feb2", + "0xa41acf96a02c96cd8744ff6577c244fc923810d17ade133587e4c223beb7b4d99fa56eae311a500d7151979267d0895c", + "0x880798938fe4ba70721be90e666dfb62fcab4f3556fdb7b0dc8ec5bc34f6b4513df965eae78527136eb391889fe2caf9", + "0x98d4d89d358e0fb7e212498c73447d94a83c1b66e98fc81427ab13acddb17a20f52308983f3a5a8e0aaacec432359604", + "0x81430b6d2998fc78ba937a1639c6020199c52da499f68109da227882dc26d005b73d54c5bdcac1a04e8356a8ca0f7017", + "0xa8d906a4786455eb74613aba4ce1c963c60095ffb8658d368df9266fdd01e30269ce10bf984e7465f34b4fd83beba26a", + "0xaf54167ac1f954d10131d44a8e0045df00d581dd9e93596a28d157543fbe5fb25d213806ed7fb3cba6b8f5b5423562db", + "0x8511e373a978a12d81266b9afbd55035d7bc736835cfa921903a92969eeba3624437d1346b55382e61415726ab84a448", + "0x8cf43eea93508ae586fa9a0f1354a1e16af659782479c2040874a46317f9e8d572a23238efa318fdfb87cc63932602b7", + "0xb0bdd3bacff077173d302e3a9678d1d37936188c7ecc34950185af6b462b7c679815176f3cce5db19aac8b282f2d60ad", + "0xa355e9b87f2f2672052f5d4d65b8c1c827d24d89b0d8594641fccfb69aef1b94009105f3242058bb31c8bf51caae5a41", + "0xb8baa9e4b950b72ff6b88a6509e8ed1304bc6fd955748b2e59a523a1e0c5e99f52aec3da7fa9ff407a7adf259652466c", + "0x840bc3dbb300ea6f27d1d6dd861f15680bd098be5174f45d6b75b094d0635aced539fa03ddbccb453879de77fb5d1fe9", + "0xb4bc7e7e30686303856472bae07e581a0c0bfc815657c479f9f5931cff208d5c12930d2fd1ff413ebd8424bcd7a9b571", + "0x89b5d514155d7999408334a50822508b9d689add55d44a240ff2bdde2eee419d117031f85e924e2a2c1ca77db9b91eea", + "0xa8604b6196f87a04e1350302e8aa745bba8dc162115d22657b37a1d1a98cb14876ddf7f65840b5dbd77e80cd22b4256c", + "0x83cb7acdb9e03247515bb2ce0227486ccf803426717a14510f0d59d45e998b245797d356f10abca94f7a14e1a2f0d552", + "0xaeb3266a9f16649210ab2df0e1908ac259f34ce1f01162c22b56cf1019096ee4ea5854c36e30bb2feb06c21a71e8a45c", + "0x89e72e86edf2aa032a0fc9acf4d876a40865fbb2c8f87cb7e4d88856295c4ac14583e874142fd0c314a49aba68c0aa3c", + "0x8c3576eba0583c2a7884976b4ed11fe1fda4f6c32f6385d96c47b0e776afa287503b397fa516a455b4b8c3afeedc76db", + "0xa31e5b633bda9ffa174654fee98b5d5930a691c3c42fcf55673d927dbc8d91c58c4e42e615353145431baa646e8bbb30", + "0x89f2f3f7a8da1544f24682f41c68114a8f78c86bd36b066e27da13acb70f18d9f548773a16bd8e24789420e17183f137", + "0xada27fa4e90a086240c9164544d2528621a415a5497badb79f8019dc3dce4d12eb6b599597e47ec6ac39c81efda43520", + "0x90dc1eb21bf21c0187f359566fc4bf5386abea52799306a0e5a1151c0817c5f5bc60c86e76b1929c092c0f3ff48cedd2", + "0xb702a53ebcc17ae35d2e735a347d2c700e9cbef8eadbece33cac83df483b2054c126593e1f462cfc00a3ce9d737e2af5", + "0x9891b06455ec925a6f8eafffba05af6a38cc5e193acaaf74ffbf199df912c5197106c5e06d72942bbb032ce277b6417f", + "0x8c0ee71eb01197b019275bcf96cae94e81d2cdc3115dbf2d8e3080074260318bc9303597e8f72b18f965ad601d31ec43", + "0x8aaf580aaf75c1b7a5f99ccf60503506e62058ef43b28b02f79b8536a96be3f019c9f71caf327b4e6730134730d1bef5", + "0xae6f9fc21dd7dfa672b25a87eb0a41644f7609fab5026d5cedb6e43a06dbbfd6d6e30322a2598c8dedde88c52eaed626", + "0x8159b953ffece5693edadb2e906ebf76ff080ee1ad22698950d2d3bfc36ac5ea78f58284b2ca180664452d55bd54716c", + "0xab7647c32ca5e9856ac283a2f86768d68de75ceeba9e58b74c5324f8298319e52183739aba4340be901699d66ac9eb3f", + "0xa4d85a5701d89bcfaf1572db83258d86a1a0717603d6f24ac2963ffcf80f1265e5ab376a4529ca504f4396498791253c", + "0x816080c0cdbfe61b4d726c305747a9eb58ac26d9a35f501dd32ba43c098082d20faf3ccd41aad24600aa73bfa453dfac", + "0x84f3afac024f576b0fd9acc6f2349c2fcefc3f77dbe5a2d4964d14b861b88e9b1810334b908cf3427d9b67a8aee74b18", + "0x94b390655557b1a09110018e9b5a14490681ade275bdc83510b6465a1218465260d9a7e2a6e4ec700f58c31dc3659962", + "0xa8c66826b1c04a2dd4c682543242e7a57acae37278bd09888a3d17747c5b5fec43548101e6f46d703638337e2fd3277b", + "0x86e6f4608a00007fa533c36a5b054c5768ccafe41ad52521d772dcae4c8a4bcaff8f7609be30d8fab62c5988cbbb6830", + "0x837da4cf09ae8aa0bceb16f8b3bfcc3b3367aecac9eed6b4b56d7b65f55981ef066490764fb4c108792623ecf8cad383", + "0x941ff3011462f9b5bf97d8cbdb0b6f5d37a1b1295b622f5485b7d69f2cb2bcabc83630dae427f0259d0d9539a77d8424", + "0xb99e5d6d82aa9cf7d5970e7f710f4039ac32c2077530e4c2779250c6b9b373bc380adb0a03b892b652f649720672fc8c", + "0xa791c78464b2d65a15440b699e1e30ebd08501d6f2720adbc8255d989a82fcded2f79819b5f8f201bed84a255211b141", + "0x84af7ad4a0e31fcbb3276ab1ad6171429cf39adcf78dc03750dc5deaa46536d15591e26d53e953dfb31e1622bc0743ab", + "0xa833e62fe97e1086fae1d4917fbaf09c345feb6bf1975b5cb863d8b66e8d621c7989ab3dbecda36bc9eaffc5eaa6fa66", + "0xb4ef79a46a2126f53e2ebe62770feb57fd94600be29459d70a77c5e9cc260fa892be06cd60f886bf48459e48eb50d063", + "0xb43b8f61919ea380bf151c294e54d3a3ff98e20d1ee5efbfe38aa2b66fafbc6a49739793bd5cb1c809f8b30466277c3a", + "0xab37735af2412d2550e62df9d8b3b5e6f467f20de3890bf56faf1abf2bf3bd1d98dc3fa0ad5e7ab3fce0fa20409eb392", + "0x82416b74b1551d484250d85bb151fabb67e29cce93d516125533df585bc80779ab057ea6992801a3d7d5c6dcff87a018", + "0x8145d0787f0e3b5325190ae10c1d6bee713e6765fb6a0e9214132c6f78f4582bb2771aaeae40d3dad4bafb56bf7e36d8", + "0xb6935886349ecbdd5774e12196f4275c97ec8279fdf28ccf940f6a022ebb6de8e97d6d2173c3fe402cbe9643bed3883b", + "0x87ef9b4d3dc71ac86369f8ed17e0dd3b91d16d14ae694bc21a35b5ae37211b043d0e36d8ff07dcc513fb9e6481a1f37f", + "0xae1d0ded32f7e6f1dc8fef495879c1d9e01826f449f903c1e5034aeeabc5479a9e323b162b688317d46d35a42d570d86", + "0xa40d16497004db4104c6794e2f4428d75bdf70352685944f3fbe17526df333e46a4ca6de55a4a48c02ecf0bde8ba03c0", + "0x8d45121efba8cc308a498e8ee39ea6fa5cae9fb2e4aab1c2ff9d448aa8494ccbec9a078f978a86fcd97b5d5e7be7522a", + "0xa8173865c64634ba4ac2fa432740f5c05056a9deaf6427cb9b4b8da94ca5ddbc8c0c5d3185a89b8b28878194de9cdfcd", + "0xb6ec06a74d690f6545f0f0efba236e63d1fdfba54639ca2617408e185177ece28901c457d02b849fd00f1a53ae319d0a", + "0xb69a12df293c014a40070e3e760169b6f3c627caf9e50b35a93f11ecf8df98b2bc481b410eecb7ab210bf213bbe944de", + "0x97e7dc121795a533d4224803e591eef3e9008bab16f12472210b73aaf77890cf6e3877e0139403a0d3003c12c8f45636", + "0xacdfa6fdd4a5acb7738cc8768f7cba84dbb95c639399b291ae8e4e63df37d2d4096900a84d2f0606bf534a9ccaa4993f", + "0x86ee253f3a9446a33e4d1169719b7d513c6b50730988415382faaf751988c10a421020609f7bcdef91be136704b906e2", + "0xaac9438382a856caf84c5a8a234282f71b5fc5f65219103b147e7e6cf565522285fbfd7417b513bdad8277a00f652ca1", + "0x83f3799d8e5772527930f5dc071a2e0a65471618993ec8990a96ccdeee65270e490bda9d26bb877612475268711ffd80", + "0x93f28a81ac8c0ec9450b9d762fae9c7f8feaace87a6ee6bd141ef1d2d0697ef1bbd159fe6e1de640dbdab2b0361fca8a", + "0xa0825c95ba69999b90eac3a31a3fd830ea4f4b2b7409bde5f202b61d741d6326852ce790f41de5cb0eccec7af4db30c1", + "0x83924b0e66233edd603c3b813d698daa05751fc34367120e3cf384ea7432e256ccee4d4daf13858950549d75a377107d", + "0x956fd9fa58345277e06ba2ec72f49ed230b8d3d4ff658555c52d6cddeb84dd4e36f1a614f5242d5ca0192e8daf0543c2", + "0x944869912476baae0b114cced4ff65c0e4c90136f73ece5656460626599051b78802df67d7201c55d52725a97f5f29fe", + "0x865cb25b64b4531fb6fe4814d7c8cd26b017a6c6b72232ff53defc18a80fe3b39511b23f9e4c6c7249d06e03b2282ed2", + "0x81e09ff55214960775e1e7f2758b9a6c4e4cd39edf7ec1adfaad51c52141182b79fe2176b23ddc7df9fd153e5f82d668", + "0xb31006896f02bc90641121083f43c3172b1039334501fbaf1672f7bf5d174ddd185f945adf1a9c6cf77be34c5501483d", + "0x88b92f6f42ae45e9f05b16e52852826e933efd0c68b0f2418ac90957fd018df661bc47c8d43c2a7d7bfcf669dab98c3c", + "0x92fc68f595853ee8683930751789b799f397135d002eda244fe63ecef2754e15849edde3ba2f0cc8b865c9777230b712", + "0x99ca06a49c5cd0bb097c447793fcdd809869b216a34c66c78c7e41e8c22f05d09168d46b8b1f3390db9452d91bc96dea", + "0xb48b9490a5d65296802431852d548d81047bbefc74fa7dc1d4e2a2878faacdfcb365ae59209cb0ade01901a283cbd15d", + "0xaff0fdbef7c188b120a02bc9085d7b808e88f73973773fef54707bf2cd772cd066740b1b6f4127b5c349f657bd97e738", + "0x966fd4463b4f43dd8ccba7ad50baa42292f9f8b2e70da23bb6780e14155d9346e275ef03ddaf79e47020dcf43f3738bd", + "0x9330c3e1fadd9e08ac85f4839121ae20bbeb0a5103d84fa5aadbd1213805bdcda67bf2fb75fc301349cbc851b5559d20", + "0x993bb99867bd9041a71a55ad5d397755cfa7ab6a4618fc526179bfc10b7dc8b26e4372fe9a9b4a15d64f2b63c1052dda", + "0xa29b59bcfab51f9b3c490a3b96f0bf1934265c315349b236012adbd64a56d7f6941b2c8cc272b412044bc7731f71e1dc", + "0xa65c9cefe1fc35d089fe8580c2e7671ebefdb43014ac291528ff4deefd4883fd4df274af83711dad610dad0d615f9d65", + "0x944c78c56fb227ae632805d448ca3884cd3d2a89181cead3d2b7835e63297e6d740aa79a112edb1d4727824991636df5", + "0xa73d782da1db7e4e65d7b26717a76e16dd9fab4df65063310b8e917dc0bc24e0d6755df5546c58504d04d9e68c3b474a", + "0xaf80f0b87811ae3124f68108b4ca1937009403f87928bbc53480e7c5408d072053ace5eeaf5a5aba814dab8a45502085", + "0x88aaf1acfc6e2e19b8387c97da707cb171c69812fefdd4650468e9b2c627bd5ccfb459f4d8e56bdfd84b09ddf87e128f", + "0x92c97276ff6f72bab6e9423d02ad6dc127962dbce15a0dd1e4a393b4510c555df6aa27be0f697c0d847033a9ca8b8dfd", + "0xa0e07d43d96e2d85b6276b3c60aadb48f0aedf2de8c415756dc597249ea64d2093731d8735231dadc961e5682ac59479", + "0xadc9e6718a8f9298957d1da3842a7751c5399bbdf56f8de6c1c4bc39428f4aee6f1ba6613d37bf46b9403345e9d6fc81", + "0x951da434da4b20d949b509ceeba02e24da7ed2da964c2fcdf426ec787779c696b385822c7dbea4df3e4a35921f1e912c", + "0xa04cbce0d2b2e87bbf038c798a12ec828423ca6aca08dc8d481cf6466e3c9c73d4d4a7fa47df9a7e2e15aae9e9f67208", + "0x8f855cca2e440d248121c0469de1f94c2a71b8ee2682bbad3a78243a9e03da31d1925e6760dbc48a1957e040fae9abe8", + "0xb642e5b17c1df4a4e101772d73851180b3a92e9e8b26c918050f51e6dd3592f102d20b0a1e96f0e25752c292f4c903ff", + "0xa92454c300781f8ae1766dbbb50a96192da7d48ef4cbdd72dd8cbb44c6eb5913c112cc38e9144615fdc03684deb99420", + "0x8b74f7e6c2304f8e780df4649ef8221795dfe85fdbdaa477a1542d135b75c8be45bf89adbbb6f3ddf54ca40f02e733e9", + "0x85cf66292cbb30cec5fd835ab10c9fcb3aea95e093aebf123e9a83c26f322d76ebc89c4e914524f6c5f6ee7d74fc917d", + "0xae0bfe0cdc97c09542a7431820015f2d16067b30dca56288013876025e81daa8c519e5e347268e19aa1a85fa1dc28793", + "0x921322fc6a47dc091afa0ad6df18ed14cde38e48c6e71550aa513918b056044983aee402de21051235eecf4ce8040fbe", + "0x96c030381e97050a45a318d307dcb3c8377b79b4dd5daf6337cded114de26eb725c14171b9b8e1b3c08fe1f5ea6b49e0", + "0x90c23b86b6111818c8baaf53a13eaee1c89203b50e7f9a994bf0edf851919b48edbac7ceef14ac9414cf70c486174a77", + "0x8bf6c301240d2d1c8d84c71d33a6dfc6d9e8f1cfae66d4d0f7a256d98ae12b0bcebfa94a667735ee89f810bcd7170cff", + "0xa41a4ffbbea0e36874d65c009ee4c3feffff322f6fc0e30d26ee4dbc1f46040d05e25d9d0ecb378cef0d24a7c2c4b850", + "0xa8d4cdd423986bb392a0a92c12a8bd4da3437eec6ef6af34cf5310944899287452a2eb92eb5386086d5063381189d10e", + "0xa81dd26ec057c4032a4ed7ad54d926165273ed51d09a1267b2e477535cf6966835a257c209e4e92d165d74fa75695fa3", + "0x8d7f708c3ee8449515d94fc26b547303b53d8dd55f177bc3b25d3da2768accd9bc8e9f09546090ebb7f15c66e6c9c723", + "0x839ba65cffcd24cfffa7ab3b21faabe3c66d4c06324f07b2729c92f15cad34e474b0f0ddb16cd652870b26a756b731d3", + "0x87f1a3968afec354d92d77e2726b702847c6afcabb8438634f9c6f7766de4c1504317dc4fa9a4a735acdbf985e119564", + "0x91a8a7fd6542f3e0673f07f510d850864b34ac087eb7eef8845a1d14b2b1b651cbdc27fa4049bdbf3fea54221c5c8549", + "0xaef3cf5f5e3a2385ead115728d7059e622146c3457d266c612e778324b6e06fbfb8f98e076624d2f3ce1035d65389a07", + "0x819915d6232e95ccd7693fdd78d00492299b1983bc8f96a08dcb50f9c0a813ed93ae53c0238345d5bea0beda2855a913", + "0x8e9ba68ded0e94935131b392b28218315a185f63bf5e3c1a9a9dd470944509ca0ba8f6122265f8da851b5cc2abce68f1", + "0xb28468e9b04ee9d69003399a3cf4457c9bf9d59f36ab6ceeb8e964672433d06b58beeea198fedc7edbaa1948577e9fa2", + "0xa633005e2c9f2fd94c8bce2dd5bb708fe946b25f1ec561ae65e54e15cdd88dc339f1a083e01f0d39610c8fe24151aaf0", + "0x841d0031e22723f9328dd993805abd13e0c99b0f59435d2426246996b08d00ce73ab906f66c4eab423473b409e972ce0", + "0x85758d1b084263992070ec8943f33073a2d9b86a8606672550c17545507a5b3c88d87382b41916a87ee96ff55a7aa535", + "0x8581b06b0fc41466ef94a76a1d9fb8ae0edca6d018063acf6a8ca5f4b02d76021902feba58972415691b4bdbc33ae3b4", + "0x83539597ff5e327357ee62bc6bf8c0bcaec2f227c55c7c385a4806f0d37fb461f1690bad5066b8a5370950af32fafbef", + "0xaee3557290d2dc10827e4791d00e0259006911f3f3fce4179ed3c514b779160613eca70f720bff7804752715a1266ffa", + "0xb48d2f0c4e90fc307d5995464e3f611a9b0ef5fe426a289071f4168ed5cc4f8770c9332960c2ca5c8c427f40e6bb389f", + "0x847af8973b4e300bb06be69b71b96183fd1a0b9d51b91701bef6fcfde465068f1eb2b1503b07afda380f18d69de5c9e1", + "0xa70a6a80ce407f07804c0051ac21dc24d794b387be94eb24e1db94b58a78e1bcfb48cd0006db8fc1f9bedaece7a44fbe", + "0xb40e942b8fa5336910ff0098347df716bff9d1fa236a1950c16eeb966b3bc1a50b8f7b0980469d42e75ae13ced53cead", + "0xb208fabaa742d7db3148515330eb7a3577487845abdb7bd9ed169d0e081db0a5816595c33d375e56aeac5b51e60e49d3", + "0xb7c8194b30d3d6ef5ab66ec88ad7ebbc732a3b8a41731b153e6f63759a93f3f4a537eab9ad369705bd730184bdbbdc34", + "0x9280096445fe7394d04aa1bc4620c8f9296e991cc4d6c131bd703cb1cc317510e6e5855ac763f4d958c5edfe7eebeed7", + "0xabc2aa4616a521400af1a12440dc544e3c821313d0ab936c86af28468ef8bbe534837e364598396a81cf8d06274ed5a6", + "0xb18ca8a3325adb0c8c18a666d4859535397a1c3fe08f95eebfac916a7a99bbd40b3c37b919e8a8ae91da38bc00fa56c0", + "0x8a40c33109ecea2a8b3558565877082f79121a432c45ec2c5a5e0ec4d1c203a6788e6b69cb37f1fd5b8c9a661bc5476d", + "0x88c47301dd30998e903c84e0b0f2c9af2e1ce6b9f187dab03528d44f834dc991e4c86d0c474a2c63468cf4020a1e24a0", + "0x920c832853e6ab4c851eecfa9c11d3acc7da37c823be7aa1ab15e14dfd8beb5d0b91d62a30cec94763bd8e4594b66600", + "0x98e1addbe2a6b8edc7f12ecb9be81c3250aeeca54a1c6a7225772ca66549827c15f3950d01b8eb44aecb56fe0fff901a", + "0x8cfb0fa1068be0ec088402f5950c4679a2eb9218c729da67050b0d1b2d7079f3ddf4bf0f57d95fe2a8db04bc6bcdb20c", + "0xb70f381aafe336b024120453813aeab70baac85b9c4c0f86918797b6aee206e6ed93244a49950f3d8ec9f81f4ac15808", + "0xa4c8edf4aa33b709a91e1062939512419711c1757084e46f8f4b7ed64f8e682f4e78b7135920c12f0eb0422fe9f87a6a", + "0xb4817e85fd0752d7ebb662d3a51a03367a84bac74ebddfba0e5af5e636a979500f72b148052d333b3dedf9edd2b4031b", + "0xa87430169c6195f5d3e314ff2d1c2f050e766fd5d2de88f5207d72dba4a7745bb86d0baca6e9ae156582d0d89e5838c7", + "0x991b00f8b104566b63a12af4826b61ce7aa40f4e5b8fff3085e7a99815bdb4471b6214da1e480214fac83f86a0b93cc5", + "0xb39966e3076482079de0678477df98578377a094054960ee518ef99504d6851f8bcd3203e8da5e1d4f6f96776e1fe6eb", + "0xa448846d9dc2ab7a0995fa44b8527e27f6b3b74c6e03e95edb64e6baa4f1b866103f0addb97c84bef1d72487b2e21796", + "0x894bec21a453ae84b592286e696c35bc30e820e9c2fd3e63dd4fbe629e07df16439c891056070faa490155f255bf7187", + "0xa9ec652a491b11f6a692064e955f3f3287e7d2764527e58938571469a1e29b5225b9415bd602a45074dfbfe9c131d6ca", + "0xb39d37822e6cbe28244b5f42ce467c65a23765bd16eb6447c5b3e942278069793763483dafd8c4dd864f8917aad357fe", + "0x88dba51133f2019cb266641c56101e3e5987d3b77647a2e608b5ff9113dfc5f85e2b7c365118723131fbc0c9ca833c9c", + "0xb566579d904b54ecf798018efcb824dccbebfc6753a0fd2128ac3b4bd3b038c2284a7c782b5ca6f310eb7ea4d26a3f0a", + "0xa97a55c0a492e53c047e7d6f9d5f3e86fb96f3dddc68389c0561515343b66b4bc02a9c0d5722dff1e3445308240b27f7", + "0xa044028ab4bcb9e1a2b9b4ca4efbf04c5da9e4bf2fff0e8bd57aa1fc12a71e897999c25d9117413faf2f45395dee0f13", + "0xa78dc461decbeaeed8ebd0909369b491a5e764d6a5645a7dac61d3140d7dc0062526f777b0eb866bff27608429ebbdde", + "0xb2c2a8991f94c39ca35fea59f01a92cb3393e0eccb2476dfbf57261d406a68bd34a6cff33ed80209991688c183609ef4", + "0x84189eefb521aff730a4fd3fd5b10ddfd29f0d365664caef63bb015d07e689989e54c33c2141dd64427805d37a7e546e", + "0x85ac80bd734a52235da288ff042dea9a62e085928954e8eacd2c751013f61904ed110e5b3afe1ab770a7e6485efb7b5e", + "0x9183a560393dcb22d0d5063e71182020d0fbabb39e32493eeffeb808df084aa243eb397027f150b55a247d1ed0c8513e", + "0x81c940944df7ecc58d3c43c34996852c3c7915ed185d7654627f7af62abae7e0048dd444a6c09961756455000bd96d09", + "0xaa8c34e164019743fd8284b84f06c3b449aae7996e892f419ee55d82ad548cb300fd651de329da0384243954c0ef6a60", + "0x89a7b7bdfc7e300d06a14d463e573d6296d8e66197491900cc9ae49504c4809ff6e61b758579e9091c61085ba1237b83", + "0x878d21809ba540f50bd11f4c4d9590fb6f3ab9de5692606e6e2ef4ed9d18520119e385be5e1f4b3f2e2b09c319f0e8fc", + "0x8eb248390193189cf0355365e630b782cd15751e672dc478b39d75dc681234dcd9309df0d11f4610dbb249c1e6be7ef9", + "0xa1d7fb3aecb896df3a52d6bd0943838b13f1bd039c936d76d03de2044c371d48865694b6f532393b27fd10a4cf642061", + "0xa34bca58a24979be442238cbb5ece5bee51ae8c0794dd3efb3983d4db713bc6f28a96e976ac3bd9a551d3ed9ba6b3e22", + "0x817c608fc8cacdd178665320b5a7587ca21df8bdd761833c3018b967575d25e3951cf3d498a63619a3cd2ad4406f5f28", + "0x86c95707db0495689afd0c2e39e97f445f7ca0edffad5c8b4cacd1421f2f3cc55049dfd504f728f91534e20383955582", + "0x99c3b0bb15942c301137765d4e19502f65806f3b126dc01a5b7820c87e8979bce6a37289a8f6a4c1e4637227ad5bf3bf", + "0x8aa1518a80ea8b074505a9b3f96829f5d4afa55a30efe7b4de4e5dbf666897fdd2cf31728ca45921e21a78a80f0e0f10", + "0x8d74f46361c79e15128ac399e958a91067ef4cec8983408775a87eca1eed5b7dcbf0ddf30e66f51780457413496c7f07", + "0xa41cde4a786b55387458a1db95171aca4fd146507b81c4da1e6d6e495527c3ec83fc42fad1dfe3d92744084a664fd431", + "0x8c352852c906fae99413a84ad11701f93f292fbf7bd14738814f4c4ceab32db02feb5eb70bc73898b0bc724a39d5d017", + "0xa5993046e8f23b71ba87b7caa7ace2d9023fb48ce4c51838813174880d918e9b4d2b0dc21a2b9c6f612338c31a289df8", + "0x83576d3324bf2d8afbfb6eaecdc5d767c8e22e7d25160414924f0645491df60541948a05e1f4202e612368e78675de8a", + "0xb43749b8df4b15bc9a3697e0f1c518e6b04114171739ef1a0c9c65185d8ec18e40e6954d125cbc14ebc652cf41ad3109", + "0xb4eebd5d80a7327a040cafb9ccdb12b2dfe1aa86e6bc6d3ac8a57fadfb95a5b1a7332c66318ff72ba459f525668af056", + "0x9198be7f1d413c5029b0e1c617bcbc082d21abe2c60ec8ce9b54ca1a85d3dba637b72fda39dae0c0ae40d047eab9f55a", + "0x8d96a0232832e24d45092653e781e7a9c9520766c3989e67bbe86b3a820c4bf621ea911e7cd5270a4bfea78b618411f6", + "0x8d7160d0ea98161a2d14d46ef01dff72d566c330cd4fabd27654d300e1bc7644c68dc8eabf2a20a59bfe7ba276545f9b", + "0xabb60fce29dec7ba37e3056e412e0ec3e05538a1fc0e2c68877378c867605966108bc5742585ab6a405ce0c962b285b6", + "0x8fabffa3ed792f05e414f5839386f6449fd9f7b41a47595c5d71074bd1bb3784cc7a1a7e1ad6b041b455035957e5b2dc", + "0x90ff017b4804c2d0533b72461436b10603ab13a55f86fd4ec11b06a70ef8166f958c110519ca1b4cc7beba440729fe2d", + "0xb340cfd120f6a4623e3a74cf8c32bfd7cd61a280b59dfd17b15ca8fae4d82f64a6f15fbde4c02f424debc72b7db5fe67", + "0x871311c9c7220c932e738d59f0ecc67a34356d1429fe570ca503d340c9996cb5ee2cd188fad0e3bd16e4c468ec1dbebd", + "0xa772470262186e7b94239ba921b29f2412c148d6f97c4412e96d21e55f3be73f992f1ad53c71008f0558ec3f84e2b5a7", + "0xb2a897dcb7ffd6257f3f2947ec966f2077d57d5191a88840b1d4f67effebe8c436641be85524d0a21be734c63ab5965d", + "0xa044f6eacc48a4a061fa149500d96b48cbf14853469aa4d045faf3dca973be1bd4b4ce01646d83e2f24f7c486d03205d", + "0x981af5dc2daa73f7fa9eae35a93d81eb6edba4a7f673b55d41f6ecd87a37685d31bb40ef4f1c469b3d72f2f18b925a17", + "0x912d2597a07864de9020ac77083eff2f15ceb07600f15755aba61251e8ce3c905a758453b417f04d9c38db040954eb65", + "0x9642b7f6f09394ba5e0805734ef6702c3eddf9eea187ba98c676d5bbaec0e360e3e51dc58433aaa1e2da6060c8659cb7", + "0x8ab3836e0a8ac492d5e707d056310c4c8e0489ca85eb771bff35ba1d658360084e836a6f51bb990f9e3d2d9aeb18fbb5", + "0x879e058e72b73bb1f4642c21ffdb90544b846868139c6511f299aafe59c2d0f0b944dffc7990491b7c4edcd6a9889250", + "0xb9e60b737023f61479a4a8fd253ed0d2a944ea6ba0439bbc0a0d3abf09b0ad1f18d75555e4a50405470ae4990626f390", + "0xb9c2535d362796dcd673640a9fa2ebdaec274e6f8b850b023153b0a7a30fffc87f96e0b72696f647ebe7ab63099a6963", + "0x94aeff145386a087b0e91e68a84a5ede01f978f9dd9fe7bebca78941938469495dc30a96bba9508c0d017873aeea9610", + "0x98b179f8a3d9f0d0a983c30682dd425a2ddc7803be59bd626c623c8951a5179117d1d2a68254c95c9952989877d0ee55", + "0x889ecf5f0ee56938273f74eb3e9ecfb5617f04fb58e83fe4c0e4aef51615cf345bc56f3f61b17f6eed3249d4afd54451", + "0xa0f2b2c39bcea4b50883e2587d16559e246248a66ecb4a4b7d9ab3b51fb39fe98d83765e087eee37a0f86b0ba4144c02", + "0xb2a61e247ed595e8a3830f7973b07079cbda510f28ad8c78c220b26cb6acde4fbb5ee90c14a665f329168ee951b08cf0", + "0x95bd0fcfb42f0d6d8a8e73d7458498a85bcddd2fb132fd7989265648d82ac2707d6d203fac045504977af4f0a2aca4b7", + "0x843e5a537c298666e6cf50fcc044f13506499ef83c802e719ff2c90e85003c132024e04711be7234c04d4b0125512d5d", + "0xa46d1797c5959dcd3a5cfc857488f4d96f74277c3d13b98b133620192f79944abcb3a361d939a100187f1b0856eae875", + "0xa1c7786736d6707a48515c38660615fcec67eb8a2598f46657855215f804fd72ab122d17f94fcffad8893f3be658dca7", + "0xb23dc9e610abc7d8bd21d147e22509a0fa49db5be6ea7057b51aae38e31654b3aa044df05b94b718153361371ba2f622", + "0xb00cc8f257d659c22d30e6d641f79166b1e752ea8606f558e4cad6fc01532e8319ea4ee12265ba4140ac45aa4613c004", + "0xac7019af65221b0cc736287b32d7f1a3561405715ba9a6a122342e04e51637ba911c41573de53e4781f2230fdcb2475f", + "0x81a630bc41b3da8b3eb4bf56cba10cd9f93153c3667f009dc332287baeb707d505fb537e6233c8e53d299ec0f013290c", + "0xa6b7aea5c545bb76df0f230548539db92bc26642572cb7dd3d5a30edca2b4c386f44fc8466f056b42de2a452b81aff5b", + "0x8271624ff736b7b238e43943c81de80a1612207d32036d820c11fc830c737972ccc9c60d3c2359922b06652311e3c994", + "0x8a684106458cb6f4db478170b9ad595d4b54c18bf63b9058f095a2fa1b928c15101472c70c648873d5887880059ed402", + "0xa5cc3c35228122f410184e4326cf61a37637206e589fcd245cb5d0cec91031f8f7586b80503070840fdfd8ce75d3c88b", + "0x9443fc631aed8866a7ed220890911057a1f56b0afe0ba15f0a0e295ab97f604b134b1ed9a4245e46ee5f9a93aa74f731", + "0x984b6f7d79835dffde9558c6bb912d992ca1180a2361757bdba4a7b69dc74b056e303adc69fe67414495dd9c2dd91e64", + "0xb15a5c8cba5de080224c274d31c68ed72d2a7126d347796569aef0c4e97ed084afe3da4d4b590b9dda1a07f0c2ff3dfb", + "0x991708fe9650a1f9a4e43938b91d45dc68c230e05ee999c95dbff3bf79b1c1b2bb0e7977de454237c355a73b8438b1d9", + "0xb4f7edc7468b176a4a7c0273700c444fa95c726af6697028bed4f77eee887e3400f9c42ee15b782c0ca861c4c3b8c98a", + "0x8c60dcc16c51087eb477c13e837031d6c6a3dc2b8bf8cb43c23f48006bc7173151807e866ead2234b460c2de93b31956", + "0x83ad63e9c910d1fc44bc114accfb0d4d333b7ebe032f73f62d25d3e172c029d5e34a1c9d547273bf6c0fead5c8801007", + "0x85de73213cc236f00777560756bdbf2b16841ba4b55902cf2cad9742ecaf5d28209b012ceb41f337456dfeca93010cd7", + "0xa7561f8827ccd75b6686ba5398bb8fc3083351c55a589b18984e186820af7e275af04bcd4c28e1dc11be1e8617a0610b", + "0x88c0a4febd4068850557f497ea888035c7fc9f404f6cc7794e7cc8722f048ad2f249e7dc62743e7a339eb7473ad3b0cd", + "0x932b22b1d3e6d5a6409c34980d176feb85ada1bf94332ef5c9fc4d42b907dabea608ceef9b5595ef3feee195151f18d8", + "0xa2867bb3f5ab88fbdae3a16c9143ab8a8f4f476a2643c505bb9f37e5b1fd34d216cab2204c9a017a5a67b7ad2dda10e8", + "0xb573d5f38e4e9e8a3a6fd82f0880dc049efa492a946d00283019bf1d5e5516464cf87039e80aef667cb86fdea5075904", + "0xb948f1b5ab755f3f5f36af27d94f503b070696d793b1240c1bdfd2e8e56890d69e6904688b5f8ff5a4bdf5a6abfe195f", + "0x917eae95ebc4109a2e99ddd8fec7881d2f7aaa0e25fda44dec7ce37458c2ee832f1829db7d2dcfa4ca0f06381c7fe91d", + "0x95751d17ed00a3030bce909333799bb7f4ab641acf585807f355b51d6976dceee410798026a1a004ef4dcdff7ec0f5b8", + "0xb9b7bd266f449a79bbfe075e429613e76c5a42ac61f01c8f0bbbd34669650682efe01ff9dbbc400a1e995616af6aa278", + "0xac1722d097ce9cd7617161f8ec8c23d68f1fb1c9ca533e2a8b4f78516c2fd8fb38f23f834e2b9a03bb06a9d655693ca9", + "0xa7ad9e96ffd98db2ecdb6340c5d592614f3c159abfd832fe27ee9293519d213a578e6246aae51672ee353e3296858873", + "0x989b8814d5de7937c4acafd000eec2b4cd58ba395d7b25f98cafd021e8efa37029b29ad8303a1f6867923f5852a220eb", + "0xa5bfe6282c771bc9e453e964042d44eff4098decacb89aecd3be662ea5b74506e1357ab26f3527110ba377711f3c9f41", + "0x8900a7470b656639721d2abbb7b06af0ac4222ab85a1976386e2a62eb4b88bfb5b72cf7921ddb3cf3a395d7eeb192a2e", + "0x95a71b55cd1f35a438cf5e75f8ff11c5ec6a2ebf2e4dba172f50bfad7d6d5dca5de1b1afc541662c81c858f7604c1163", + "0x82b5d62fea8db8d85c5bc3a76d68dedd25794cf14d4a7bc368938ffca9e09f7e598fdad2a5aac614e0e52f8112ae62b9", + "0x997173f07c729202afcde3028fa7f52cefc90fda2d0c8ac2b58154a5073140683e54c49ed1f254481070d119ce0ce02a", + "0xaeffb91ccc7a72bbd6ffe0f9b99c9e66e67d59cec2e02440465e9636a613ab3017278cfa72ea8bc4aba9a8dc728cb367", + "0x952743b06e8645894aeb6440fc7a5f62dd3acf96dab70a51e20176762c9751ea5f2ba0b9497ccf0114dc4892dc606031", + "0x874c63baeddc56fbbca2ff6031f8634b745f6e34ea6791d7c439201aee8f08ef5ee75f7778700a647f3b21068513fce6", + "0x85128fec9c750c1071edfb15586435cc2f317e3e9a175bb8a9697bcda1eb9375478cf25d01e7fed113483b28f625122d", + "0x85522c9576fd9763e32af8495ae3928ed7116fb70d4378448926bc9790e8a8d08f98cf47648d7da1b6e40d6a210c7924", + "0x97d0f37a13cfb723b848099ca1c14d83e9aaf2f7aeb71829180e664b7968632a08f6a85f557d74b55afe6242f2a36e7c", + "0xabaa472d6ad61a5fccd1a57c01aa1bc081253f95abbcba7f73923f1f11c4e79b904263890eeb66926de3e2652f5d1c70", + "0xb3c04945ba727a141e5e8aec2bf9aa3772b64d8fd0e2a2b07f3a91106a95cbcb249adcd074cbe498caf76fffac20d4ef", + "0x82c46781a3d730d9931bcabd7434a9171372dde57171b6180e5516d4e68db8b23495c8ac3ab96994c17ddb1cf249b9fb", + "0xa202d8b65613c42d01738ccd68ed8c2dbc021631f602d53f751966e04182743ebc8e0747d600b8a8676b1da9ae7f11ab", + "0xae73e7256e9459db04667a899e0d3ea5255211fb486d084e6550b6dd64ca44af6c6b2d59d7aa152de9f96ce9b58d940d", + "0xb67d87b176a9722945ec7593777ee461809861c6cfd1b945dde9ee4ff009ca4f19cf88f4bbb5c80c9cbab2fe25b23ac8", + "0x8f0b7a317a076758b0dac79959ee4a06c08b07d0f10538a4b53d3da2eda16e2af26922feb32c090330dc4d969cf69bd3", + "0x90b36bf56adbd8c4b6cb32febc3a8d5f714370c2ac3305c10fa6d168dffb2a026804517215f9a2d4ec8310cdb6bb459b", + "0xaa80c19b0682ead69934bf18cf476291a0beddd8ef4ed75975d0a472e2ab5c70f119722a8574ae4973aceb733d312e57", + "0xa3fc9abb12574e5c28dcb51750b4339b794b8e558675eef7d26126edf1de920c35e992333bcbffcbf6a5f5c0d383ce62", + "0xa1573ff23ab972acdcd08818853b111fc757fdd35aa070186d3e11e56b172fb49d840bf297ac0dd222e072fc09f26a81", + "0x98306f2be4caa92c2b4392212d0cbf430b409b19ff7d5b899986613bd0e762c909fc01999aa94be3bd529d67f0113d7f", + "0x8c1fc42482a0819074241746d17dc89c0304a2acdae8ed91b5009e9e3e70ff725ba063b4a3e68fdce05b74f5180c545e", + "0xa6c6113ebf72d8cf3163b2b8d7f3fa24303b13f55752522c660a98cd834d85d8c79214d900fa649499365e2e7641f77a", + "0xab95eea424f8a2cfd9fb1c78bb724e5b1d71a0d0d1e4217c5d0f98b0d8bbd3f8400a2002abc0a0e4576d1f93f46fefad", + "0x823c5a4fd8cf4a75fdc71d5f2dd511b6c0f189b82affeacd2b7cfcad8ad1a5551227dcc9bfdb2e34b2097eaa00efbb51", + "0xb97314dfff36d80c46b53d87a61b0e124dc94018a0bb680c32765b9a2d457f833a7c42bbc90b3b1520c33a182580398d", + "0xb17566ee3dcc6bb3b004afe4c0136dfe7dd27df9045ae896dca49fb36987501ae069eb745af81ba3fc19ff037e7b1406", + "0xb0bdc0f55cfd98d331e3a0c4fbb776a131936c3c47c6bffdc3aaf7d8c9fa6803fbc122c2fefbb532e634228687d52174", + "0xaa5d9e60cc9f0598559c28bb9bdd52aa46605ab4ffe3d192ba982398e72cec9a2a44c0d0d938ce69935693cabc0887ea", + "0x802b6459d2354fa1d56c592ac1346c428dadea6b6c0a87bf7d309bab55c94e1cf31dd98a7a86bd92a840dd51f218b91b", + "0xa526914efdc190381bf1a73dd33f392ecf01350b9d3f4ae96b1b1c3d1d064721c7d6eec5788162c933245a3943f5ee51", + "0xb3b8fcf637d8d6628620a1a99dbe619eabb3e5c7ce930d6efd2197e261bf394b74d4e5c26b96c4b8009c7e523ccfd082", + "0x8f7510c732502a93e095aba744535f3928f893f188adc5b16008385fb9e80f695d0435bfc5b91cdad4537e87e9d2551c", + "0x97b90beaa56aa936c3ca45698f79273a68dd3ccd0076eab48d2a4db01782665e63f33c25751c1f2e070f4d1a8525bf96", + "0xb9fb798324b1d1283fdc3e48288e3861a5449b2ab5e884b34ebb8f740225324af86e4711da6b5cc8361c1db15466602f", + "0xb6d52b53cea98f1d1d4c9a759c25bf9d8a50b604b144e4912acbdbdc32aab8b9dbb10d64a29aa33a4f502121a6fb481c", + "0x9174ffff0f2930fc228f0e539f5cfd82c9368d26b074467f39c07a774367ff6cccb5039ac63f107677d77706cd431680", + "0xa33b6250d4ac9e66ec51c063d1a6a31f253eb29bbaed12a0d67e2eccfffb0f3a52750fbf52a1c2aaba8c7692346426e7", + "0xa97025fd5cbcebe8ef865afc39cd3ea707b89d4e765ec817fd021d6438e02fa51e3544b1fd45470c58007a08efac6edd", + "0xb32a78480edd9ff6ba2f1eec4088db5d6ceb2d62d7e59e904ecaef7bb4a2e983a4588e51692b3be76e6ffbc0b5f911a5", + "0xb5ab590ef0bb77191f00495b33d11c53c65a819f7d0c1f9dc4a2caa147a69c77a4fff7366a602d743ee1f395ce934c1e", + "0xb3fb0842f9441fb1d0ee0293b6efbc70a8f58d12d6f769b12872db726b19e16f0f65efbc891cf27a28a248b0ef9c7e75", + "0x9372ad12856fefb928ccb0d34e198df99e2f8973b07e9d417a3134d5f69e12e79ff572c4e03ccd65415d70639bc7c73e", + "0xaa8d6e83d09ce216bfe2009a6b07d0110d98cf305364d5529c170a23e693aabb768b2016befb5ada8dabdd92b4d012bb", + "0xa954a75791eeb0ce41c85200c3763a508ed8214b5945a42c79bfdcfb1ec4f86ad1dd7b2862474a368d4ac31911a2b718", + "0x8e2081cfd1d062fe3ab4dab01f68062bac802795545fede9a188f6c9f802cb5f884e60dbe866710baadbf55dc77c11a4", + "0xa2f06003b9713e7dd5929501ed485436b49d43de80ea5b15170763fd6346badf8da6de8261828913ee0dacd8ff23c0e1", + "0x98eecc34b838e6ffd1931ca65eec27bcdb2fdcb61f33e7e5673a93028c5865e0d1bf6d3bec040c5e96f9bd08089a53a4", + "0x88cc16019741b341060b95498747db4377100d2a5bf0a5f516f7dec71b62bcb6e779de2c269c946d39040e03b3ae12b7", + "0xad1135ccbc3019d5b2faf59a688eef2500697642be8cfbdf211a1ab59abcc1f24483e50d653b55ff1834675ac7b4978f", + "0xa946f05ed9972f71dfde0020bbb086020fa35b482cce8a4cc36dd94355b2d10497d7f2580541bb3e81b71ac8bba3c49f", + "0xa83aeed488f9a19d8cfd743aa9aa1982ab3723560b1cd337fc2f91ad82f07afa412b3993afb845f68d47e91ba4869840", + "0x95eebe006bfc316810cb71da919e5d62c2cebb4ac99d8e8ef67be420302320465f8b69873470982de13a7c2e23516be9", + "0xa55f8961295a11e91d1e5deadc0c06c15dacbfc67f04ccba1d069cba89d72aa3b3d64045579c3ea8991b150ac29366ae", + "0xb321991d12f6ac07a5de3c492841d1a27b0d3446082fbce93e7e1f9e8d8fe3b45d41253556261c21b70f5e189e1a7a6f", + "0xa0b0822f15f652ce7962a4f130104b97bf9529797c13d6bd8e24701c213cc37f18157bd07f3d0f3eae6b7cd1cb40401f", + "0x96e2fa4da378aa782cc2d5e6e465fc9e49b5c805ed01d560e9b98abb5c0de8b74a2e7bec3aa5e2887d25cccb12c66f0c", + "0x97e4ab610d414f9210ed6f35300285eb3ccff5b0b6a95ed33425100d7725e159708ea78704497624ca0a2dcabce3a2f9", + "0x960a375b17bdb325761e01e88a3ea57026b2393e1d887b34b8fa5d2532928079ce88dc9fd06a728b26d2bb41b12b9032", + "0x8328a1647398e832aadc05bd717487a2b6fcdaa0d4850d2c4da230c6a2ed44c3e78ec4837b6094f3813f1ee99414713f", + "0xaa283834ebd18e6c99229ce4b401eda83f01d904f250fedd4e24f1006f8fa0712a6a89a7296a9bf2ce8de30e28d1408e", + "0xb29e097f2caadae3e0f0ae3473c072b0cd0206cf6d2e9b22c1a5ad3e07d433e32bd09ed1f4e4276a2da4268633357b7f", + "0x9539c5cbba14538b2fe077ecf67694ef240da5249950baaabea0340718b882a966f66d97f08556b08a4320ceb2cc2629", + "0xb4529f25e9b42ae8cf8338d2eface6ba5cd4b4d8da73af502d081388135c654c0b3afb3aa779ffc80b8c4c8f4425dd2b", + "0x95be0739c4330619fbe7ee2249c133c91d6c07eab846c18c5d6c85fc21ac5528c5d56dcb0145af68ed0c6a79f68f2ccd", + "0xac0c83ea802227bfc23814a24655c9ff13f729619bcffdb487ccbbf029b8eaee709f8bddb98232ef33cd70e30e45ca47", + "0xb503becb90acc93b1901e939059f93e671900ca52c6f64ae701d11ac891d3a050b505d89324ce267bc43ab8275da6ffe", + "0x98e3811b55b1bacb70aa409100abb1b870f67e6d059475d9f278c751b6e1e2e2d6f2e586c81a9fb6597fda06e7923274", + "0xb0b0f61a44053fa6c715dbb0731e35d48dba257d134f851ee1b81fd49a5c51a90ebf5459ec6e489fce25da4f184fbdb1", + "0xb1d2117fe811720bb997c7c93fe9e4260dc50fca8881b245b5e34f724aaf37ed970cdad4e8fcb68e05ac8cf55a274a53", + "0xa10f502051968f14b02895393271776dee7a06db9de14effa0b3471825ba94c3f805302bdddac4d397d08456f620999d", + "0xa3dbad2ef060ae0bb7b02eaa4a13594f3f900450faa1854fc09620b01ac94ab896321dfb1157cf2374c27e5718e8026a", + "0xb550fdec503195ecb9e079dcdf0cad559d64d3c30818ef369b4907e813e689da316a74ad2422e391b4a8c2a2bef25fc0", + "0xa25ba865e2ac8f28186cea497294c8649a201732ecb4620c4e77b8e887403119910423df061117e5f03fc5ba39042db1", + "0xb3f88174e03fdb443dd6addd01303cf88a4369352520187c739fc5ae6b22fa99629c63c985b4383219dab6acc5f6f532", + "0x97a7503248e31e81b10eb621ba8f5210c537ad11b539c96dfb7cf72b846c7fe81bd7532c5136095652a9618000b7f8d3", + "0xa8bcdc1ce5aa8bfa683a2fc65c1e79de8ff5446695dcb8620f7350c26d2972a23da22889f9e2b1cacb3f688c6a2953dc", + "0x8458c111df2a37f5dd91a9bee6c6f4b79f4f161c93fe78075b24a35f9817da8dde71763218d627917a9f1f0c4709c1ed", + "0xac5f061a0541152b876cbc10640f26f1cc923c9d4ae1b6621e4bb3bf2cec59bbf87363a4eb72fb0e5b6d4e1c269b52d5", + "0xa9a25ca87006e8a9203cbb78a93f50a36694aa4aad468b8d80d3feff9194455ca559fcc63838128a0ab75ad78c07c13a", + "0xa450b85f5dfffa8b34dfd8bc985f921318efacf8857cf7948f93884ba09fb831482ee90a44224b1a41e859e19b74962f", + "0x8ed91e7f92f5c6d7a71708b6132f157ac226ecaf8662af7d7468a4fa25627302efe31e4620ad28719318923e3a59bf82", + "0xab524165fd4c71b1fd395467a14272bd2b568592deafa039d8492e9ef36c6d3f96927c95c72d410a768dc0b6d1fbbc9b", + "0xb662144505aa8432c75ffb8d10318526b6d5777ac7af9ebfad87d9b0866c364f7905a6352743bd8fd79ffd9d5dd4f3e6", + "0xa48f1677550a5cd40663bb3ba8f84caaf8454f332d0ceb1d94dbea52d0412fe69c94997f7749929712fd3995298572f7", + "0x8391cd6e2f6b0c242de1117a612be99776c3dc95cb800b187685ea5bf7e2722275eddb79fd7dfc8be8e389c4524cdf70", + "0x875d3acb9af47833b72900bc0a2448999d638f153c5e97e8a14ec02d0c76f6264353a7e275e1f1a5855daced523d243b", + "0x91f1823657d30b59b2f627880a9a9cb530f5aca28a9fd217fe6f2f5133690dfe7ad5a897872e400512db2e788b3f7628", + "0xad3564332aa56cea84123fc7ca79ea70bb4fef2009fa131cb44e4b15e8613bd11ca1d83b9d9bf456e4b7fee9f2e8b017", + "0x8c530b84001936d5ab366c84c0b105241a26d1fb163669f17c8f2e94776895c2870edf3e1bc8ccd04d5e65531471f695", + "0x932d01fa174fdb0c366f1230cffde2571cc47485f37f23ba5a1825532190cc3b722aeb1f15aed62cf83ccae9403ba713", + "0x88b28c20585aca50d10752e84b901b5c2d58efef5131479fbbe53de7bce2029e1423a494c0298e1497669bd55be97a5d", + "0xb914148ca717721144ebb3d3bf3fcea2cd44c30c5f7051b89d8001502f3856fef30ec167174d5b76265b55d70f8716b5", + "0x81d0173821c6ddd2a068d70766d9103d1ee961c475156e0cbd67d54e668a796310474ef698c7ab55abe6f2cf76c14679", + "0x8f28e8d78e2fe7fa66340c53718e0db4b84823c8cfb159c76eac032a62fb53da0a5d7e24ca656cf9d2a890cb2a216542", + "0x8a26360335c73d1ab51cec3166c3cf23b9ea51e44a0ad631b0b0329ef55aaae555420348a544e18d5760969281759b61", + "0x94f326a32ed287545b0515be9e08149eb0a565025074796d72387cc3a237e87979776410d78339e23ef3172ca43b2544", + "0xa785d2961a2fa5e70bffa137858a92c48fe749fee91b02599a252b0cd50d311991a08efd7fa5e96b78d07e6e66ffe746", + "0x94af9030b5ac792dd1ce517eaadcec1482206848bea4e09e55cc7f40fd64d4c2b3e9197027c5636b70d6122c51d2235d", + "0x9722869f7d1a3992850fe7be405ec93aa17dc4d35e9e257d2e469f46d2c5a59dbd504056c85ab83d541ad8c13e8bcd54", + "0xb13c4088b61a06e2c03ac9813a75ff1f68ffdfee9df6a8f65095179a475e29cc49119cad2ce05862c3b1ac217f3aace9", + "0x8c64d51774753623666b10ca1b0fe63ae42f82ed6aa26b81dc1d48c86937c5772eb1402624c52a154b86031854e1fb9f", + "0xb47e4df18002b7dac3fee945bf9c0503159e1b8aafcce2138818e140753011b6d09ef1b20894e08ba3006b093559061b", + "0x93cb5970076522c5a0483693f6a35ffd4ea2aa7aaf3730c4eccd6af6d1bebfc1122fc4c67d53898ae13eb6db647be7e2", + "0xa68873ef80986795ea5ed1a597d1cd99ed978ec25e0abb57fdcc96e89ef0f50aeb779ff46e3dce21dc83ada3157a8498", + "0x8cab67f50949cc8eee6710e27358aea373aae3c92849f8f0b5531c080a6300cdf2c2094fe6fecfef6148de0d28446919", + "0x993e932bcb616dbaa7ad18a4439e0565211d31071ef1b85a0627db74a05d978c60d507695eaeea5c7bd9868a21d06923", + "0xacdadff26e3132d9478a818ef770e9fa0d2b56c6f5f48bd3bd674436ccce9bdfc34db884a73a30c04c5f5e9764cb2218", + "0xa0d3e64c9c71f84c0eef9d7a9cb4fa184224b969db5514d678e93e00f98b41595588ca802643ea225512a4a272f5f534", + "0x91c9140c9e1ba6e330cb08f6b2ce4809cd0d5a0f0516f70032bf30e912b0ed684d07b413b326ab531ee7e5b4668c799b", + "0x87bc2ee7a0c21ba8334cd098e35cb703f9af57f35e091b8151b9b63c3a5b0f89bd7701dbd44f644ea475901fa6d9ef08", + "0x9325ccbf64bf5d71b303e31ee85d486298f9802c5e55b2c3d75427097bf8f60fa2ab4fcaffa9b60bf922c3e24fbd4b19", + "0x95d0506e898318f3dc8d28d16dfd9f0038b54798838b3c9be2a2ae3c2bf204eb496166353fc042220b0bd4f6673b9285", + "0x811de529416331fe9c416726d45df9434c29dcd7e949045eb15740f47e97dde8f31489242200e19922cac2a8b7c6fd1f", + "0xade632d04a4c8bbab6ca7df370b2213cb9225023e7973f0e29f4f5e52e8aeaabc65171306bbdd12a67b195dfbb96d48f", + "0x88b7f029e079b6ae956042c0ea75d53088c5d0efd750dd018adaeacf46be21bf990897c58578c491f41afd3978d08073", + "0x91f477802de507ffd2be3f4319903119225b277ad24f74eb50f28b66c14d32fae53c7edb8c7590704741af7f7f3e3654", + "0x809838b32bb4f4d0237e98108320d4b079ee16ed80c567e7548bd37e4d7915b1192880f4812ac0e00476d246aec1dbc8", + "0x84183b5fc4a7997a8ae5afedb4d21dce69c480d5966b5cbdafd6dd10d29a9a6377f3b90ce44da0eb8b176ac3af0253bb", + "0x8508abbf6d3739a16b9165caf0f95afb3b3ac1b8c38d6d374cf0c91296e2c1809a99772492b539cda184510bce8a0271", + "0x8722054e59bab2062e6419a6e45fc803af77fde912ef2cd23055ad0484963de65a816a2debe1693d93c18218d2b8e81a", + "0x8e895f80e485a7c4f56827bf53d34b956281cdc74856c21eb3b51f6288c01cc3d08565a11cc6f3e2604775885490e8c5", + "0xafc92714771b7aa6e60f3aee12efd9c2595e9659797452f0c1e99519f67c8bc3ac567119c1ddfe82a3e961ee9defea9a", + "0x818ff0fd9cefd32db87b259e5fa32967201016fc02ef44116cdca3c63ce5e637756f60477a408709928444a8ad69c471", + "0x8251e29af4c61ae806fc5d032347fb332a94d472038149225298389495139ce5678fae739d02dfe53a231598a992e728", + "0xa0ea39574b26643f6f1f48f99f276a8a64b5481989cfb2936f9432a3f8ef5075abfe5c067dc5512143ce8bf933984097", + "0xaf67a73911b372bf04e57e21f289fc6c3dfac366c6a01409b6e76fea4769bdb07a6940e52e8d7d3078f235c6d2f632c6", + "0xb5291484ef336024dd2b9b4cf4d3a6b751133a40656d0a0825bcc6d41c21b1c79cb50b0e8f4693f90c29c8f4358641f9", + "0x8bc0d9754d70f2cb9c63f991902165a87c6535a763d5eece43143b5064ae0bcdce7c7a8f398f2c1c29167b2d5a3e6867", + "0x8d7faff53579ec8f6c92f661c399614cc35276971752ce0623270f88be937c414eddcb0997e14724a783905a026c8883", + "0x9310b5f6e675fdf60796f814dbaa5a6e7e9029a61c395761e330d9348a7efab992e4e115c8be3a43d08e90d21290c892", + "0xb5eb4f3eb646038ad2a020f0a42202532d4932e766da82b2c1002bf9c9c2e5336b54c8c0ffcc0e02d19dde2e6a35b6cc", + "0x91dabfd30a66710f1f37a891136c9be1e23af4abf8cb751f512a40c022a35f8e0a4fb05b17ec36d4208de02d56f0d53a", + "0xb3ded14e82d62ac7a5a036122a62f00ff8308498f3feae57d861babaff5a6628d43f0a0c5fc903f10936bcf4e2758ceb", + "0xa88e8348fed2b26acca6784d19ef27c75963450d99651d11a950ea81d4b93acd2c43e0ecce100eaf7e78508263d5baf3", + "0xb1f5bbf7c4756877b87bb42163ac570e08c6667c4528bf68b5976680e19beeff7c5effd17009b0718797077e2955457a", + "0xad2e7b516243f915d4d1415326e98b1a7390ae88897d0b03b66c2d9bd8c3fba283d7e8fe44ed3333296a736454cef6d8", + "0x8f82eae096d5b11f995de6724a9af895f5e1c58d593845ad16ce8fcae8507e0d8e2b2348a0f50a1f66a17fd6fac51a5c", + "0x890e4404d0657c6c1ee14e1aac132ecf7a568bb3e04137b85ac0f84f1d333bd94993e8750f88eee033a33fb00f85dcc7", + "0x82ac7d3385e035115f1d39a99fc73e5919de44f5e6424579776d118d711c8120b8e5916372c6f27bed4cc64cac170b6c", + "0x85ee16d8901c272cfbbe966e724b7a891c1bd5e68efd5d863043ad8520fc409080af61fd726adc680b3f1186fe0ac8b8", + "0x86dc564c9b545567483b43a38f24c41c6551a49cabeebb58ce86404662a12dbfafd0778d30d26e1c93ce222e547e3898", + "0xa29f5b4522db26d88f5f95f18d459f8feefab02e380c2edb65aa0617a82a3c1a89474727a951cef5f15050bcf7b380fb", + "0xa1ce039c8f6cac53352899edb0e3a72c76da143564ad1a44858bd7ee88552e2fe6858d1593bbd74aeee5a6f8034b9b9d", + "0x97f10d77983f088286bd7ef3e7fdd8fa275a56bec19919adf33cf939a90c8f2967d2b1b6fc51195cb45ad561202a3ed7", + "0xa25e2772e8c911aaf8712bdac1dd40ee061c84d3d224c466cfaae8e5c99604053f940cde259bd1c3b8b69595781dbfec", + "0xb31bb95a0388595149409c48781174c340960d59032ab2b47689911d03c68f77a2273576fbe0c2bf4553e330656058c7", + "0xb8b2e9287ad803fb185a13f0d7456b397d4e3c8ad5078f57f49e8beb2e85f661356a3392dbd7bcf6a900baa5582b86a1", + "0xa3d0893923455eb6e96cc414341cac33d2dbc88fba821ac672708cce131761d85a0e08286663a32828244febfcae6451", + "0x82310cb42f647d99a136014a9f881eb0b9791efd2e01fc1841907ad3fc8a9654d3d1dab6689c3607214b4dc2aca01cee", + "0x874022d99c16f60c22de1b094532a0bc6d4de700ad01a31798fac1d5088b9a42ad02bef8a7339af7ed9c0d4f16b186ee", + "0x94981369e120265aed40910eebc37eded481e90f4596b8d57c3bec790ab7f929784bd33ddd05b7870aad6c02e869603b", + "0xa4f1f50e1e2a73f07095e0dd31cb45154f24968dae967e38962341c1241bcd473102fff1ff668b20c6547e9732d11701", + "0xae2328f3b0ad79fcda807e69a1b5278145225083f150f67511dafc97e079f860c3392675f1752ae7e864c056e592205b", + "0x875d8c971e593ca79552c43d55c8c73b17cd20c81ff2c2fed1eb19b1b91e4a3a83d32df150dbfd5db1092d0aebde1e1f", + "0xadd2e80aa46aae95da73a11f130f4bda339db028e24c9b11e5316e75ba5e63bc991d2a1da172c7c8e8fee038baae3433", + "0xb46dbe1cb3424002aa7de51e82f600852248e251465c440695d52538d3f36828ff46c90ed77fc1d11534fe3c487df8ef", + "0xa5e5045d28b4e83d0055863c30c056628c58d4657e6176fd0536f5933f723d60e851bb726d5bf3c546b8ce4ac4a57ef8", + "0x91fec01e86dd1537e498fff7536ea3ca012058b145f29d9ada49370cd7b7193ac380e116989515df1b94b74a55c45df3", + "0xa7428176d6918cd916a310bdc75483c72de660df48cac4e6e7478eef03205f1827ea55afc0df5d5fa7567d14bbea7fc9", + "0x851d89bef45d9761fe5fdb62972209335193610015e16a675149519f9911373bac0919add226ef118d9f3669cfdf4734", + "0xb74acf5c149d0042021cb2422ea022be4c4f72a77855f42393e71ffd12ebb3eec16bdf16f812159b67b79a9706e7156d", + "0x99f35dce64ec99aa595e7894b55ce7b5a435851b396e79036ffb249c28206087db4c85379df666c4d95857db02e21ff9", + "0xb6b9a384f70db9e298415b8ab394ee625dafff04be2886476e59df8d052ca832d11ac68a9b93fba7ab055b7bc36948a4", + "0x898ee4aefa923ffec9e79f2219c7389663eb11eb5b49014e04ed4a336399f6ea1691051d86991f4c46ca65bcd4fdf359", + "0xb0f948217b0d65df7599a0ba4654a5e43c84db477936276e6f11c8981efc6eaf14c90d3650107ed4c09af4cc8ec11137", + "0xaa6286e27ac54f73e63dbf6f41865dd94d24bc0cf732262fcaff67319d162bb43af909f6f8ee27b1971939cfbba08141", + "0x8bca7cdf730cf56c7b2c8a2c4879d61361a6e1dba5a3681a1a16c17a56e168ace0e99cf0d15826a1f5e67e6b8a8a049a", + "0xa746d876e8b1ce225fcafca603b099b36504846961526589af977a88c60d31ba2cc56e66a3dec8a77b3f3531bf7524c9", + "0xa11e2e1927e6704cdb8874c75e4f1842cef84d7d43d7a38e339e61dc8ba90e61bbb20dd3c12e0b11d2471d58eed245be", + "0xa36395e22bc1d1ba8b0459a235203177737397da5643ce54ded3459d0869ff6d8d89f50c73cb62394bf66a959cde9b90", + "0x8b49f12ba2fdf9aca7e5f81d45c07d47f9302a2655610e7634d1e4bd16048381a45ef2c95a8dd5b0715e4b7cf42273af", + "0x91cffa2a17e64eb7f76bccbe4e87280ee1dd244e04a3c9eac12e15d2d04845d876eb24fe2ec6d6d266cce9efb281077f", + "0xa6b8afabf65f2dee01788114e33a2f3ce25376fb47a50b74da7c3c25ff1fdc8aa9f41307534abbf48acb6f7466068f69", + "0x8d13db896ccfea403bd6441191995c1a65365cab7d0b97fbe9526da3f45a877bd1f4ef2edef160e8a56838cd1586330e", + "0x98c717de9e01bef8842c162a5e757fe8552d53269c84862f4d451e7c656ae6f2ae473767b04290b134773f63be6fdb9d", + "0x8c2036ace1920bd13cf018e82848c49eb511fad65fd0ff51f4e4b50cf3bfc294afb63cba682c16f52fb595a98fa84970", + "0xa3520fdff05dbad9e12551b0896922e375f9e5589368bcb2cc303bde252743b74460cb5caf99629325d3620f13adc796", + "0x8d4f83a5bfec05caf5910e0ce538ee9816ee18d0bd44c1d0da2a87715a23cd2733ad4d47552c6dc0eb397687d611dd19", + "0xa7b39a0a6a02823452d376533f39d35029867b3c9a6ad6bca181f18c54132d675613a700f9db2440fb1b4fa13c8bf18a", + "0x80bcb114b2544b80f404a200fc36860ed5e1ad31fe551acd4661d09730c452831751baa9b19d7d311600d267086a70bc", + "0x90dcce03c6f88fc2b08f2b42771eedde90cc5330fe0336e46c1a7d1b5a6c1641e5fcc4e7b3d5db00bd8afca9ec66ed81", + "0xaec15f40805065c98e2965b1ae12a6c9020cfdb094c2d0549acfc7ea2401a5fb48d3ea7d41133cf37c4e096e7ff53eb9", + "0x80e129b735dba49fa627a615d6c273119acec8e219b2f2c4373a332b5f98d66cbbdd688dfbe72a8f8bfefaccc02c50c1", + "0xa9b596da3bdfe23e6799ece5f7975bf7a1979a75f4f546deeaf8b34dfe3e0d623217cb4cf4ccd504cfa3625b88cd53f1", + "0xabcbbb70b16f6e517c0ab4363ab76b46e4ff58576b5f8340e5c0e8cc0e02621b6e23d742d73b015822a238b17cfd7665", + "0xa046937cc6ea6a2e1adae543353a9fe929c1ae4ad655be1cc051378482cf88b041e28b1e9a577e6ccff2d3570f55e200", + "0x831279437282f315e65a60184ef158f0a3dddc15a648dc552bdc88b3e6fe8288d3cfe9f0031846d81350f5e7874b4b33", + "0x993d7916fa213c6d66e7c4cafafc1eaec9a2a86981f91c31eb8a69c5df076c789cbf498a24c84e0ee77af95b42145026", + "0x823907a3b6719f8d49b3a4b7c181bd9bb29fcf842d7c70660c4f351852a1e197ca46cf5e879b47fa55f616fa2b87ce5e", + "0x8d228244e26132b234930ee14c75d88df0943cdb9c276a8faf167d259b7efc1beec2a87c112a6c608ad1600a239e9aae", + "0xab6e55766e5bfb0cf0764ed909a8473ab5047d3388b4f46faeba2d1425c4754c55c6daf6ad4751e634c618b53e549529", + "0xab0cab6860e55a84c5ad2948a7e0989e2b4b1fd637605634b118361497332df32d9549cb854b2327ca54f2bcb85eed8f", + "0xb086b349ae03ef34f4b25a57bcaa5d1b29bd94f9ebf87e22be475adfe475c51a1230c1ebe13506cb72c4186192451658", + "0x8a0b49d8a254ca6d91500f449cbbfbb69bb516c6948ac06808c65595e46773e346f97a5ce0ef7e5a5e0de278af22709c", + "0xac49de11edaaf04302c73c578cc0824bdd165c0d6321be1c421c1950e68e4f3589aa3995448c9699e93c6ebae8803e27", + "0x884f02d841cb5d8f4c60d1402469216b114ab4e93550b5bc1431756e365c4f870a9853449285384a6fa49e12ce6dc654", + "0xb75f3a28fa2cc8d36b49130cb7448a23d73a7311d0185ba803ad55c8219741d451c110f48b786e96c728bc525903a54f", + "0x80ae04dbd41f4a35e33f9de413b6ad518af0919e5a30cb0fa1b061b260420780bb674f828d37fd3b52b5a31673cbd803", + "0xb9a8011eb5fcea766907029bf743b45262db3e49d24f84503687e838651ed11cb64c66281e20a0ae9f6aa51acc552263", + "0x90bfdd75e2dc9cf013e22a5d55d2d2b8a754c96103a17524488e01206e67f8b6d52b1be8c4e3d5307d4fe06d0e51f54c", + "0xb4af353a19b06203a815ec43e79a88578cc678c46f5a954b85bc5c53b84059dddba731f3d463c23bfd5273885c7c56a4", + "0xaa125e96d4553b64f7140e5453ff5d2330318b69d74d37d283e84c26ad672fa00e3f71e530eb7e28be1e94afb9c4612e", + "0xa18e060aee3d49cde2389b10888696436bb7949a79ca7d728be6456a356ea5541b55492b2138da90108bd1ce0e6f5524", + "0x93e55f92bdbccc2de655d14b1526836ea2e52dba65eb3f87823dd458a4cb5079bf22ce6ef625cb6d6bfdd0995ab9a874", + "0x89f5a683526b90c1c3ceebbb8dc824b21cff851ce3531b164f6626e326d98b27d3e1d50982e507d84a99b1e04e86a915", + "0x83d1c38800361633a3f742b1cb2bfc528129496e80232611682ddbe403e92c2ac5373aea0bca93ecb5128b0b2b7a719e", + "0x8ecba560ac94905e19ce8d9c7af217bf0a145d8c8bd38e2db82f5e94cc3f2f26f55819176376b51f154b4aab22056059", + "0xa7e2a4a002b60291924850642e703232994acb4cfb90f07c94d1e0ecd2257bb583443283c20fc6017c37e6bfe85b7366", + "0x93ed7316fa50b528f1636fc6507683a672f4f4403e55e94663f91221cc198199595bd02eef43d609f451acc9d9b36a24", + "0xa1220a8ebc5c50ceed76a74bc3b7e0aa77f6884c71b64b67c4310ac29ce5526cb8992d6abc13ef6c8413ce62486a6795", + "0xb2f6eac5c869ad7f4a25161d3347093e2f70e66cd925032747e901189355022fab3038bca4d610d2f68feb7e719c110b", + "0xb703fa11a4d511ca01c7462979a94acb40b5d933759199af42670eb48f83df202fa0c943f6ab3b4e1cc54673ea3aab1e", + "0xb5422912afbfcb901f84791b04f1ddb3c3fbdc76d961ee2a00c5c320e06d3cc5b5909c3bb805df66c5f10c47a292b13d", + "0xad0934368da823302e1ac08e3ede74b05dfdbfffca203e97ffb0282c226814b65c142e6e15ec1e754518f221f01b30f7", + "0xa1dd302a02e37df15bf2f1147efe0e3c06933a5a767d2d030e1132f5c3ce6b98e216b6145eb39e1e2f74e76a83165b8d", + "0xa346aab07564432f802ae44738049a36f7ca4056df2d8f110dbe7fef4a3e047684dea609b2d03dc6bf917c9c2a47608f", + "0xb96c5f682a5f5d02123568e50f5d0d186e4b2c4c9b956ec7aabac1b3e4a766d78d19bd111adb5176b898e916e49be2aa", + "0x8a96676d56876fc85538db2e806e1cba20fd01aeb9fa3cb43ca6ca94a2c102639f65660db330e5d74a029bb72d6a0b39", + "0xab0048336bd5c3def1a4064eadd49e66480c1f2abb4df46e03afbd8a3342c2c9d74ee35d79f08f4768c1646681440984", + "0x888427bdf76caec90814c57ee1c3210a97d107dd88f7256f14f883ad0f392334b82be11e36dd8bfec2b37935177c7831", + "0xb622b282becf0094a1916fa658429a5292ba30fb48a4c8066ce1ddcefb71037948262a01c95bab6929ed3a76ba5db9fe", + "0xb5b9e005c1f456b6a368a3097634fb455723abe95433a186e8278dceb79d4ca2fbe21f8002e80027b3c531e5bf494629", + "0xa3c6707117a1e48697ed41062897f55d8119403eea6c2ee88f60180f6526f45172664bfee96bf61d6ec0b7fbae6aa058", + "0xb02a9567386a4fbbdb772d8a27057b0be210447348efe6feb935ceec81f361ed2c0c211e54787dc617cdffed6b4a6652", + "0xa9b8364e40ef15c3b5902e5534998997b8493064fa2bea99600def58279bb0f64574c09ba11e9f6f669a8354dd79dc85", + "0x9998a2e553a9aa9a206518fae2bc8b90329ee59ab23005b10972712389f2ec0ee746033c733092ffe43d73d33abbb8ef", + "0x843a4b34d9039bf79df96d79f2d15e8d755affb4d83d61872daf540b68c0a3888cf8fc00d5b8b247b38524bcb3b5a856", + "0x84f7128920c1b0bb40eee95701d30e6fc3a83b7bb3709f16d97e72acbb6057004ee7ac8e8f575936ca9dcb7866ab45f7", + "0x918d3e2222e10e05edb34728162a899ad5ada0aaa491aeb7c81572a9c0d506e31d5390e1803a91ff3bd8e2bb15d47f31", + "0x9442d18e2489613a7d47bb1cb803c8d6f3259d088cd079460976d87f7905ee07dea8f371b2537f6e1d792d36d7e42723", + "0xb491976970fe091995b2ed86d629126523ccf3e9daf8145302faca71b5a71a5da92e0e05b62d7139d3efac5c4e367584", + "0xaa628006235dc77c14cef4c04a308d66b07ac92d377df3de1a2e6ecfe3144f2219ad6d7795e671e1cb37a3641910b940", + "0x99d386adaea5d4981d7306feecac9a555b74ffdc218c907c5aa7ac04abaead0ec2a8237300d42a3fbc464673e417ceed", + "0x8f78e8b1556f9d739648ea3cab9606f8328b52877fe72f9305545a73b74d49884044ba9c1f1c6db7d9b7c7b7c661caba", + "0x8fb357ae49932d0babdf74fc7aa7464a65d3b6a2b3acf4f550b99601d3c0215900cfd67f2b6651ef94cfc323bac79fae", + "0x9906f2fa25c0290775aa001fb6198113d53804262454ae8b83ef371b5271bde189c0460a645829cb6c59f9ee3a55ce4d", + "0x8f4379b3ebb50e052325b27655ca6a82e6f00b87bf0d2b680d205dd2c7afdc9ff32a9047ae71a1cdf0d0ce6b9474d878", + "0xa85534e88c2bd43c043792eaa75e50914b21741a566635e0e107ae857aed0412035f7576cf04488ade16fd3f35fdbb87", + "0xb4ce93199966d3c23251ca7f28ec5af7efea1763d376b0385352ffb2e0a462ef95c69940950278cf0e3dafd638b7bd36", + "0xb10cb3d0317dd570aa73129f4acf63c256816f007607c19b423fb42f65133ce21f2f517e0afb41a5378cccf893ae14d0", + "0xa9b231c9f739f7f914e5d943ed9bff7eba9e2c333fbd7c34eb1648a362ee01a01af6e2f7c35c9fe962b11152cddf35de", + "0x99ff6a899e156732937fb81c0cced80ae13d2d44c40ba99ac183aa246103b31ec084594b1b7feb96da58f4be2dd5c0ed", + "0x8748d15d18b75ff2596f50d6a9c4ce82f61ecbcee123a6ceae0e43cab3012a29b6f83cf67b48c22f6f9d757c6caf76b2", + "0xb88ab05e4248b7fb634cf640a4e6a945d13e331237410f7217d3d17e3e384ddd48897e7a91e4516f1b9cbd30f35f238b", + "0x8d826deaeeb84a3b2d2c04c2300ca592501f992810582d6ae993e0d52f6283a839dba66c6c72278cff5871802b71173b", + "0xb36fed027c2f05a5ef625ca00b0364b930901e9e4420975b111858d0941f60e205546474bb25d6bfa6928d37305ae95f", + "0xaf2fcfc6b87967567e8b8a13a4ed914478185705724e56ce68fb2df6d1576a0cf34a61e880997a0d35dc2c3276ff7501", + "0xac351b919cd1fbf106feb8af2c67692bfcddc84762d18cea681cfa7470a5644839caace27efee5f38c87d3df306f4211", + "0x8d6665fb1d4d8d1fa23bd9b8a86e043b8555663519caac214d1e3e3effbc6bee7f2bcf21e645f77de0ced279d69a8a8b", + "0xa9fc1c2061756b2a1a169c1b149f212ff7f0d2488acd1c5a0197eba793cffa593fc6d1d1b40718aa75ca3ec77eff10e1", + "0xaff64f0fa009c7a6cf0b8d7a22ddb2c8170c3cb3eec082e60d5aadb00b0040443be8936d728d99581e33c22178c41c87", + "0x82e0b181adc5e3b1c87ff8598447260e839d53debfae941ebea38265575546c3a74a14b4325a030833a62ff6c52d9365", + "0xb7ad43cbb22f6f892c2a1548a41dc120ab1f4e1b8dea0cb6272dd9cb02054c542ecabc582f7e16de709d48f5166cae86", + "0x985e0c61094281532c4afb788ecb2dfcba998e974b5d4257a22040a161883908cdd068fe80f8eb49b8953cfd11acf43a", + "0xae46895c6d67ea6d469b6c9c07b9e5d295d9ae73b22e30da4ba2c973ba83a130d7eef39717ec9d0f36e81d56bf742671", + "0x8600177ea1f7e7ef90514b38b219a37dedfc39cb83297e4c7a5b479817ef56479d48cf6314820960c751183f6edf8b0e", + "0xb9208ec1c1d7a1e99b59c62d3e4e61dfb706b0e940d09d3abfc3454c19749083260614d89cfd7e822596c3cdbcc6bb95", + "0xa1e94042c796c2b48bc724352d2e9f3a22291d9a34705993357ddb6adabd76da6fc25dac200a8cb0b5bbd99ecddb7af6", + "0xb29c3adedd0bcad8a930625bc4dfdc3552a9afd5ca6dd9c0d758f978068c7982b50b711aa0eb5b97f2b84ee784637835", + "0xaf0632a238bb1f413c7ea8e9b4c3d68f2827bd2e38cd56024391fba6446ac5d19a780d0cfd4a78fe497d537b766a591a", + "0xaaf6e7f7d54f8ef5e2e45dd59774ecbeecf8683aa70483b2a75be6a6071b5981bbaf1627512a65d212817acdfab2e428", + "0x8c751496065da2e927cf492aa5ca9013b24f861d5e6c24b30bbf52ec5aaf1905f40f9a28175faef283dd4ed4f2182a09", + "0x8952377d8e80a85cf67d6b45499f3bad5fd452ea7bcd99efc1b066c4720d8e5bff1214cea90fd1f972a7f0baac3d29be", + "0xa1946ee543d1a6e21f380453be4d446e4130950c5fc3d075794eb8260f6f52d0a795c1ff91d028a648dc1ce7d9ab6b47", + "0x89f3fefe37af31e0c17533d2ca1ce0884cc1dc97c15cbfab9c331b8debd94781c9396abef4bb2f163d09277a08d6adf0", + "0xa2753f1e6e1a154fb117100a5bd9052137add85961f8158830ac20541ab12227d83887d10acf7fd36dcaf7c2596d8d23", + "0x814955b4198933ee11c3883863b06ff98c7eceb21fc3e09df5f916107827ccf3323141983e74b025f46ae00284c9513b", + "0x8cc5c6bb429073bfef47cae7b3bfccb0ffa076514d91a1862c6bda4d581e0df87db53cc6c130bf8a7826304960f5a34e", + "0x909f22c1f1cdc87f7be7439c831a73484a49acbf8f23d47087d7cf867c64ef61da3bde85dc57d705682b4c3fc710d36e", + "0x8048fee7f276fcd504aed91284f28e73693615e0eb3858fa44bcf79d7285a9001c373b3ef71d9a3054817ba293ebe28c", + "0x94400e5cf5d2700ca608c5fe35ce14623f71cc24959f2bc27ca3684092850f76b67fb1f07ca9e5b2ca3062cf8ad17bd4", + "0x81c2ae7d4d1b17f8b6de6a0430acc0d58260993980fe48dc2129c4948269cdc74f9dbfbf9c26b19360823fd913083d48", + "0x8c41fe765128e63f6889d6a979f6a4342300327c8b245a8cfe3ecfbcac1e09c3da30e2a1045b24b78efc6d6d50c8c6ac", + "0xa5dd4ae51ae48c8be4b218c312ade226cffce671cf121cb77810f6c0990768d6dd767badecb5c69921d5574d5e8433d3", + "0xb7642e325f4ba97ae2a39c1c9d97b35aafd49d53dba36aed3f3cb0ca816480b3394079f46a48252d46596559c90f4d58", + "0xae87375b40f35519e7bd4b1b2f73cd0b329b0c2cb9d616629342a71c6c304338445eda069b78ea0fbe44087f3de91e09", + "0xb08918cb6f736855e11d3daca1ddfbdd61c9589b203b5493143227bf48e2c77c2e8c94b0d1aa2fab2226e0eae83f2681", + "0xac36b84a4ac2ebd4d6591923a449c564e3be8a664c46092c09e875c2998eba16b5d32bfd0882fd3851762868e669f0b1", + "0xa44800a3bb192066fa17a3f29029a23697240467053b5aa49b9839fb9b9b8b12bcdcbfc557f024b61f4f51a9aacdefcb", + "0x9064c688fec23441a274cdf2075e5a449caf5c7363cc5e8a5dc9747183d2e00a0c69f2e6b3f6a7057079c46014c93b3b", + "0xaa367b021469af9f5b764a79bb3afbe2d87fe1e51862221672d1a66f954b165778b7c27a705e0f93841fab4c8468344d", + "0xa1a8bfc593d4ab71f91640bc824de5c1380ab2591cfdafcbc78a14b32de3c0e15f9d1b461d85c504baa3d4232c16bb53", + "0x97df48da1799430f528184d30b6baa90c2a2f88f34cdfb342d715339c5ebd6d019aa693cea7c4993daafc9849063a3aa", + "0xabd923831fbb427e06e0dd335253178a9e5791395c84d0ab1433c07c53c1209161097e9582fb8736f8a60bde62d8693e", + "0x84cd1a43f1a438b43dc60ffc775f646937c4f6871438163905a3cebf1115f814ccd38a6ccb134130bff226306e412f32", + "0x91426065996b0743c5f689eb3ca68a9f7b9e4d01f6c5a2652b57fa9a03d8dc7cd4bdbdab0ca5a891fee1e97a7f00cf02", + "0xa4bee50249db3df7fd75162b28f04e57c678ba142ce4d3def2bc17bcb29e4670284a45f218dad3969af466c62a903757", + "0x83141ebcc94d4681404e8b67a12a46374fded6df92b506aff3490d875919631408b369823a08b271d006d5b93136f317", + "0xa0ea1c8883d58d5a784da3d8c8a880061adea796d7505c1f903d07c287c5467f71e4563fc0faafbc15b5a5538b0a7559", + "0x89d9d480574f201a87269d26fb114278ed2c446328df431dc3556e3500e80e4cd01fcac196a2459d8646361ebda840df", + "0x8bf302978973632dd464bec819bdb91304712a3ec859be071e662040620422c6e75eba6f864f764cffa2799272efec39", + "0x922f666bc0fd58b6d7d815c0ae4f66d193d32fc8382c631037f59eeaeae9a8ca6c72d08e72944cf9e800b8d639094e77", + "0x81ad8714f491cdff7fe4399f2eb20e32650cff2999dd45b9b3d996d54a4aba24cc6c451212e78c9e5550368a1a38fb3f", + "0xb58fcf4659d73edb73175bd9139d18254e94c3e32031b5d4b026f2ed37aa19dca17ec2eb54c14340231615277a9d347e", + "0xb365ac9c2bfe409b710928c646ea2fb15b28557e0f089d39878e365589b9d1c34baf5566d20bb28b33bb60fa133f6eff", + "0x8fcae1d75b53ab470be805f39630d204853ca1629a14158bac2f52632277d77458dec204ff84b7b2d77e641c2045be65", + "0xa03efa6bebe84f4f958a56e2d76b5ba4f95dd9ed7eb479edc7cc5e646c8d4792e5b0dfc66cc86aa4b4afe2f7a4850760", + "0xaf1c823930a3638975fb0cc5c59651771b2719119c3cd08404fbd4ce77a74d708cefbe3c56ea08c48f5f10e6907f338f", + "0x8260c8299b17898032c761c325ac9cabb4c5b7e735de81eacf244f647a45fb385012f4f8df743128888c29aefcaaad16", + "0xab2f37a573c82e96a8d46198691cd694dfa860615625f477e41f91b879bc58a745784fccd8ffa13065834ffd150d881d", + "0x986c746c9b4249352d8e5c629e8d7d05e716b3c7aab5e529ca969dd1e984a14b5be41528baef4c85d2369a42d7209216", + "0xb25e32da1a8adddf2a6080725818b75bc67240728ad1853d90738485d8924ea1e202df0a3034a60ffae6f965ec55cf63", + "0xa266e627afcebcefea6b6b44cbc50f5c508f7187e87d047b0450871c2a030042c9e376f3ede0afcf9d1952f089582f71", + "0x86c3bbca4c0300606071c0a80dbdec21ce1dd4d8d4309648151c420854032dff1241a1677d1cd5de4e4de4385efda986", + "0xb9a21a1fe2d1f3273a8e4a9185abf2ff86448cc98bfa435e3d68306a2b8b4a6a3ea33a155be3cb62a2170a86f77679a5", + "0xb117b1ea381adce87d8b342cba3a15d492ff2d644afa28f22424cb9cbc820d4f7693dfc1a4d1b3697046c300e1c9b4c8", + "0x9004c425a2e68870d6c69b658c344e3aa3a86a8914ee08d72b2f95c2e2d8a4c7bb0c6e7e271460c0e637cec11117bf8e", + "0x86a18aa4783b9ebd9131580c8b17994825f27f4ac427b0929a1e0236907732a1c8139e98112c605488ee95f48bbefbfc", + "0x84042243b955286482ab6f0b5df4c2d73571ada00716d2f737ca05a0d2e88c6349e8ee9e67934cfee4a1775dbf7f4800", + "0x92c2153a4733a62e4e1d5b60369f3c26777c7d01cd3c8679212660d572bd3bac9b8a8a64e1f10f7dbf5eaa7579c4e423", + "0x918454b6bb8e44a2afa144695ba8d48ae08d0cdfef4ad078f67709eddf3bb31191e8b006f04e82ea45a54715ef4d5817", + "0xacf0b54f6bf34cf6ed6c2b39cf43194a40d68de6bcf1e4b82c34c15a1343e9ac3737885e1a30b78d01fa3a5125463db8", + "0xa7d60dbe4b6a7b054f7afe9ee5cbbfeca0d05dc619e6041fa2296b549322529faddb8a11e949562309aecefb842ac380", + "0x91ffb53e6d7e5f11159eaf13e783d6dbdfdb1698ed1e6dbf3413c6ea23492bbb9e0932230a9e2caac8fe899a17682795", + "0xb6e8d7be5076ee3565d5765a710c5ecf17921dd3cf555c375d01e958a365ae087d4a88da492a5fb81838b7b92bf01143", + "0xa8c6b763de2d4b2ed42102ef64eccfef31e2fb2a8a2776241c82912fa50fc9f77f175b6d109a97ede331307c016a4b1a", + "0x99839f86cb700c297c58bc33e28d46b92931961548deac29ba8df91d3e11721b10ea956c8e16984f9e4acf1298a79b37", + "0x8c2e2c338f25ea5c25756b7131cde0d9a2b35abf5d90781180a00fe4b8e64e62590dc63fe10a57fba3a31c76d784eb01", + "0x9687d7df2f41319ca5469d91978fed0565a5f11f829ebadaa83db92b221755f76c6eacd7700735e75c91e257087512e3", + "0x8795fdfb7ff8439c58b9bf58ed53873d2780d3939b902b9ddaaa4c99447224ced9206c3039a23c2c44bcc461e2bb637f", + "0xa803697b744d2d087f4e2307218d48fa88620cf25529db9ce71e2e3bbcc65bac5e8bb9be04777ef7bfb5ed1a5b8e6170", + "0x80f3d3efbbb9346ddd413f0a8e36b269eb5d7ff6809d5525ff9a47c4bcab2c01b70018b117f6fe05253775612ff70c6b", + "0x9050e0e45bcc83930d4c505af35e5e4d7ca01cd8681cba92eb55821aececcebe32bb692ebe1a4daac4e7472975671067", + "0x8d206812aac42742dbaf233e0c080b3d1b30943b54b60283515da005de05ea5caa90f91fedcfcba72e922f64d7040189", + "0xa2d44faaeb2eff7915c83f32b13ca6f31a6847b1c1ce114ea240bac3595eded89f09b2313b7915ad882292e2b586d5b4", + "0x961776c8576030c39f214ea6e0a3e8b3d32f023d2600958c098c95c8a4e374deeb2b9dc522adfbd6bda5949bdc09e2a2", + "0x993fa7d8447407af0fbcd9e6d77f815fa5233ab00674efbcf74a1f51c37481445ae291cc7b76db7c178f9cb0e570e0fc", + "0xabd5b1c78e05f9d7c8cc99bdaef8b0b6a57f2daf0f02bf492bec48ea4a27a8f1e38b5854da96efff11973326ff980f92", + "0x8f15af4764bc275e6ccb892b3a4362cacb4e175b1526a9a99944e692fe6ccb1b4fc19abf312bb2a089cb1f344d91a779", + "0xa09b27ccd71855512aba1d0c30a79ffbe7f6707a55978f3ced50e674b511a79a446dbc6d7946add421ce111135a460af", + "0x94b2f98ce86a9271fbd4153e1fc37de48421fe3490fb3840c00f2d5a4d0ba8810c6a32880b002f6374b59e0a7952518b", + "0x8650ac644f93bbcb88a6a0f49fee2663297fd4bc6fd47b6a89b9d8038d32370438ab3a4775ec9b58cb10aea8a95ef7b6", + "0x95e5c2f2e84eed88c6980bbba5a1c0bb375d5a628bff006f7516d45bb7d723da676add4fdd45956f312e7bab0f052644", + "0xb3278a3fa377ac93af7cfc9453f8cb594aae04269bbc99d2e0e45472ff4b6a2f97a26c4c57bf675b9d86f5e77a5d55d1", + "0xb4bcbe6eb666a206e2ea2f877912c1d3b5bdbd08a989fc4490eb06013e1a69ad1ba08bcdac048bf29192312be399077b", + "0xa76d70b78c99fffcbf9bb9886eab40f1ea4f99a309710b660b64cbf86057cbcb644d243f6e341711bb7ef0fedf0435a7", + "0xb2093c1ee945dca7ac76ad5aed08eae23af31dd5a77c903fd7b6f051f4ab84425d33a03c3d45bf2907bc93c02d1f3ad8", + "0x904b1f7534e053a265b22d20be859912b9c9ccb303af9a8d6f1d8f6ccdc5c53eb4a45a1762b880d8444d9be0cd55e7f9", + "0x8f664a965d65bc730c9ef1ec7467be984d4b8eb46bd9b0d64e38e48f94e6e55dda19aeac82cbcf4e1473440e64c4ca18", + "0x8bcee65c4cc7a7799353d07b114c718a2aae0cd10a3f22b7eead5185d159dafd64852cb63924bf87627d176228878bce", + "0x8c78f2e3675096fef7ebaa898d2615cd50d39ca3d8f02b9bdfb07e67da648ae4be3da64838dffc5935fd72962c4b96c7", + "0x8c40afd3701629421fec1df1aac4e849384ef2e80472c0e28d36cb1327acdf2826f99b357f3d7afdbc58a6347fc40b3c", + "0xa197813b1c65a8ea5754ef782522a57d63433ef752215ecda1e7da76b0412ee619f58d904abd2e07e0c097048b6ae1dd", + "0xa670542629e4333884ad7410f9ea3bd6f988df4a8f8a424ca74b9add2312586900cf9ae8bd50411f9146e82626b4af56", + "0xa19875cc07ab84e569d98b8b67fb1dbbdfb59093c7b748fae008c8904a6fd931a63ca8d03ab5fea9bc8d263568125a9b", + "0xb57e7f68e4eb1bd04aafa917b1db1bdab759a02aa8a9cdb1cba34ba8852b5890f655645c9b4e15d5f19bf37e9f2ffe9f", + "0x8abe4e2a4f6462b6c64b3f10e45db2a53c2b0d3c5d5443d3f00a453e193df771eda635b098b6c8604ace3557514027af", + "0x8459e4fb378189b22b870a6ef20183deb816cefbf66eca1dc7e86d36a2e011537db893729f500dc154f14ce24633ba47", + "0x930851df4bc7913c0d8c0f7bd3b071a83668987ed7c397d3d042fdc0d9765945a39a3bae83da9c88cb6b686ed8aeeb26", + "0x8078c9e5cd05e1a8c932f8a1d835f61a248b6e7133fcbb3de406bf4ffc0e584f6f9f95062740ba6008d98348886cf76b", + "0xaddff62bb29430983fe578e3709b0949cdc0d47a13a29bc3f50371a2cb5c822ce53e2448cfaa01bcb6e0aa850d5a380e", + "0x9433add687b5a1e12066721789b1db2edf9b6558c3bdc0f452ba33b1da67426abe326e9a34d207bfb1c491c18811bde1", + "0x822beda3389963428cccc4a2918fa9a8a51cf0919640350293af70821967108cded5997adae86b33cb917780b097f1ca", + "0xa7a9f52bda45e4148ed56dd176df7bd672e9b5ed18888ccdb405f47920fdb0844355f8565cefb17010b38324edd8315f", + "0xb35c3a872e18e607b2555c51f9696a17fa18da1f924d503b163b4ec9fe22ed0c110925275cb6c93ce2d013e88f173d6a", + "0xadf34b002b2b26ab84fc1bf94e05bd8616a1d06664799ab149363c56a6e0c807fdc473327d25632416e952ea327fcd95", + "0xae4a6b9d22a4a3183fac29e2551e1124a8ce4a561a9a2afa9b23032b58d444e6155bb2b48f85c7b6d70393274e230db7", + "0xa2ea3be4fc17e9b7ce3110284038d46a09e88a247b6971167a7878d9dcf36925d613c382b400cfa4f37a3ebea3699897", + "0x8e5863786b641ce3140fbfe37124d7ad3925472e924f814ebfc45959aaf3f61dc554a597610b5defaecc85b59a99b50f", + "0xaefde3193d0f700d0f515ab2aaa43e2ef1d7831c4f7859f48e52693d57f97fa9e520090f3ed700e1c966f4b76048e57f", + "0x841a50f772956622798e5cd208dc7534d4e39eddee30d8ce133383d66e5f267e389254a0cdae01b770ecd0a9ca421929", + "0x8fbc2bfd28238c7d47d4c03b1b910946c0d94274a199575e5b23242619b1de3497784e646a92aa03e3e24123ae4fcaba", + "0x926999579c8eec1cc47d7330112586bdca20b4149c8b2d066f527c8b9f609e61ce27feb69db67eea382649c6905efcf9", + "0xb09f31f305efcc65589adf5d3690a76cf339efd67cd43a4e3ced7b839507466e4be72dd91f04e89e4bbef629d46e68c0", + "0xb917361f6b95f759642638e0b1d2b3a29c3bdef0b94faa30de562e6078c7e2d25976159df3edbacbf43614635c2640b4", + "0x8e7e8a1253bbda0e134d62bfe003a2669d471b47bd2b5cde0ff60d385d8e62279d54022f5ac12053b1e2d3aaa6910b4c", + "0xb69671a3c64e0a99d90b0ed108ce1912ff8ed983e4bddd75a370e9babde25ee1f5efb59ec707edddd46793207a8b1fe7", + "0x910b2f4ebd37b7ae94108922b233d0920b4aba0bd94202c70f1314418b548d11d8e9caa91f2cd95aff51b9432d122b7f", + "0x82f645c90dfb52d195c1020346287c43a80233d3538954548604d09fbab7421241cde8593dbc4acc4986e0ea39a27dd9", + "0x8fee895f0a140d88104ce442fed3966f58ff9d275e7373483f6b4249d64a25fb5374bbdc6bce6b5ab0270c2847066f83", + "0x84f5bd7aab27b2509397aeb86510dd5ac0a53f2c8f73799bf720f2f87a52277f8d6b0f77f17bc80739c6a7119b7eb062", + "0x9903ceced81099d7e146e661bcf01cbaccab5ba54366b85e2177f07e2d8621e19d9c9c3eee14b9266de6b3f9b6ea75ae", + "0xb9c16ea2a07afa32dd6c7c06df0dec39bca2067a9339e45475c98917f47e2320f6f235da353fd5e15b477de97ddc68dd", + "0x9820a9bbf8b826bec61ebf886de2c4f404c1ebdc8bab82ee1fea816d9de29127ce1852448ff717a3fe8bbfe9e92012e5", + "0x817224d9359f5da6f2158c2c7bf9165501424f063e67ba9859a07ab72ee2ee62eb00ca6da821cfa19065c3282ca72c74", + "0x94b95c465e6cb00da400558a3c60cfec4b79b27e602ca67cbc91aead08de4b6872d8ea096b0dc06dca4525c8992b8547", + "0xa2b539a5bccd43fa347ba9c15f249b417997c6a38c63517ca38394976baa08e20be384a360969ff54e7e721db536b3e5", + "0x96caf707e34f62811ee8d32ccf28d8d6ec579bc33e424d0473529af5315c456fd026aa910c1fed70c91982d51df7d3ca", + "0x8a77b73e890b644c6a142bdbac59b22d6a676f3b63ddafb52d914bb9d395b8bf5aedcbcc90429337df431ebd758a07a6", + "0x8857830a7351025617a08bc44caec28d2fae07ebf5ffc9f01d979ce2a53839a670e61ae2783e138313929129790a51a1", + "0xaa3e420321ed6f0aa326d28d1a10f13facec6f605b6218a6eb9cbc074801f3467bf013a456d1415a5536f12599efa3d3", + "0x824aed0951957b00ea2f3d423e30328a3527bf6714cf9abbae84cf27e58e5c35452ba89ccc011de7c68c75d6e021d8f1", + "0xa2e87cc06bf202e953fb1081933d8b4445527dde20e38ed1a4f440144fd8fa464a2b73e068b140562e9045e0f4bd3144", + "0xae3b8f06ad97d7ae3a5e5ca839efff3e4824dc238c0c03fc1a8d2fc8aa546cdfd165b784a31bb4dec7c77e9305b99a4b", + "0xb30c3e12395b1fb8b776f3ec9f87c70e35763a7b2ddc68f0f60a4982a84017f27c891a98561c830038deb033698ed7fc", + "0x874e507757cd1177d0dff0b0c62ce90130324442a33da3b2c8ee09dbca5d543e3ecfe707e9f1361e7c7db641c72794bb", + "0xb53012dd10b5e7460b57c092eaa06d6502720df9edbbe3e3f61a9998a272bf5baaac4a5a732ad4efe35d6fac6feca744", + "0x85e6509d711515534d394e6cacbed6c81da710074d16ef3f4950bf2f578d662a494d835674f79c4d6315bced4defc5f0", + "0xb6132b2a34b0905dcadc6119fd215419a7971fe545e52f48b768006944b4a9d7db1a74b149e2951ea48c083b752d0804", + "0x989867da6415036d19b4bacc926ce6f4df7a556f50a1ba5f3c48eea9cefbb1c09da81481c8009331ee83f0859185e164", + "0x960a6c36542876174d3fbc1505413e29f053ed87b8d38fef3af180491c7eff25200b45dd5fe5d4d8e63c7e8c9c00f4c8", + "0x9040b59bd739d9cc2e8f6e894683429e4e876a8106238689ff4c22770ae5fdae1f32d962b30301fa0634ee163b524f35", + "0xaf3fcd0a45fe9e8fe256dc7eab242ef7f582dd832d147444483c62787ac820fafc6ca55d639a73f76bfa5e7f5462ab8f", + "0xb934c799d0736953a73d91e761767fdb78454355c4b15c680ce08accb57ccf941b13a1236980001f9e6195801cffd692", + "0x8871e8e741157c2c326b22cf09551e78da3c1ec0fc0543136f581f1550f8bab03b0a7b80525c1e99812cdbf3a9698f96", + "0xa8a977f51473a91d178ee8cfa45ffef8d6fd93ab1d6e428f96a3c79816d9c6a93cd70f94d4deda0125fd6816e30f3bea", + "0xa7688b3b0a4fc1dd16e8ba6dc758d3cfe1b7cf401c31739484c7fa253cce0967df1b290769bcefc9d23d3e0cb19e6218", + "0x8ae84322662a57c6d729e6ff9d2737698cc2da2daeb1f39e506618750ed23442a6740955f299e4a15dda6db3e534d2c6", + "0xa04a961cdccfa4b7ef83ced17ab221d6a043b2c718a0d6cc8e6f798507a31f10bf70361f70a049bc8058303fa7f96864", + "0xb463e39732a7d9daec8a456fb58e54b30a6e160aa522a18b9a9e836488cce3342bcbb2e1deab0f5e6ec0a8796d77197d", + "0xb1434a11c6750f14018a2d3bcf94390e2948f4f187e93bb22070ca3e5393d339dc328cbfc3e48815f51929465ffe7d81", + "0x84ff81d73f3828340623d7e3345553610aa22a5432217ef0ebd193cbf4a24234b190c65ca0873c22d10ea7b63bd1fbed", + "0xb6fe2723f0c47757932c2ddde7a4f8434f665612f7b87b4009c2635d56b6e16b200859a8ade49276de0ef27a2b6c970a", + "0x9742884ed7cd52b4a4a068a43d3faa02551a424136c85a9313f7cb58ea54c04aa83b0728fd741d1fe39621e931e88f8f", + "0xb7d2d65ea4d1ad07a5dee39e40d6c03a61264a56b1585b4d76fc5b2a68d80a93a42a0181d432528582bf08d144c2d6a9", + "0x88c0f66bada89f8a43e5a6ead2915088173d106c76f724f4a97b0f6758aed6ae5c37c373c6b92cdd4aea8f6261f3a374", + "0x81f9c43582cb42db3900747eb49ec94edb2284999a499d1527f03315fd330e5a509afa3bff659853570e9886aab5b28b", + "0x821f9d27d6beb416abf9aa5c79afb65a50ed276dbda6060103bc808bcd34426b82da5f23e38e88a55e172f5c294b4d40", + "0x8ba307b9e7cb63a6c4f3851b321aebfdb6af34a5a4c3bd949ff7d96603e59b27ff4dc4970715d35f7758260ff942c9e9", + "0xb142eb6c5f846de33227d0bda61d445a7c33c98f0a8365fe6ab4c1fabdc130849be597ef734305894a424ea715372d08", + "0xa732730ae4512e86a741c8e4c87fee8a05ee840fec0e23b2e037d58dba8dde8d10a9bc5191d34d00598941becbbe467f", + "0xadce6f7c30fd221f6b10a0413cc76435c4bb36c2d60bca821e5c67409fe9dbb2f4c36ef85eb3d734695e4be4827e9fd3", + "0xa74f00e0f9b23aff7b2527ce69852f8906dab9d6abe62ecd497498ab21e57542e12af9918d4fd610bb09e10b0929c510", + "0xa593b6b0ef26448ce4eb3ab07e84238fc020b3cb10d542ff4b16d4e2be1bcde3797e45c9cf753b8dc3b0ffdb63984232", + "0xaed3913afccf1aa1ac0eb4980eb8426d0baccebd836d44651fd72af00d09fac488a870223c42aca3ceb39752070405ae", + "0xb2c44c66a5ea7fde626548ba4cef8c8710191343d3dadfd3bb653ce715c0e03056a5303a581d47dde66e70ea5a2d2779", + "0x8e5029b2ccf5128a12327b5103f7532db599846e422531869560ceaff392236434d87159f597937dbf4054f810c114f4", + "0x82beed1a2c4477e5eb39fc5b0e773b30cfec77ef2b1bf17eadaf60eb35b6d0dd9d8cf06315c48d3546badb3f21cd0cca", + "0x90077bd6cc0e4be5fff08e5d07a5a158d36cebd1d1363125bc4fae0866ffe825b26f933d4ee5427ba5cd0c33c19a7b06", + "0xa7ec0d8f079970e8e34f0ef3a53d3e0e45428ddcef9cc776ead5e542ef06f3c86981644f61c5a637e4faf001fb8c6b3e", + "0xae6d4add6d1a6f90b22792bc9d40723ee6850c27d0b97eefafd5b7fd98e424aa97868b5287cc41b4fbd7023bca6a322c", + "0x831aa917533d077da07c01417feaa1408846363ba2b8d22c6116bb858a95801547dd88b7d7fa1d2e3f0a02bdeb2e103d", + "0x96511b860b07c8a5ed773f36d4aa9d02fb5e7882753bf56303595bcb57e37ccc60288887eb83bef08c657ec261a021a2", + "0x921d2a3e7e9790f74068623de327443666b634c8443aba80120a45bba450df920b2374d96df1ce3fb1b06dd06f8cf6e3", + "0xaa74451d51fe82b4581ead8e506ec6cd881010f7e7dd51fc388eb9a557db5d3c6721f81c151d08ebd9c2591689fbc13e", + "0xa972bfbcf4033d5742d08716c927c442119bdae336bf5dff914523b285ccf31953da2733759aacaa246a9af9f698342c", + "0xad1fcd0cae0e76840194ce4150cb8a56ebed728ec9272035f52a799d480dfc85840a4d52d994a18b6edb31e79be6e8ad", + "0xa2c69fe1d36f235215432dad48d75887a44c99dfa0d78149acc74087da215a44bdb5f04e6eef88ff7eff80a5a7decc77", + "0xa94ab2af2b6ee1bc6e0d4e689ca45380d9fbd3c5a65b9bd249d266a4d4c07bf5d5f7ef2ae6000623aee64027892bf8fe", + "0x881ec1fc514e926cdc66480ac59e139148ff8a2a7895a49f0dff45910c90cdda97b66441a25f357d6dd2471cddd99bb3", + "0x884e6d3b894a914c8cef946a76d5a0c8351843b2bffa2d1e56c6b5b99c84104381dd1320c451d551c0b966f4086e60f9", + "0x817c6c10ce2677b9fc5223500322e2b880583254d0bb0d247d728f8716f5e05c9ff39f135854342a1afecd9fbdcf7c46", + "0xaaf4a9cb686a14619aa1fc1ac285dd3843ac3dd99f2b2331c711ec87b03491c02f49101046f3c5c538dc9f8dba2a0ac2", + "0x97ecea5ce53ca720b5d845227ae61d70269a2f53540089305c86af35f0898bfd57356e74a8a5e083fa6e1ea70080bd31", + "0xa22d811e1a20a75feac0157c418a4bfe745ccb5d29466ffa854dca03e395b6c3504a734341746b2846d76583a780b32e", + "0x940cbaa0d2b2db94ae96b6b9cf2deefbfd059e3e5745de9aec4a25f0991b9721e5cd37ef71c631575d1a0c280b01cd5b", + "0xae33cb4951191258a11044682de861bf8d92d90ce751b354932dd9f3913f542b6a0f8a4dc228b3cd9244ac32c4582832", + "0xa580df5e58c4274fe0f52ac2da1837e32f5c9db92be16c170187db4c358f43e5cfdda7c5911dcc79d77a5764e32325f5", + "0x81798178cb9d8affa424f8d3be67576ba94d108a28ccc01d330c51d5a63ca45bb8ca63a2f569b5c5fe1303cecd2d777f", + "0x89975b91b94c25c9c3660e4af4047a8bacf964783010820dbc91ff8281509379cb3b24c25080d5a01174dd9a049118d5", + "0xa7327fcb3710ed3273b048650bde40a32732ef40a7e58cf7f2f400979c177944c8bc54117ba6c80d5d4260801dddab79", + "0x92b475dc8cb5be4b90c482f122a51bcb3b6c70593817e7e2459c28ea54a7845c50272af38119406eaadb9bcb993368d0", + "0x9645173e9ecefc4f2eae8363504f7c0b81d85f8949a9f8a6c01f2d49e0a0764f4eacecf3e94016dd407fc14494fce9f9", + "0x9215fd8983d7de6ae94d35e6698226fc1454977ae58d42d294be9aad13ac821562ad37d5e7ee5cdfe6e87031d45cd197", + "0x810360a1c9b88a9e36f520ab5a1eb8bed93f52deefbe1312a69225c0a08edb10f87cc43b794aced9c74220cefcc57e7d", + "0xad7e810efd61ed4684aeda9ed8bb02fb9ae4b4b63fda8217d37012b94ff1b91c0087043bfa4e376f961fff030c729f3b", + "0x8b07c95c6a06db8738d10bb03ec11b89375c08e77f0cab7e672ce70b2685667ca19c7e1c8b092821d31108ea18dfd4c7", + "0x968825d025ded899ff7c57245250535c732836f7565eab1ae23ee7e513201d413c16e1ba3f5166e7ac6cf74de8ceef4f", + "0x908243370c5788200703ade8164943ad5f8c458219186432e74dbc9904a701ea307fd9b94976c866e6c58595fd891c4b", + "0x959969d16680bc535cdc6339e6186355d0d6c0d53d7bbfb411641b9bf4b770fd5f575beef5deec5c4fa4d192d455c350", + "0xad177f4f826a961adeac76da40e2d930748effff731756c797eddc4e5aa23c91f070fb69b19221748130b0961e68a6bb", + "0x82f8462bcc25448ef7e0739425378e9bb8a05e283ce54aae9dbebaf7a3469f57833c9171672ad43a79778366c72a5e37", + "0xa28fb275b1845706c2814d9638573e9bc32ff552ebaed761fe96fdbce70395891ca41c400ae438369264e31a2713b15f", + "0x8a9c613996b5e51dadb587a787253d6081ea446bf5c71096980bf6bd3c4b69905062a8e8a3792de2d2ece3b177a71089", + "0x8d5aefef9f60cb27c1db2c649221204dda48bb9bf8bf48f965741da051340e8e4cab88b9d15c69f3f84f4c854709f48a", + "0x93ebf2ca6ad85ab6deace6de1a458706285b31877b1b4d7dcb9d126b63047efaf8c06d580115ec9acee30c8a7212fa55", + "0xb3ee46ce189956ca298057fa8223b7fd1128cf52f39159a58bca03c71dd25161ac13f1472301f72aef3e1993fe1ab269", + "0xa24d7a8d066504fc3f5027ccb13120e2f22896860e02c45b5eba1dbd512d6a17c28f39155ea581619f9d33db43a96f92", + "0xae9ceacbfe12137db2c1a271e1b34b8f92e4816bad1b3b9b6feecc34df0f8b3b0f7ed0133acdf59c537d43d33fc8d429", + "0x83967e69bf2b361f86361bd705dce0e1ad26df06da6c52b48176fe8dfcbeb03c462c1a4c9e649eff8c654b18c876fdef", + "0x9148e6b814a7d779c19c31e33a068e97b597de1f8100513db3c581190513edc4d544801ce3dd2cf6b19e0cd6daedd28a", + "0x94ccdafc84920d320ed22de1e754adea072935d3c5f8c2d1378ebe53d140ea29853f056fb3fb1e375846061a038cc9bc", + "0xafb43348498c38b0fa5f971b8cdd3a62c844f0eb52bc33daf2f67850af0880fce84ecfb96201b308d9e6168a0d443ae3", + "0x86d5736520a83538d4cd058cc4b4e84213ed00ebd6e7af79ae787adc17a92ba5359e28ba6c91936d967b4b28d24c3070", + "0xb5210c1ff212c5b1e9ef9126e08fe120a41e386bb12c22266f7538c6d69c7fd8774f11c02b81fd4e88f9137b020801fe", + "0xb78cfd19f94d24e529d0f52e18ce6185cb238edc6bd43086270fd51dd99f664f43dd4c7d2fe506762fbd859028e13fcf", + "0xa6e7220598c554abdcc3fdc587b988617b32c7bb0f82c06205467dbedb58276cc07cae317a190f19d19078773f4c2bbb", + "0xb88862809487ee430368dccd85a5d72fa4d163ca4aad15c78800e19c1a95be2192719801e315d86cff7795e0544a77e4", + "0x87ecb13a03921296f8c42ceb252d04716f10e09c93962239fcaa0a7fef93f19ab3f2680bc406170108bc583e9ff2e721", + "0xa810cd473832b6581c36ec4cb403f2849357ba2d0b54df98ef3004b8a530c078032922a81d40158f5fb0043d56477f6e", + "0xa247b45dd85ca7fbb718b328f30a03f03c84aef2c583fbdc9fcc9eb8b52b34529e8c8f535505c10598b1b4dac3d7c647", + "0x96ee0b91313c68bac4aa9e065ce9e1d77e51ca4cff31d6a438718c58264dee87674bd97fc5c6b8008be709521e4fd008", + "0x837567ad073e42266951a9a54750919280a2ac835a73c158407c3a2b1904cf0d17b7195a393c71a18ad029cbd9cf79ee", + "0xa6a469c44b67ebf02196213e7a63ad0423aab9a6e54acc6fcbdbb915bc043586993454dc3cd9e4be8f27d67c1050879b", + "0x8712d380a843b08b7b294f1f06e2f11f4ad6bcc655fdde86a4d8bc739c23916f6fad2b902fe47d6212f03607907e9f0e", + "0x920adfb644b534789943cdae1bdd6e42828dda1696a440af2f54e6b97f4f97470a1c6ea9fa6a2705d8f04911d055acd1", + "0xa161c73adf584a0061e963b062f59d90faac65c9b3a936b837a10d817f02fcabfa748824607be45a183dd40f991fe83f", + "0x874f4ecd408c76e625ea50bc59c53c2d930ee25baf4b4eca2440bfbffb3b8bc294db579caa7c68629f4d9ec24187c1ba", + "0x8bff18087f112be7f4aa654e85c71fef70eee8ae480f61d0383ff6f5ab1a0508f966183bb3fc4d6f29cb7ca234aa50d3", + "0xb03b46a3ca3bc743a173cbc008f92ab1aedd7466b35a6d1ca11e894b9482ea9dc75f8d6db2ddd1add99bfbe7657518b7", + "0x8b4f3691403c3a8ad9e097f02d130769628feddfa8c2b3dfe8cff64e2bed7d6e5d192c1e2ba0ac348b8585e94acd5fa1", + "0xa0d9ca4a212301f97591bf65d5ef2b2664766b427c9dd342e23cb468426e6a56be66b1cb41fea1889ac5d11a8e3c50a5", + "0x8c93ed74188ca23b3df29e5396974b9cc135c91fdefdea6c0df694c8116410e93509559af55533a3776ac11b228d69b1", + "0x82dd331fb3f9e344ebdeeb557769b86a2cc8cc38f6c298d7572a33aea87c261afa9dbd898989139b9fc16bc1e880a099", + "0xa65faedf326bcfd8ef98a51410c78b021d39206704e8291cd1f09e096a66b9b0486be65ff185ca224c45918ac337ddeb", + "0xa188b37d363ac072a766fd5d6fa27df07363feff1342217b19e3c37385e42ffde55e4be8355aceaa2f267b6d66b4ac41", + "0x810fa3ba3e96d843e3bafd3f2995727f223d3567c8ba77d684c993ba1773c66551eb5009897c51b3fe9b37196984f5ec", + "0x87631537541852da323b4353af45a164f68b304d24c01183bf271782e11687f3fcf528394e1566c2a26cb527b3148e64", + "0xb721cb2b37b3c477a48e3cc0044167d51ff568a5fd2fb606e5aec7a267000f1ddc07d3db919926ae12761a8e017c767c", + "0x904dfad4ba2cc1f6e60d1b708438a70b1743b400164cd981f13c064b8328d5973987d4fb9cf894068f29d3deaf624dfb", + "0xa70491538893552c20939fae6be2f07bfa84d97e2534a6bbcc0f1729246b831103505e9f60e97a8fa7d2e6c1c2384579", + "0x8726cf1b26b41f443ff7485adcfddc39ace2e62f4d65dd0bb927d933e262b66f1a9b367ded5fbdd6f3b0932553ac1735", + "0xae8a11cfdf7aa54c08f80cb645e3339187ab3886babe9fae5239ba507bb3dd1c0d161ca474a2df081dcd3d63e8fe445e", + "0x92328719e97ce60e56110f30a00ac5d9c7a2baaf5f8d22355d53c1c77941e3a1fec7d1405e6fbf8959665fe2ba7a8cad", + "0x8d9d6255b65798d0018a8cccb0b6343efd41dc14ff2058d3eed9451ceaad681e4a0fa6af67b0a04318aa628024e5553d", + "0xb70209090055459296006742d946a513f0cba6d83a05249ee8e7a51052b29c0ca9722dc4af5f9816a1b7938a5dac7f79", + "0xaab7b766b9bf91786dfa801fcef6d575dc6f12b77ecc662eb4498f0312e54d0de9ea820e61508fc8aeee5ab5db529349", + "0xa8104b462337748b7f086a135d0c3f87f8e51b7165ca6611264b8fb639d9a2f519926cb311fa2055b5fadf03da70c678", + "0xb0d2460747d5d8b30fc6c6bd0a87cb343ddb05d90a51b465e8f67d499cfc5e3a9e365da05ae233bbee792cdf90ec67d5", + "0xaa55f5bf3815266b4a149f85ed18e451c93de9163575e3ec75dd610381cc0805bb0a4d7c4af5b1f94d10231255436d2c", + "0x8d4c6a1944ff94426151909eb5b99cfd92167b967dabe2bf3aa66bb3c26c449c13097de881b2cfc1bf052862c1ef7b03", + "0x8862296162451b9b6b77f03bf32e6df71325e8d7485cf3335d66fd48b74c2a8334c241db8263033724f26269ad95b395", + "0x901aa96deb26cda5d9321190ae6624d357a41729d72ef1abfd71bebf6139af6d690798daba53b7bc5923462115ff748a", + "0x96c195ec4992728a1eb38cdde42d89a7bce150db43adbc9e61e279ea839e538deec71326b618dd39c50d589f78fc0614", + "0xb6ff8b8aa0837b99a1a8b46fb37f20ad4aecc6a98381b1308697829a59b8442ffc748637a88cb30c9b1f0f28a926c4f6", + "0x8d807e3dca9e7bef277db1d2cfb372408dd587364e8048b304eff00eacde2c723bfc84be9b98553f83cba5c7b3cba248", + "0x8800c96adb0195c4fc5b24511450dee503c32bf47044f5e2e25bd6651f514d79a2dd9b01cd8c09f3c9d3859338490f57", + "0x89fe366096097e38ec28dd1148887112efa5306cc0c3da09562aafa56f4eb000bf46ff79bf0bdd270cbde6bf0e1c8957", + "0xaf409a90c2776e1e7e3760b2042507b8709e943424606e31e791d42f17873a2710797f5baaab4cc4a19998ef648556b0", + "0x8d761863c9b6edbd232d35ab853d944f5c950c2b643f84a1a1327ebb947290800710ff01dcfa26dc8e9828481240e8b1", + "0x90b95e9be1e55c463ed857c4e0617d6dc3674e99b6aa62ed33c8e79d6dfcf7d122f4f4cc2ee3e7c5a49170cb617d2e2e", + "0xb3ff381efefabc4db38cc4727432e0301949ae4f16f8d1dea9b4f4de611cf5a36d84290a0bef160dac4e1955e516b3b0", + "0xa8a84564b56a9003adcadb3565dc512239fc79572762cda7b5901a255bc82656bb9c01212ad33d6bef4fbbce18dacc87", + "0x90a081890364b222eef54bf0075417f85e340d2fec8b7375995f598aeb33f26b44143ebf56fca7d8b4ebb36b5747b0eb", + "0xade6ee49e1293224ddf2d8ab7f14bb5be6bc6284f60fd5b3a1e0cf147b73cff57cf19763b8a36c5083badc79c606b103", + "0xb2fa99806dd2fa3de09320b615a2570c416c9bcdb052e592b0aead748bbe407ec9475a3d932ae48b71c2627eb81986a6", + "0x91f3b7b73c8ccc9392542711c45fe6f236057e6efad587d661ad5cb4d6e88265f86b807bb1151736b1009ab74fd7acb4", + "0x8800e2a46af96696dfbdcbf2ca2918b3dcf28ad970170d2d1783b52b8d945a9167d052beeb55f56c126da7ffa7059baa", + "0x9862267a1311c385956b977c9aa08548c28d758d7ba82d43dbc3d0a0fd1b7a221d39e8399997fea9014ac509ff510ac4", + "0xb7d24f78886fd3e2d283e18d9ad5a25c1a904e7d9b9104bf47da469d74f34162e27e531380dbbe0a9d051e6ffd51d6e7", + "0xb0f445f9d143e28b9df36b0f2c052da87ee2ca374d9d0fbe2eff66ca6fe5fe0d2c1951b428d58f7314b7e74e45d445ea", + "0xb63fc4083eabb8437dafeb6a904120691dcb53ce2938b820bb553da0e1eecd476f72495aacb72600cf9cad18698fd3db", + "0xb9ffd8108eaebd582d665f8690fe8bb207fd85185e6dd9f0b355a09bac1bbff26e0fdb172bc0498df025414e88fe2eda", + "0x967ed453e1f1a4c5b7b6834cc9f75c13f6889edc0cc91dc445727e9f408487bbf05c337103f61397a10011dfbe25d61d", + "0x98ceb673aff36e1987d5521a3984a07079c3c6155974bb8b413e8ae1ce84095fe4f7862fba7aefa14753eb26f2a5805f", + "0x85f01d28603a8fdf6ce6a50cb5c44f8a36b95b91302e3f4cd95c108ce8f4d212e73aec1b8d936520d9226802a2bd9136", + "0x88118e9703200ca07910345fbb789e7a8f92bd80bbc79f0a9e040e8767d33df39f6eded403a9b636eabf9101e588482a", + "0x90833a51eef1b10ed74e8f9bbd6197e29c5292e469c854eed10b0da663e2bceb92539710b1858bbb21887bd538d28d89", + "0xb513b905ec19191167c6193067b5cfdf5a3d3828375360df1c7e2ced5815437dfd37f0c4c8f009d7fb29ff3c8793f560", + "0xb1b6d405d2d18f9554b8a358cc7e2d78a3b34269737d561992c8de83392ac9a2857be4bf15de5a6c74e0c9d0f31f393c", + "0xb828bd3e452b797323b798186607849f85d1fb20c616833c0619360dfd6b3e3aa000fd09dafe4b62d74abc41072ff1a9", + "0x8efde67d0cca56bb2c464731879c9ac46a52e75bac702a63200a5e192b4f81c641f855ca6747752b84fe469cb7113b6c", + "0xb2762ba1c89ac3c9a983c242e4d1c2610ff0528585ed5c0dfc8a2c0253551142af9b59f43158e8915a1da7cc26b9df67", + "0x8a3f1157fb820d1497ef6b25cd70b7e16bb8b961b0063ad340d82a79ee76eb2359ca9e15e6d42987ed7f154f5eeaa2da", + "0xa75e29f29d38f09c879f971c11beb5368affa084313474a5ecafa2896180b9e47ea1995c2733ec46f421e395a1d9cffe", + "0x8e8c3dd3e7196ef0b4996b531ec79e4a1f211db5d5635e48ceb80ff7568b2ff587e845f97ee703bb23a60945ad64314a", + "0x8e7f32f4a3e3c584af5e3d406924a0aa34024c42eca74ef6cc2a358fd3c9efaf25f1c03aa1e66bb94b023a2ee2a1cace", + "0xab7dce05d59c10a84feb524fcb62478906b3fa045135b23afbede3bb32e0c678d8ebe59feabccb5c8f3550ea76cae44b", + "0xb38bb4b44d827f6fd3bd34e31f9186c59e312dbfadd4a7a88e588da10146a78b1f8716c91ad8b806beb8da65cab80c4c", + "0x9490ce9442bbbd05438c7f5c4dea789f74a7e92b1886a730544b55ba377840740a3ae4f2f146ee73f47c9278b0e233bc", + "0x83c003fab22a7178eed1a668e0f65d4fe38ef3900044e9ec63070c23f2827d36a1e73e5c2b883ec6a2afe2450171b3b3", + "0x9982f02405978ddc4fca9063ebbdb152f524c84e79398955e66fe51bc7c1660ec1afc3a86ec49f58d7b7dde03505731c", + "0xab337bd83ccdd2322088ffa8d005f450ced6b35790f37ab4534313315ee84312adc25e99cce052863a8bedee991729ed", + "0x8312ce4bec94366d88f16127a17419ef64285cd5bf9e5eda010319b48085966ed1252ed2f5a9fd3e0259b91bb65f1827", + "0xa60d5a6327c4041b0c00a1aa2f0af056520f83c9ce9d9ccd03a0bd4d9e6a1511f26a422ea86bd858a1f77438adf07e6c", + "0xb84a0a0b030bdad83cf5202aa9afe58c9820e52483ab41f835f8c582c129ee3f34aa096d11c1cd922eda02ea1196a882", + "0x8077d105317f4a8a8f1aadeb05e0722bb55f11abcb490c36c0904401107eb3372875b0ac233144829e734f0c538d8c1d", + "0x9202503bd29a6ec198823a1e4e098f9cfe359ed51eb5174d1ca41368821bfeebcbd49debfd02952c41359d1c7c06d2b1", + "0xabc28c155e09365cb77ffead8dc8f602335ef93b2f44e4ef767ce8fc8ef9dd707400f3a722e92776c2e0b40192c06354", + "0xb0f6d1442533ca45c9399e0a63a11f85ff288d242cea6cb3b68c02e77bd7d158047cae2d25b3bcd9606f8f66d9b32855", + "0xb01c3d56a0db84dc94575f4b6ee2de4beca3230e86bed63e2066beb22768b0a8efb08ebaf8ac3dedb5fe46708b084807", + "0x8c8634b0432159f66feaabb165842d1c8ac378f79565b1b90c381aa8450eb4231c3dad11ec9317b9fc2b155c3a771e32", + "0x8e67f623d69ecd430c9ee0888520b6038f13a2b6140525b056dc0951f0cfed2822e62cf11d952a483107c5c5acac4826", + "0x9590bb1cba816dd6acd5ac5fba5142c0a19d53573e422c74005e0bcf34993a8138c83124cad35a3df65879dba6134edd", + "0x801cd96cde0749021a253027118d3ea135f3fcdbe895db08a6c145641f95ebd368dd6a1568d995e1d0084146aebe224a", + "0x848b5d196427f6fc1f762ee3d36e832b64a76ec1033cfedc8b985dea93932a7892b8ef1035c653fb9dcd9ab2d9a44ac8", + "0xa1017eb83d5c4e2477e7bd2241b2b98c4951a3b391081cae7d75965cadc1acaec755cf350f1f3d29741b0828e36fedea", + "0x8d6d2785e30f3c29aad17bd677914a752f831e96d46caf54446d967cb2432be2c849e26f0d193a60bee161ea5c6fe90a", + "0x935c0ba4290d4595428e034b5c8001cbd400040d89ab00861108e8f8f4af4258e41f34a7e6b93b04bc253d3b9ffc13bf", + "0xaac02257146246998477921cef2e9892228590d323b839f3e64ea893b991b463bc2f47e1e5092ddb47e70b2f5bce7622", + "0xb921fde9412970a5d4c9a908ae8ce65861d06c7679af577cf0ad0d5344c421166986bee471fd6a6cecb7d591f06ec985", + "0x8ef4c37487b139d6756003060600bb6ebac7ea810b9c4364fc978e842f13ac196d1264fbe5af60d76ff6d9203d8e7d3f", + "0x94b65e14022b5cf6a9b95f94be5ace2711957c96f4211c3f7bb36206bd39cfbd0ea82186cab5ad0577a23214a5c86e9e", + "0xa31c166d2a2ca1d5a75a5920fef7532681f62191a50d8555fdaa63ba4581c3391cc94a536fc09aac89f64eafceec3f90", + "0x919a8cc128de01e9e10f5d83b08b52293fdd41bde2b5ae070f3d95842d4a16e5331cf2f3d61c765570c8022403610fa4", + "0xb23d6f8331eef100152d60483cfa14232a85ee712c8538c9b6417a5a7c5b353c2ac401390c6c215cb101f5cee6b5f43e", + "0xab357160c08a18319510a571eafff154298ce1020de8e1dc6138a09fcb0fcbcdd8359f7e9386bda00b7b9cdea745ffdc", + "0xab55079aea34afa5c0bd1124b9cdfe01f325b402fdfa017301bf87812eaa811ea5798c3aaf818074d420d1c782b10ada", + "0xade616010dc5009e7fc4f8d8b00dc716686a5fa0a7816ad9e503e15839d3b909b69d9dd929b7575376434ffec0d2bea8", + "0x863997b97ed46898a8a014599508fa3079f414b1f4a0c4fdc6d74ae8b444afa350f327f8bfc2a85d27f9e2d049c50135", + "0x8d602ff596334efd4925549ed95f2aa762b0629189f0df6dbb162581657cf3ea6863cd2287b4d9c8ad52813d87fcd235", + "0xb70f68c596dcdeed92ad5c6c348578b26862a51eb5364237b1221e840c47a8702f0fbc56eb520a22c0eed99795d3903e", + "0x9628088f8e0853cefadee305a8bf47fa990c50fa96a82511bbe6e5dc81ef4b794e7918a109070f92fc8384d77ace226f", + "0x97e26a46e068b605ce96007197ecd943c9a23881862f4797a12a3e96ba2b8d07806ad9e2a0646796b1889c6b7d75188c", + "0xb1edf467c068cc163e2d6413cc22b16751e78b3312fe47b7ea82b08a1206d64415b2c8f2a677fa89171e82cc49797150", + "0xa44d15ef18745b251429703e3cab188420e2d974de07251501799b016617f9630643fcd06f895634d8ecdd579e1bf000", + "0xabd126df3917ba48c618ee4dbdf87df506193462f792874439043fa1b844466f6f4e0ff2e42516e63b5b23c0892b2695", + "0xa2a67f57c4aa3c2aa1eeddbfd5009a89c26c2ce8fa3c96a64626aba19514beb125f27df8559506f737de3eae0f1fc18f", + "0xa633e0132197e6038197304b296ab171f1d8e0d0f34dcf66fe9146ac385b0239232a8470b9205a4802ab432389f4836d", + "0xa914b3a28509a906c3821463b936455d58ff45dcbe158922f9efb2037f2eb0ce8e92532d29b5d5a3fcd0d23fa773f272", + "0xa0e1412ce4505daf1a2e59ce4f0fc0e0023e335b50d2b204422f57cd65744cc7a8ed35d5ef131a42c70b27111d3115b7", + "0xa2339e2f2b6072e88816224fdd612c04d64e7967a492b9f8829db15367f565745325d361fd0607b0def1be384d010d9e", + "0xa7309fc41203cb99382e8193a1dcf03ac190a7ce04835304eb7e341d78634e83ea47cb15b885601956736d04cdfcaa01", + "0x81f3ccd6c7f5b39e4e873365f8c37b214e8ab122d04a606fbb7339dc3298c427e922ec7418002561d4106505b5c399ee", + "0x92c121cf914ca549130e352eb297872a63200e99b148d88fbc9506ad882bec9d0203d65f280fb5b0ba92e336b7f932e8", + "0xa4b330cf3f064f5b131578626ad7043ce2a433b6f175feb0b52d36134a454ca219373fd30d5e5796410e005b69082e47", + "0x86fe5774112403ad83f9c55d58317eeb17ad8e1176d9f2f69c2afb7ed83bc718ed4e0245ceab4b377f5f062dcd4c00e7", + "0x809d152a7e2654c7fd175b57f7928365a521be92e1ed06c05188a95864ddb25f7cab4c71db7d61bbf4cae46f3a1d96ce", + "0xb82d663e55c2a5ada7e169e9b1a87bc1c0177baf1ec1c96559b4cb1c5214ce1ddf2ab8d345014cab6402f3774235cf5a", + "0x86580af86df1bd2c385adb8f9a079e925981b7184db66fc5fe5b14cddb82e7d836b06eaeef14924ac529487b23dae111", + "0xb5f5f4c5c94944ecc804df6ab8687d64e27d988cbfeae1ba7394e0f6adbf778c5881ead7cd8082dd7d68542b9bb4ecd5", + "0xa6016916146c2685c46e8fdd24186394e2d5496e77e08c0c6a709d4cd7dfa97f1efcef94922b89196819076a91ad37b5", + "0xb778e7367ded3b6eab53d5fc257f7a87e8faf74a593900f2f517220add2125be3f6142022660d8181df8d164ad9441ce", + "0x8581b2d36abe6f553add4d24be761bec1b8efaa2929519114346615380b3c55b59e6ad86990e312f7e234d0203bdf59b", + "0x9917e74fd45c3f71a829ff5498a7f6b5599b48c098dda2339bf04352bfc7f368ccf1a407f5835901240e76452ae807d7", + "0xafd196ce6f9335069138fd2e3d133134da253978b4ce373152c0f26affe77a336505787594022e610f8feb722f7cc1fb", + "0xa477491a1562e329764645e8f24d8e228e5ef28c9f74c6b5b3abc4b6a562c15ffb0f680d372aed04d9e1bf944dece7be", + "0x9767440d58c57d3077319d3a330e5322b9ba16981ec74a5a14d53462eab59ae7fd2b14025bfc63b268862094acb444e6", + "0x80986d921be3513ef69264423f351a61cb48390c1be8673aee0f089076086aaebea7ebe268fd0aa7182695606116f679", + "0xa9554c5c921c07b450ee04e34ec58e054ac1541b26ce2ce5a393367a97348ba0089f53db6660ad76b60278b66fd12e3e", + "0x95097e7d2999b3e84bf052c775581cf361325325f4a50192521d8f4693c830bed667d88f482dc1e3f833aa2bd22d2cbf", + "0x9014c91d0f85aefd28436b5228c12f6353c055a9326c7efbf5e071e089e2ee7c070fcbc84c5fafc336cbb8fa6fec1ca1", + "0x90f57ba36ee1066b55d37384942d8b57ae00f3cf9a3c1d6a3dfee1d1af42d4b5fa9baeb0cd7e46687d1d6d090ddb931d", + "0x8e4b1db12fd760a17214c9e47f1fce6e43c0dbb4589a827a13ac61aaae93759345697bb438a00edab92e0b7b62414683", + "0x8022a959a513cdc0e9c705e0fc04eafd05ff37c867ae0f31f6d01cddd5df86138a426cab2ff0ac8ff03a62e20f7e8f51", + "0x914e9a38829834c7360443b8ed86137e6f936389488eccf05b4b4db7c9425611705076ecb3f27105d24b85c852be7511", + "0x957fb10783e2bd0db1ba66b18e794df710bc3b2b05776be146fa5863c15b1ebdd39747b1a95d9564e1772cdfc4f37b8a", + "0xb6307028444daed8ed785ac9d0de76bc3fe23ff2cc7e48102553613bbfb5afe0ebe45e4212a27021c8eb870721e62a1f", + "0x8f76143597777d940b15a01b39c5e1b045464d146d9a30a6abe8b5d3907250e6c7f858ff2308f8591e8b0a7b3f3c568a", + "0x96163138ac0ce5fd00ae9a289648fd9300a0ca0f63a88481d703ecd281c06a52a3b5178e849e331f9c85ca4ba398f4cc", + "0xa63ef47c3e18245b0482596a09f488a716df3cbd0f9e5cfabed0d742843e65db8961c556f45f49762f3a6ac8b627b3ef", + "0x8cb595466552e7c4d42909f232d4063e0a663a8ef6f6c9b7ce3a0542b2459cde04e0e54c7623d404acb5b82775ac04f6", + "0xb47fe69960eb45f399368807cff16d941a5a4ebad1f5ec46e3dc8a2e4d598a7e6114d8f0ca791e9720fd786070524e2b", + "0x89eb5ff83eea9df490e5beca1a1fbbbbcf7184a37e2c8c91ede7a1e654c81e8cd41eceece4042ea7918a4f4646b67fd6", + "0xa84f5d155ed08b9054eecb15f689ba81e44589e6e7207a99790c598962837ca99ec12344105b16641ca91165672f7153", + "0xa6cc8f25c2d5b2d2f220ec359e6a37a52b95fa6af6e173c65e7cd55299eff4aa9e6d9e6f2769e6459313f1f2aecb0fab", + "0xafcde944411f017a9f7979755294981e941cc41f03df5e10522ef7c7505e5f1babdd67b3bf5258e8623150062eb41d9b", + "0x8fab39f39c0f40182fcd996ade2012643fe7731808afbc53f9b26900b4d4d1f0f5312d9d40b3df8baa4739970a49c732", + "0xae193af9726da0ebe7df1f9ee1c4846a5b2a7621403baf8e66c66b60f523e719c30c6b4f897bb14b27d3ff3da8392eeb", + "0x8ac5adb82d852eba255764029f42e6da92dcdd0e224d387d1ef94174038db9709ac558d90d7e7c57ad4ce7f89bbfc38c", + "0xa2066b3458fdf678ee487a55dd5bfb74fde03b54620cb0e25412a89ee28ad0d685e309a51e3e4694be2fa6f1593a344c", + "0x88d031745dd0ae07d61a15b594be5d4b2e2a29e715d081649ad63605e3404b0c3a5353f0fd9fad9c05c18e93ce674fa1", + "0x8283cfb0ef743a043f2b77ecaeba3005e2ca50435585b5dd24777ee6bce12332f85e21b446b536da38508807f0f07563", + "0xb376de22d5f6b0af0b59f7d9764561f4244cf8ffe22890ecd3dcf2ff1832130c9b821e068c9d8773136f4796721e5963", + "0xae3afc50c764f406353965363840bf28ee85e7064eb9d5f0bb3c31c64ab10f48c853e942ee2c9b51bae59651eaa08c2f", + "0x948b204d103917461a01a6c57a88f2d66b476eae5b00be20ec8c747650e864bc8a83aee0aff59cb7584b7a3387e0ee48", + "0x81ab098a082b07f896c5ffd1e4446cb7fb44804cbbf38d125208b233fc82f8ec9a6a8d8dd1c9a1162dc28ffeec0dde50", + "0xa149c6f1312821ced2969268789a3151bdda213451760b397139a028da609c4134ac083169feb0ee423a0acafd10eceb", + "0xb0ac9e27a5dadaf523010f730b28f0ebac01f460d3bbbe277dc9d44218abb5686f4fac89ae462682fef9edbba663520a", + "0x8d0e0073cca273daaaa61b6fc54bfe5a009bc3e20ae820f6c93ba77b19eca517d457e948a2de5e77678e4241807157cb", + "0xad61d3a2edf7c7533a04964b97499503fd8374ca64286dba80465e68fe932e96749b476f458c6fc57cb1a7ca85764d11", + "0x90eb5e121ae46bc01a30881eaa556f46bd8457a4e80787cf634aab355082de34ac57d7f497446468225f7721e68e2a47", + "0x8cdac557de7c42d1f3780e33dec1b81889f6352279be81c65566cdd4952d4c15d79e656cbd46035ab090b385e90245ef", + "0x82b67e61b88b84f4f4d4f65df37b3e3dcf8ec91ea1b5c008fdccd52da643adbe6468a1cfdb999e87d195afe2883a3b46", + "0x8503b467e8f5d6048a4a9b78496c58493a462852cab54a70594ae3fd064cfd0deb4b8f336a262155d9fedcaa67d2f6fd", + "0x8db56c5ac763a57b6ce6832930c57117058e3e5a81532b7d19346346205e2ec614eb1a2ee836ef621de50a7bc9b7f040", + "0xad344699198f3c6e8c0a3470f92aaffc805b76266734414c298e10b5b3797ca53578de7ccb2f458f5e0448203f55282b", + "0x80602032c43c9e2a09154cc88b83238343b7a139f566d64cb482d87436b288a98f1ea244fd3bff8da3c398686a900c14", + "0xa6385bd50ecd548cfb37174cdbb89e10025b5cadaf3cff164c95d7aef5a33e3d6a9bf0c681b9e11db9ef54ebeee2a0c1", + "0xabf2d95f4aa34b0581eb9257a0cc8462b2213941a5deb8ba014283293e8b36613951b61261cc67bbd09526a54cbbff76", + "0xa3d5de52f48df72c289ff713e445991f142390798cd42bd9d9dbefaee4af4f5faf09042d126b975cf6b98711c3072553", + "0x8e627302ff3d686cff8872a1b7c2a57b35f45bf2fc9aa42b049d8b4d6996a662b8e7cbac6597f0cb79b0cc4e29fbf133", + "0x8510702e101b39a1efbf4e504e6123540c34b5689645e70d0bac1ecc1baf47d86c05cef6c4317a4e99b4edaeb53f2d00", + "0xaa173f0ecbcc6088f878f8726d317748c81ebf501bba461f163b55d66099b191ec7c55f7702f351a9c8eb42cfa3280e2", + "0xb560a697eafab695bcef1416648a0a664a71e311ecbe5823ae903bd0ed2057b9d7574b9a86d3fe22aa3e6ddce38ea513", + "0x8df6304a3d9cf40100f3f687575419c998cd77e5cc27d579cf4f8e98642de3609af384a0337d145dd7c5635172d26a71", + "0x8105c7f3e4d30a29151849673853b457c1885c186c132d0a98e63096c3774bc9deb956cf957367e633d0913680bda307", + "0x95373fc22c0917c3c2044ac688c4f29a63ed858a45c0d6d2d0fe97afd6f532dcb648670594290c1c89010ecc69259bef", + "0x8c2fae9bcadab341f49b55230310df93cac46be42d4caa0d42e45104148a91e527af1b4209c0d972448162aed28fab64", + "0xb05a77baab70683f76209626eaefdda2d36a0b66c780a20142d23c55bd479ddd4ad95b24579384b6cf62c8eb4c92d021", + "0x8e6bc6a7ea2755b4aaa19c1c1dee93811fcde514f03485fdc3252f0ab7f032c315614f6336e57cea25dcfb8fb6084eeb", + "0xb656a27d06aade55eadae2ad2a1059198918ea6cc3fd22c0ed881294d34d5ac7b5e4700cc24350e27d76646263b223aa", + "0xa296469f24f6f56da92d713afcd4dd606e7da1f79dc4e434593c53695847eefc81c7c446486c4b3b8c8d00c90c166f14", + "0x87a326f57713ac2c9dffeb3af44b9f3c613a8f952676fc46343299122b47ee0f8d792abaa4b5db6451ced5dd153aabd0", + "0xb689e554ba9293b9c1f6344a3c8fcb6951d9f9eac4a2e2df13de021aade7c186be27500e81388e5b8bcab4c80f220a31", + "0x87ae0aa0aa48eac53d1ca5a7b93917de12db9e40ceabf8fdb40884ae771cfdf095411deef7c9f821af0b7070454a2608", + "0xa71ffa7eae8ace94e6c3581d4cb2ad25d48cbd27edc9ec45baa2c8eb932a4773c3272b2ffaf077b40f76942a1f3af7f2", + "0x94c218c91a9b73da6b7a495b3728f3028df8ad9133312fc0c03e8c5253b7ccb83ed14688fd4602e2fd41f29a0bc698bd", + "0xae1e77b90ca33728af07a4c03fb2ef71cd92e2618e7bf8ed4d785ce90097fc4866c29999eb84a6cf1819d75285a03af2", + "0xb7a5945b277dab9993cf761e838b0ac6eaa903d7111fca79f9fde3d4285af7a89bf6634a71909d095d7619d913972c9c", + "0x8c43b37be02f39b22029b20aca31bff661abce4471dca88aa3bddefd9c92304a088b2dfc8c4795acc301ca3160656af2", + "0xb32e5d0fba024554bd5fe8a793ebe8003335ddd7f585876df2048dcf759a01285fecb53daae4950ba57f3a282a4d8495", + "0x85ea7fd5e10c7b659df5289b2978b2c89e244f269e061b9a15fcab7983fc1962b63546e82d5731c97ec74b6804be63ef", + "0x96b89f39181141a7e32986ac02d7586088c5a9662cec39843f397f3178714d02f929af70630c12cbaba0268f8ba2d4fa", + "0x929ab1a2a009b1eb37a2817c89696a06426529ebe3f306c586ab717bd34c35a53eca2d7ddcdef36117872db660024af9", + "0xa696dccf439e9ca41511e16bf3042d7ec0e2f86c099e4fc8879d778a5ea79e33aa7ce96b23dc4332b7ba26859d8e674d", + "0xa8fe69a678f9a194b8670a41e941f0460f6e2dbc60470ab4d6ae2679cc9c6ce2c3a39df2303bee486dbfde6844e6b31a", + "0x95f58f5c82de2f2a927ca99bf63c9fc02e9030c7e46d0bf6b67fe83a448d0ae1c99541b59caf0e1ccab8326231af09a5", + "0xa57badb2c56ca2c45953bd569caf22968f76ed46b9bac389163d6fe22a715c83d5e94ae8759b0e6e8c2f27bff7748f3f", + "0x868726fd49963b24acb5333364dffea147e98f33aa19c7919dc9aca0fd26661cfaded74ede7418a5fadbe7f5ae67b67b", + "0xa8d8550dcc64d9f1dd7bcdab236c4122f2b65ea404bb483256d712c7518f08bb028ff8801f1da6aed6cbfc5c7062e33b", + "0x97e25a87dae23155809476232178538d4bc05d4ff0882916eb29ae515f2a62bfce73083466cc0010ca956aca200aeacc", + "0xb4ea26be3f4bd04aa82d7c4b0913b97bcdf5e88b76c57eb1a336cbd0a3eb29de751e1bc47c0e8258adec3f17426d0c71", + "0x99ee555a4d9b3cf2eb420b2af8e3bc99046880536116d0ce7193464ac40685ef14e0e3c442f604e32f8338cb0ef92558", + "0x8c64efa1da63cd08f319103c5c7a761221080e74227bbc58b8fb35d08aa42078810d7af3e60446cbaff160c319535648", + "0x8d9fd88040076c28420e3395cbdfea402e4077a3808a97b7939d49ecbcf1418fe50a0460e1c1b22ac3f6e7771d65169a", + "0xae3c19882d7a9875d439265a0c7003c8d410367627d21575a864b9cb4918de7dbdb58a364af40c5e045f3df40f95d337", + "0xb4f7bfacab7b2cafe393f1322d6dcc6f21ffe69cd31edc8db18c06f1a2b512c27bd0618091fd207ba8df1808e9d45914", + "0x94f134acd0007c623fb7934bcb65ef853313eb283a889a3ffa79a37a5c8f3665f3d5b4876bc66223610c21dc9b919d37", + "0xaa15f74051171daacdc1f1093d3f8e2d13da2833624b80a934afec86fc02208b8f55d24b7d66076444e7633f46375c6a", + "0xa32d6bb47ef9c836d9d2371807bafbbbbb1ae719530c19d6013f1d1f813c49a60e4fa51d83693586cba3a840b23c0404", + "0xb61b3599145ea8680011aa2366dc511a358b7d67672d5b0c5be6db03b0efb8ca5a8294cf220ea7409621f1664e00e631", + "0x859cafc3ee90b7ececa1ed8ef2b2fc17567126ff10ca712d5ffdd16aa411a5a7d8d32c9cab1fbf63e87dce1c6e2f5f53", + "0xa2fef1b0b2874387010e9ae425f3a9676d01a095d017493648bcdf3b31304b087ccddb5cf76abc4e1548b88919663b6b", + "0x939e18c73befc1ba2932a65ede34c70e4b91e74cc2129d57ace43ed2b3af2a9cc22a40fbf50d79a63681b6d98852866d", + "0xb3b4259d37b1b14aee5b676c9a0dd2d7f679ab95c120cb5f09f9fbf10b0a920cb613655ddb7b9e2ba5af4a221f31303c", + "0x997255fe51aaca6e5a9cb3359bcbf25b2bb9e30649bbd53a8a7c556df07e441c4e27328b38934f09c09d9500b5fabf66", + "0xabb91be2a2d860fd662ed4f1c6edeefd4da8dc10e79251cf87f06029906e7f0be9b486462718f0525d5e049472692cb7", + "0xb2398e593bf340a15f7801e1d1fbda69d93f2a32a889ec7c6ae5e8a37567ac3e5227213c1392ee86cfb3b56ec2787839", + "0x8ddf10ccdd72922bed36829a36073a460c2118fc7a56ff9c1ac72581c799b15c762cb56cb78e3d118bb9f6a7e56cb25e", + "0x93e6bc0a4708d16387cacd44cf59363b994dc67d7ada7b6d6dbd831c606d975247541b42b2a309f814c1bfe205681fc6", + "0xb93fc35c05998cffda2978e12e75812122831523041f10d52f810d34ff71944979054b04de0117e81ddf5b0b4b3e13c0", + "0x92221631c44d60d68c6bc7b287509f37ee44cbe5fdb6935cee36b58b17c7325098f98f7910d2c3ca5dc885ad1d6dabc7", + "0xa230124424a57fad3b1671f404a94d7c05f4c67b7a8fbacfccea28887b78d7c1ed40b92a58348e4d61328891cd2f6cee", + "0xa6a230edb8518a0f49d7231bc3e0bceb5c2ac427f045819f8584ba6f3ae3d63ed107a9a62aad543d7e1fcf1f20605706", + "0x845be1fe94223c7f1f97d74c49d682472585d8f772762baad8a9d341d9c3015534cc83d102113c51a9dea2ab10d8d27b", + "0xb44262515e34f2db597c8128c7614d33858740310a49cdbdf9c8677c5343884b42c1292759f55b8b4abc4c86e4728033", + "0x805592e4a3cd07c1844bc23783408310accfdb769cca882ad4d07d608e590a288b7370c2cb327f5336e72b7083a0e30f", + "0x95153e8b1140df34ee864f4ca601cb873cdd3efa634af0c4093fbaede36f51b55571ab271e6a133020cd34db8411241f", + "0x82878c1285cfa5ea1d32175c9401f3cc99f6bb224d622d3fd98cc7b0a27372f13f7ab463ce3a33ec96f9be38dbe2dfe3", + "0xb7588748f55783077c27fc47d33e20c5c0f5a53fc0ac10194c003aa09b9f055d08ec971effa4b7f760553997a56967b3", + "0xb36b4de6d1883b6951f59cfae381581f9c6352fcfcf1524fccdab1571a20f80441d9152dc6b48bcbbf00371337ca0bd5", + "0x89c5523f2574e1c340a955cbed9c2f7b5fbceb260cb1133160dabb7d41c2f613ec3f6e74bbfab3c4a0a6f0626dbe068f", + "0xa52f58cc39f968a9813b1a8ddc4e83f4219e4dd82c7aa1dd083bea7edf967151d635aa9597457f879771759b876774e4", + "0x8300a67c2e2e123f89704abfde095463045dbd97e20d4c1157bab35e9e1d3d18f1f4aaba9cbe6aa2d544e92578eaa1b6", + "0xac6a7f2918768eb6a43df9d3a8a04f8f72ee52f2e91c064c1c7d75cad1a3e83e5aba9fe55bb94f818099ac91ccf2e961", + "0x8d64a2b0991cf164e29835c8ddef6069993a71ec2a7de8157bbfa2e00f6367be646ed74cbaf524f0e9fe13fb09fa15fd", + "0x8b2ffe5a545f9f680b49d0a9797a4a11700a2e2e348c34a7a985fc278f0f12def6e06710f40f9d48e4b7fbb71e072229", + "0x8ab8f71cd337fa19178924e961958653abf7a598e3f022138b55c228440a2bac4176cea3aea393549c03cd38a13eb3fc", + "0x8419d28318c19ea4a179b7abb43669fe96347426ef3ac06b158d79c0acf777a09e8e770c2fb10e14b3a0421705990b23", + "0x8bacdac310e1e49660359d0a7a17fe3d334eb820e61ae25e84cb52f863a2f74cbe89c2e9fc3283745d93a99b79132354", + "0xb57ace3fa2b9f6b2db60c0d861ace7d7e657c5d35d992588aeed588c6ce3a80b6f0d49f8a26607f0b17167ab21b675e4", + "0x83e265cde477f2ecc164f49ddc7fb255bb05ff6adc347408353b7336dc3a14fdedc86d5a7fb23f36b8423248a7a67ed1", + "0xa60ada971f9f2d79d436de5d3d045f5ab05308cae3098acaf5521115134b2a40d664828bb89895840db7f7fb499edbc5", + "0xa63eea12efd89b62d3952bf0542a73890b104dd1d7ff360d4755ebfa148fd62de668edac9eeb20507967ea37fb220202", + "0xa0275767a270289adc991cc4571eff205b58ad6d3e93778ddbf95b75146d82517e8921bd0d0564e5b75fa0ccdab8e624", + "0xb9b03fd3bf07201ba3a039176a965d736b4ef7912dd9e9bf69fe1b57c330a6aa170e5521fe8be62505f3af81b41d7806", + "0xa95f640e26fb1106ced1729d6053e41a16e4896acac54992279ff873e5a969aad1dcfa10311e28b8f409ac1dab7f03bb", + "0xb144778921742418053cb3c70516c63162c187f00db2062193bb2c14031075dbe055d020cde761b26e8c58d0ea6df2c1", + "0x8432fbb799e0435ef428d4fefc309a05dd589bce74d7a87faf659823e8c9ed51d3e42603d878e80f439a38be4321c2fa", + "0xb08ddef14e42d4fd5d8bf39feb7485848f0060d43b51ed5bdda39c05fe154fb111d29719ee61a23c392141358c0cfcff", + "0x8ae3c5329a5e025b86b5370e06f5e61177df4bda075856fade20a17bfef79c92f54ed495f310130021ba94fb7c33632b", + "0x92b6d3c9444100b4d7391febfc1dddaa224651677c3695c47a289a40d7a96d200b83b64e6d9df51f534564f272a2c6c6", + "0xb432bc2a3f93d28b5e506d68527f1efeb2e2570f6be0794576e2a6ef9138926fdad8dd2eabfa979b79ab7266370e86bc", + "0x8bc315eacedbcfc462ece66a29662ca3dcd451f83de5c7626ef8712c196208fb3d8a0faf80b2e80384f0dd9772f61a23", + "0xa72375b797283f0f4266dec188678e2b2c060dfed5880fc6bb0c996b06e91a5343ea2b695adaab0a6fd183b040b46b56", + "0xa43445036fbaa414621918d6a897d3692fdae7b2961d87e2a03741360e45ebb19fcb1703d23f1e15bb1e2babcafc56ac", + "0xb9636b2ffe305e63a1a84bd44fb402442b1799bd5272638287aa87ca548649b23ce8ce7f67be077caed6aa2dbc454b78", + "0x99a30bf0921d854c282b83d438a79f615424f28c2f99d26a05201c93d10378ab2cd94a792b571ddae5d4e0c0013f4006", + "0x8648e3c2f93d70b392443be116b48a863e4b75991bab5db656a4ef3c1e7f645e8d536771dfe4e8d1ceda3be8d32978b0", + "0xab50dc9e6924c1d2e9d2e335b2d679fc7d1a7632e84964d3bac0c9fe57e85aa5906ec2e7b0399d98ddd022e9b19b5904", + "0xab729328d98d295f8f3272afaf5d8345ff54d58ff9884da14f17ecbdb7371857fdf2f3ef58080054e9874cc919b46224", + "0x83fa5da7592bd451cad3ad7702b4006332b3aae23beab4c4cb887fa6348317d234bf62a359e665b28818e5410c278a09", + "0x8bdbff566ae9d368f114858ef1f009439b3e9f4649f73efa946e678d6c781d52c69af195df0a68170f5f191b2eac286b", + "0x91245e59b4425fd4edb2a61d0d47c1ccc83d3ced8180de34887b9655b5dcda033d48cde0bdc3b7de846d246c053a02e8", + "0xa2cb00721e68f1cad8933947456f07144dc69653f96ceed845bd577d599521ba99cdc02421118971d56d7603ed118cbf", + "0xaf8cd66d303e808b22ec57860dd909ca64c27ec2c60e26ffecfdc1179d8762ffd2739d87b43959496e9fee4108df71df", + "0x9954136812dffcd5d3f167a500e7ab339c15cfc9b3398d83f64b0daa3dd5b9a851204f424a3493b4e326d3de81e50a62", + "0x93252254d12511955f1aa464883ad0da793f84d900fea83e1df8bca0f2f4cf5b5f9acbaec06a24160d33f908ab5fea38", + "0x997cb55c26996586ba436a95566bd535e9c22452ca5d2a0ded2bd175376557fa895f9f4def4519241ff386a063f2e526", + "0xa12c78ad451e0ac911260ade2927a768b50cb4125343025d43474e7f465cdc446e9f52a84609c5e7e87ae6c9b3f56cda", + "0xa789d4ca55cbba327086563831b34487d63d0980ba8cf55197c016702ed6da9b102b1f0709ce3da3c53ff925793a3d73", + "0xa5d76acbb76741ce85be0e655b99baa04f7f587347947c0a30d27f8a49ae78cce06e1cde770a8b618d3db402be1c0c4b", + "0x873c0366668c8faddb0eb7c86f485718d65f8c4734020f1a18efd5fa123d3ea8a990977fe13592cd01d17e60809cb5ff", + "0xb659b71fe70f37573ff7c5970cc095a1dc0da3973979778f80a71a347ef25ad5746b2b9608bad4ab9a4a53a4d7df42d7", + "0xa34cbe05888e5e5f024a2db14cb6dcdc401a9cbd13d73d3c37b348f68688f87c24ca790030b8f84fef9e74b4eab5e412", + "0x94ce8010f85875c045b0f014db93ef5ab9f1f6842e9a5743dce9e4cb872c94affd9e77c1f1d1ab8b8660b52345d9acb9", + "0xadefa9b27a62edc0c5b019ddd3ebf45e4de846165256cf6329331def2e088c5232456d3de470fdce3fa758bfdd387512", + "0xa6b83821ba7c1f83cc9e4529cf4903adb93b26108e3d1f20a753070db072ad5a3689643144bdd9c5ea06bb9a7a515cd0", + "0xa3a9ddedc2a1b183eb1d52de26718151744db6050f86f3580790c51d09226bf05f15111691926151ecdbef683baa992c", + "0xa64bac89e7686932cdc5670d07f0b50830e69bfb8c93791c87c7ffa4913f8da881a9d8a8ce8c1a9ce5b6079358c54136", + "0xa77b5a63452cb1320b61ab6c7c2ef9cfbcade5fd4727583751fb2bf3ea330b5ca67757ec1f517bf4d503ec924fe32fbd", + "0x8746fd8d8eb99639d8cd0ca34c0d9c3230ed5a312aab1d3d925953a17973ee5aeb66e68667e93caf9cb817c868ea8f3d", + "0x88a2462a26558fc1fbd6e31aa8abdc706190a17c27fdc4217ffd2297d1b1f3321016e5c4b2384c5454d5717dc732ed03", + "0xb78893a97e93d730c8201af2e0d3b31cb923d38dc594ffa98a714e627c473d42ea82e0c4d2eeb06862ee22a9b2c54588", + "0x920cc8b5f1297cf215a43f6fc843e379146b4229411c44c0231f6749793d40f07b9af7699fd5d21fd69400b97febe027", + "0xa0f0eafce1e098a6b58c7ad8945e297cd93aaf10bc55e32e2e32503f02e59fc1d5776936577d77c0b1162cb93b88518b", + "0x98480ba0064e97a2e7a6c4769b4d8c2a322cfc9a3b2ca2e67e9317e2ce04c6e1108169a20bd97692e1cb1f1423b14908", + "0x83dbbb2fda7e287288011764a00b8357753a6a44794cc8245a2275237f11affdc38977214e463ad67aec032f3dfa37e9", + "0x86442fff37598ce2b12015ff19b01bb8a780b40ad353d143a0f30a06f6d23afd5c2b0a1253716c855dbf445cc5dd6865", + "0xb8a4c60c5171189414887847b9ed9501bff4e4c107240f063e2d254820d2906b69ef70406c585918c4d24f1dd052142b", + "0x919f33a98e84015b2034b57b5ffe9340220926b2c6e45f86fd79ec879dbe06a148ae68b77b73bf7d01bd638a81165617", + "0x95c13e78d89474a47fbc0664f6f806744b75dede95a479bbf844db4a7f4c3ae410ec721cb6ffcd9fa9c323da5740d5ae", + "0xab7151acc41fffd8ec6e90387700bcd7e1cde291ea669567295bea1b9dd3f1df2e0f31f3588cd1a1c08af8120aca4921", + "0x80e74c5c47414bd6eeef24b6793fb1fa2d8fb397467045fcff887c52476741d5bc4ff8b6d3387cb53ad285485630537f", + "0xa296ad23995268276aa351a7764d36df3a5a3cffd7dbeddbcea6b1f77adc112629fdeffa0918b3242b3ccd5e7587e946", + "0x813d2506a28a2b01cb60f49d6bd5e63c9b056aa56946faf2f33bd4f28a8d947569cfead3ae53166fc65285740b210f86", + "0x924b265385e1646287d8c09f6c855b094daaee74b9e64a0dddcf9ad88c6979f8280ba30c8597b911ef58ddb6c67e9fe3", + "0x8d531513c70c2d3566039f7ca47cd2352fd2d55b25675a65250bdb8b06c3843db7b2d29c626eed6391c238fc651cf350", + "0x82b338181b62fdc81ceb558a6843df767b6a6e3ceedc5485664b4ea2f555904b1a45fbb35f6cf5d96f27da10df82a325", + "0x92e62faaedea83a37f314e1d3cb4faaa200178371d917938e59ac35090be1db4b4f4e0edb78b9c991de202efe4f313d8", + "0x99d645e1b642c2dc065bac9aaa0621bc648c9a8351efb6891559c3a41ba737bd155fb32d7731950514e3ecf4d75980e4", + "0xb34a13968b9e414172fb5d5ece9a39cf2eb656128c3f2f6cc7a9f0c69c6bae34f555ecc8f8837dc34b5e470e29055c78", + "0xa2a0bb7f3a0b23a2cbc6585d59f87cd7e56b2bbcb0ae48f828685edd9f7af0f5edb4c8e9718a0aaf6ef04553ba71f3b7", + "0x8e1a94bec053ed378e524b6685152d2b52d428266f2b6eadd4bcb7c4e162ed21ab3e1364879673442ee2162635b7a4d8", + "0x9944adaff14a85eab81c73f38f386701713b52513c4d4b838d58d4ffa1d17260a6d056b02334850ea9a31677c4b078bd", + "0xa450067c7eceb0854b3eca3db6cf38669d72cb7143c3a68787833cbca44f02c0be9bfbe082896f8a57debb13deb2afb1", + "0x8be4ad3ac9ef02f7df09254d569939757101ee2eda8586fefcd8c847adc1efe5bdcb963a0cafa17651befaafb376a531", + "0x90f6de91ea50255f148ac435e08cf2ac00c772a466e38155bd7e8acf9197af55662c7b5227f88589b71abe9dcf7ba343", + "0x86e5a24f0748b106dee2d4d54e14a3b0af45a96cbee69cac811a4196403ebbee17fd24946d7e7e1b962ac7f66dbaf610", + "0xafdd96fbcda7aa73bf9eeb2292e036c25753d249caee3b9c013009cc22e10d3ec29e2aa6ddbb21c4e949b0c0bccaa7f4", + "0xb5a4e7436d5473647c002120a2cb436b9b28e27ad4ebdd7c5f122b91597c507d256d0cbd889d65b3a908531936e53053", + "0xb632414c3da704d80ac2f3e5e0e9f18a3637cdc2ebeb613c29300745582427138819c4e7b0bec3099c1b8739dac1807b", + "0xa28df1464d3372ce9f37ef1db33cc010f752156afae6f76949d98cd799c0cf225c20228ae86a4da592d65f0cffe3951b", + "0x898b93d0a31f7d3f11f253cb7a102db54b669fd150da302d8354d8e02b1739a47cb9bd88015f3baf12b00b879442464e", + "0x96fb88d89a12049091070cb0048a381902965e67a8493e3991eaabe5d3b7ff7eecd5c94493a93b174df3d9b2c9511755", + "0xb899cb2176f59a5cfba3e3d346813da7a82b03417cad6342f19cc8f12f28985b03bf031e856a4743fd7ebe16324805b0", + "0xa60e2d31bc48e0c0579db15516718a03b73f5138f15037491f4dae336c904e312eda82d50862f4debd1622bb0e56d866", + "0x979fc8b987b5cef7d4f4b58b53a2c278bd25a5c0ea6f41c715142ea5ff224c707de38451b0ad3aa5e749aa219256650a", + "0xb2a75bff18e1a6b9cf2a4079572e41205741979f57e7631654a3c0fcec57c876c6df44733c9da3d863db8dff392b44a3", + "0xb7a0f0e811222c91e3df98ff7f286b750bc3b20d2083966d713a84a2281744199e664879401e77470d44e5a90f3e5181", + "0x82b74ba21c9d147fbc338730e8f1f8a6e7fc847c3110944eb17a48bea5e06eecded84595d485506d15a3e675fd0e5e62", + "0xa7f44eef817d5556f0d1abcf420301217d23c69dd2988f44d91ea1f1a16c322263cbacd0f190b9ba22b0f141b9267b4f", + "0xaadb68164ede84fc1cb3334b3194d84ba868d5a88e4c9a27519eef4923bc4abf81aab8114449496c073c2a6a0eb24114", + "0xb5378605fabe9a8c12a5dc55ef2b1de7f51aedb61960735c08767a565793cea1922a603a6983dc25f7cea738d0f7c40d", + "0xa97a4a5cd8d51302e5e670aee78fe6b5723f6cc892902bbb4f131e82ca1dfd5de820731e7e3367fb0c4c1922a02196e3", + "0x8bdfeb15c29244d4a28896f2b2cb211243cd6a1984a3f5e3b0ebe5341c419beeab3304b390a009ffb47588018034b0ea", + "0xa9af3022727f2aa2fca3b096968e97edad3f08edcbd0dbca107b892ae8f746a9c0485e0d6eb5f267999b23a845923ed0", + "0x8e7594034feef412f055590fbb15b6322dc4c6ab7a4baef4685bd13d71a83f7d682b5781bdfa0d1c659489ce9c2b8000", + "0x84977ca6c865ebee021c58106c1a4ad0c745949ecc5332948002fd09bd9b890524878d0c29da96fd11207621136421fe", + "0x8687551a79158e56b2375a271136756313122132a6670fa51f99a1b5c229ed8eea1655a734abae13228b3ebfd2a825dd", + "0xa0227d6708979d99edfc10f7d9d3719fd3fc68b0d815a7185b60307e4c9146ad2f9be2b8b4f242e320d4288ceeb9504c", + "0x89f75583a16735f9dd8b7782a130437805b34280ccea8dac6ecaee4b83fe96947e7b53598b06fecfffdf57ffc12cc445", + "0xa0056c3353227f6dd9cfc8e3399aa5a8f1d71edf25d3d64c982910f50786b1e395c508d3e3727ac360e3e040c64b5298", + "0xb070e61a6d813626144b312ded1788a6d0c7cec650a762b2f8df6e4743941dd82a2511cd956a3f141fc81e15f4e092da", + "0xb4e6db232e028a1f989bb5fc13416711f42d389f63564d60851f009dcffac01acfd54efa307aa6d4c0f932892d4e62b0", + "0x89b5991a67db90024ddd844e5e1a03ef9b943ad54194ae0a97df775dde1addf31561874f4e40fbc37a896630f3bbda58", + "0xad0e8442cb8c77d891df49cdb9efcf2b0d15ac93ec9be1ad5c3b3cca1f4647b675e79c075335c1f681d56f14dc250d76", + "0xb5d55a6ae65bb34dd8306806cb49b5ccb1c83a282ee47085cf26c4e648e19a52d9c422f65c1cd7e03ca63e926c5e92ea", + "0xb749501347e5ec07e13a79f0cb112f1b6534393458b3678a77f02ca89dca973fa7b30e55f0b25d8b92b97f6cb0120056", + "0x94144b4a3ffc5eec6ba35ce9c245c148b39372d19a928e236a60e27d7bc227d18a8cac9983851071935d8ffb64b3a34f", + "0x92bb4f9f85bc8c028a3391306603151c6896673135f8a7aefedd27acb322c04ef5dac982fc47b455d6740023e0dd3ea3", + "0xb9633a4a101461a782fc2aa092e9dbe4e2ad00987578f18cd7cf0021a909951d60fe79654eb7897806795f93c8ff4d1c", + "0x809f0196753024821b48a016eca5dbb449a7c55750f25981bb7a4b4c0e0846c09b8f6128137905055fc43a3f0deb4a74", + "0xa27dc9cdd1e78737a443570194a03d89285576d3d7f3a3cf15cc55b3013e42635d4723e2e8fe1d0b274428604b630db9", + "0x861f60f0462e04cd84924c36a28163def63e777318d00884ab8cb64c8df1df0bce5900342163edb60449296484a6c5bf", + "0xb7bc23fb4e14af4c4704a944253e760adefeca8caee0882b6bbd572c84434042236f39ae07a8f21a560f486b15d82819", + "0xb9a6eb492d6dd448654214bd01d6dc5ff12067a11537ab82023fc16167507ee25eed2c91693912f4155d1c07ed9650b3", + "0x97678af29c68f9a5e213bf0fb85c265303714482cfc4c2c00b4a1e8a76ed08834ee6af52357b143a1ca590fb0265ea5a", + "0x8a15b499e9eca5b6cac3070b5409e8296778222018ad8b53a5d1f6b70ad9bb10c68a015d105c941ed657bf3499299e33", + "0xb487fefede2e8091f2c7bfe85770db2edff1db83d4effe7f7d87bff5ab1ace35e9b823a71adfec6737fede8d67b3c467", + "0x8b51b916402aa2c437fce3bcad6dad3be8301a1a7eab9d163085b322ffb6c62abf28637636fe6114573950117fc92898", + "0xb06a2106d031a45a494adec0881cb2f82275dff9dcdd2bc16807e76f3bec28a6734edd3d54f0be8199799a78cd6228ad", + "0xaf0a185391bbe2315eb97feac98ad6dd2e5d931d012c621abd6e404a31cc188b286fef14871762190acf086482b2b5e2", + "0x8e78ee8206506dd06eb7729e32fceda3bebd8924a64e4d8621c72e36758fda3d0001af42443851d6c0aea58562870b43", + "0xa1ba52a569f0461aaf90b49b92be976c0e73ec4a2c884752ee52ffb62dd137770c985123d405dfb5de70692db454b54a", + "0x8d51b692fa1543c51f6b62b9acb8625ed94b746ef96c944ca02859a4133a5629da2e2ce84e111a7af8d9a5b836401c64", + "0xa7a20d45044cf6492e0531d0b8b26ffbae6232fa05a96ed7f06bdb64c2b0f5ca7ec59d5477038096a02579e633c7a3ff", + "0x84df867b98c53c1fcd4620fef133ee18849c78d3809d6aca0fb6f50ff993a053a455993f216c42ab6090fa5356b8d564", + "0xa7227c439f14c48e2577d5713c97a5205feb69acb0b449152842e278fa71e8046adfab468089c8b2288af1fc51fa945b", + "0x855189b3a105670779997690876dfaa512b4a25a24931a912c2f0f1936971d2882fb4d9f0b3d9daba77eaf660e9d05d5", + "0xb5696bd6706de51c502f40385f87f43040a5abf99df705d6aac74d88c913b8ecf7a99a63d7a37d9bdf3a941b9e432ff5", + "0xab997beb0d6df9c98d5b49864ef0b41a2a2f407e1687dfd6089959757ba30ed02228940b0e841afe6911990c74d536c4", + "0xb36b65f85546ebfdbe98823d5555144f96b4ab39279facd19c0de3b8919f105ba0315a0784dce4344b1bc62d8bb4a5a3", + "0xb8371f0e4450788720ac5e0f6cd3ecc5413d33895083b2c168d961ec2b5c3de411a4cc0712481cbe8df8c2fa1a7af006", + "0x98325d8026b810a8b7a114171ae59a57e8bbc9848e7c3df992efc523621729fd8c9f52114ce01d7730541a1ada6f1df1", + "0x8d0e76dbd37806259486cd9a31bc8b2306c2b95452dc395546a1042d1d17863ef7a74c636b782e214d3aa0e8d717f94a", + "0xa4e15ead76da0214d702c859fb4a8accdcdad75ed08b865842bd203391ec4cba2dcc916455e685f662923b96ee0c023f", + "0x8618190972086ebb0c4c1b4a6c94421a13f378bc961cc8267a301de7390c5e73c3333864b3b7696d81148f9d4843fd02", + "0x85369d6cc7342e1aa15b59141517d8db8baaaeb7ab9670f3ba3905353948d575923d283b7e5a05b13a30e7baf1208a86", + "0x87c51ef42233c24a6da901f28c9a075d9ba3c625687c387ad6757b72ca6b5a8885e6902a3082da7281611728b1e45f26", + "0xaa6348a4f71927a3106ad0ea8b02fc8d8c65531e4ab0bd0a17243e66f35afe252e40ab8eef9f13ae55a72566ffdaff5c", + "0x96a3bc976e9d03765cc3fee275fa05b4a84c94fed6b767e23ca689394501e96f56f7a97cffddc579a6abff632bf153be", + "0x97dbf96c6176379fdb2b888be4e757b2bca54e74124bd068d3fa1dbd82a011bbeb75079da38e0cd22a761fe208ecad9b", + "0xb70cf0a1d14089a4129ec4e295313863a59da8c7e26bf74cc0e704ed7f0ee4d7760090d0ddf7728180f1bf2c5ac64955", + "0x882d664714cc0ffe53cbc9bef21f23f3649824f423c4dbad1f893d22c4687ab29583688699efc4d5101aa08b0c3e267a", + "0x80ecb7cc963e677ccaddbe3320831dd6ee41209acf4ed41b16dc4817121a3d86a1aac9c4db3d8c08a55d28257088af32", + "0xa25ba667d832b145f9ce18c3f9b1bd00737aa36db020e1b99752c8ef7d27c6c448982bd8d352e1b6df266b8d8358a8d5", + "0x83734841c13dee12759d40bdd209b277e743b0d08cc0dd1e0b7afd2d65bfa640400eefcf6be4a52e463e5b3d885eeac6", + "0x848d16505b04804afc773aebabb51b36fd8aacfbb0e09b36c0d5d57df3c0a3b92f33e7d5ad0a7006ec46ebb91df42b8c", + "0x909a8d793f599e33bb9f1dc4792a507a97169c87cd5c087310bc05f30afcd247470b4b56dec59894c0fb1d48d39bb54e", + "0x8e558a8559df84a1ba8b244ece667f858095c50bb33a5381e60fcc6ba586b69693566d8819b4246a27287f16846c1dfa", + "0x84d6b69729f5aaa000cd710c2352087592cfbdf20d5e1166977e195818e593fa1a50d1e04566be23163a2523dc1612f1", + "0x9536d262b7a42125d89f4f32b407d737ba8d9242acfc99d965913ab3e043dcac9f7072a43708553562cac4cba841df30", + "0x9598548923ca119d6a15fd10861596601dd1dedbcccca97bb208cdc1153cf82991ea8cc17686fbaa867921065265970c", + "0xb87f2d4af6d026e4d2836bc3d390a4a18e98a6e386282ce96744603bab74974272e97ac2da281afa21885e2cbb3a8001", + "0x991ece62bf07d1a348dd22191868372904b9f8cf065ae7aa4e44fd24a53faf6d851842e35fb472895963aa1992894918", + "0xa8c53dea4c665b30e51d22ca6bc1bc78aaf172b0a48e64a1d4b93439b053877ec26cb5221c55efd64fa841bbf7d5aff4", + "0x93487ec939ed8e740f15335b58617c3f917f72d07b7a369befd479ae2554d04deb240d4a14394b26192efae4d2f4f35d", + "0xa44793ab4035443f8f2968a40e043b4555960193ffa3358d22112093aadfe2c136587e4139ffd46d91ed4107f61ea5e0", + "0xb13fe033da5f0d227c75927d3dacb06dbaf3e1322f9d5c7c009de75cdcba5e308232838785ab69a70f0bedea755e003f", + "0x970a29b075faccd0700fe60d1f726bdebf82d2cc8252f4a84543ebd3b16f91be42a75c9719a39c4096139f0f31393d58", + "0xa4c3eb1f7160f8216fc176fb244df53008ff32f2892363d85254002e66e2de21ccfe1f3b1047589abee50f29b9d507e3", + "0x8c552885eab04ba40922a8f0c3c38c96089c95ff1405258d3f1efe8d179e39e1295cbf67677894c607ae986e4e6b1fb0", + "0xb3671746fa7f848c4e2ae6946894defadd815230b906b419143523cc0597bc1d6c0a4c1e09d49b66b4a2c11cde3a4de3", + "0x937a249a95813a5e2ef428e355efd202e15a37d73e56cfb7e57ea9f943f2ce5ca8026f2f1fd25bf164ba89d07077d858", + "0x83646bdf6053a04aa9e2f112499769e5bd5d0d10f2e13db3ca89bd45c0b3b7a2d752b7d137fb3909f9c62b78166c9339", + "0xb4eac4b91e763666696811b7ed45e97fd78310377ebea1674b58a2250973f80492ac35110ed1240cd9bb2d17493d708c", + "0x82db43a99bc6573e9d92a3fd6635dbbb249ac66ba53099c3c0c8c8080b121dd8243cd5c6e36ba0a4d2525bae57f5c89c", + "0xa64d6a264a681b49d134c655d5fc7756127f1ee7c93d328820f32bca68869f53115c0d27fef35fe71f7bc4fdaed97348", + "0x8739b7a9e2b4bc1831e7f04517771bc7cde683a5e74e052542517f8375a2f64e53e0d5ac925ef722327e7bb195b4d1d9", + "0x8f337cdd29918a2493515ebb5cf702bbe8ecb23b53c6d18920cc22f519e276ca9b991d3313e2d38ae17ae8bdfa4f8b7e", + "0xb0edeab9850e193a61f138ef2739fc42ceec98f25e7e8403bfd5fa34a7bc956b9d0898250d18a69fa4625a9b3d6129da", + "0xa9920f26fe0a6d51044e623665d998745c9eca5bce12051198b88a77d728c8238f97d4196f26e43b24f8841500b998d0", + "0x86e655d61502b979eeeeb6f9a7e1d0074f936451d0a1b0d2fa4fb3225b439a3770767b649256fe481361f481a8dbc276", + "0x84d3b32fa62096831cc3bf013488a9f3f481dfe293ae209ed19585a03f7db8d961a7a9dd0db82bd7f62d612707575d9c", + "0x81c827826ec9346995ffccf62a241e3b2d32f7357acd1b1f8f7a7dbc97022d3eb51b8a1230e23ce0b401d2e535e8cd78", + "0x94a1e40c151191c5b055b21e86f32e69cbc751dcbdf759a48580951834b96a1eed75914c0d19a38aefd21fb6c8d43d0c", + "0xab890222b44bc21b71f7c75e15b6c6e16bb03371acce4f8d4353ff3b8fcd42a14026589c5ed19555a3e15e4d18bfc3a3", + "0xaccb0be851e93c6c8cc64724cdb86887eea284194b10e7a43c90528ed97e9ec71ca69c6fac13899530593756dd49eab2", + "0xb630220aa9e1829c233331413ee28c5efe94ea8ea08d0c6bfd781955078b43a4f92915257187d8526873e6c919c6a1de", + "0xadd389a4d358c585f1274b73f6c3c45b58ef8df11f9d11221f620e241bf3579fba07427b288c0c682885a700cc1fa28d", + "0xa9fe6ca8bf2961a3386e8b8dcecc29c0567b5c0b3bcf3b0f9169f88e372b80151af883871fc5229815f94f43a6f5b2b0", + "0xad839ae003b92b37ea431fa35998b46a0afc3f9c0dd54c3b3bf7a262467b13ff3c323ada1c1ae02ac7716528bdf39e3e", + "0x9356d3fd0edcbbb65713c0f2a214394f831b26f792124b08c5f26e7f734b8711a87b7c4623408da6a091c9aef1f6af3c", + "0x896b25b083c35ac67f0af3784a6a82435b0e27433d4d74cd6d1eafe11e6827827799490fb1c77c11de25f0d75f14e047", + "0x8bfa019391c9627e8e5f05c213db625f0f1e51ec68816455f876c7e55b8f17a4f13e5aae9e3fb9e1cf920b1402ee2b40", + "0x8ba3a6faa6a860a8f3ce1e884aa8769ceded86380a86520ab177ab83043d380a4f535fe13884346c5e51bee68da6ab41", + "0xa8292d0844084e4e3bb7af92b1989f841a46640288c5b220fecfad063ee94e86e13d3d08038ec2ac82f41c96a3bfe14d", + "0x8229bb030b2fc566e11fd33c7eab7a1bb7b49fed872ea1f815004f7398cb03b85ea14e310ec19e1f23e0bdaf60f8f76c", + "0x8cfbf869ade3ec551562ff7f63c2745cc3a1f4d4dc853a0cd42dd5f6fe54228f86195ea8fe217643b32e9f513f34a545", + "0xac52a3c8d3270ddfe1b5630159da9290a5ccf9ccbdef43b58fc0a191a6c03b8a5974cf6e2bbc7bd98d4a40a3581482d7", + "0xab13decb9e2669e33a7049b8eca3ca327c40dea15ad6e0e7fa63ed506db1d258bc36ac88b35f65cae0984e937eb6575d", + "0xb5e748eb1a7a1e274ff0cc56311c198f2c076fe4b7e73e5f80396fe85358549df906584e6bb2c8195b3e2be7736850a5", + "0xb5cb911325d8f963c41f691a60c37831c7d3bbd92736efa33d1f77a22b3fde7f283127256c2f47e197571e6fe0b46149", + "0x8a01dc6ed1b55f26427a014faa347130738b191a06b800e32042a46c13f60b49534520214359d68eb2e170c31e2b8672", + "0xa72fa874866e19b2efb8e069328362bf7921ec375e3bcd6b1619384c3f7ee980f6cf686f3544e9374ff54b4d17a1629c", + "0x8db21092f7c5f110fba63650b119e82f4b42a997095d65f08f8237b02dd66fdf959f788df2c35124db1dbd330a235671", + "0x8c65d50433d9954fe28a09fa7ba91a70a590fe7ba6b3060f5e4be0f6cef860b9897fa935fb4ebc42133524eb071dd169", + "0xb4614058e8fa21138fc5e4592623e78b8982ed72aa35ee4391b164f00c68d277fa9f9eba2eeefc890b4e86eba5124591", + "0xab2ad3a1bce2fbd55ca6b7c23786171fe1440a97d99d6df4d80d07dd56ac2d7203c294b32fc9e10a6c259381a73f24a1", + "0x812ae3315fdc18774a8da3713a4679e8ed10b9405edc548c00cacbe25a587d32040566676f135e4723c5dc25df5a22e9", + "0xa464b75f95d01e5655b54730334f443c8ff27c3cb79ec7af4b2f9da3c2039c609908cd128572e1fd0552eb597e8cef8d", + "0xa0db3172e93ca5138fe419e1c49a1925140999f6eff7c593e5681951ee0ec1c7e454c851782cbd2b8c9bc90d466e90e0", + "0x806db23ba7d00b87d544eed926b3443f5f9c60da6b41b1c489fba8f73593b6e3b46ebfcab671ee009396cd77d5e68aa1", + "0x8bfdf2c0044cc80260994e1c0374588b6653947b178e8b312be5c2a05e05767e98ea15077278506aee7df4fee1aaf89e", + "0x827f6558c16841b5592ff089c9c31e31eb03097623524394813a2e4093ad2d3f8f845504e2af92195aaa8a1679d8d692", + "0x925c4f8eab2531135cd71a4ec88e7035b5eea34ba9d799c5898856080256b4a15ed1a746e002552e2a86c9c157e22e83", + "0xa9f9a368f0e0b24d00a35b325964c85b69533013f9c2cfad9708be5fb87ff455210f8cb8d2ce3ba58ca3f27495552899", + "0x8ac0d3bebc1cae534024187e7c71f8927ba8fcc6a1926cb61c2b6c8f26bb7831019e635a376146c29872a506784a4aaa", + "0x97c577be2cbbfdb37ad754fae9df2ada5fc5889869efc7e18a13f8e502fbf3f4067a509efbd46fd990ab47ce9a70f5a8", + "0x935e7d82bca19f16614aa43b4a3474e4d20d064e4bfdf1cea2909e5c9ab72cfe3e54dc50030e41ee84f3588cebc524e9", + "0x941aafc08f7c0d94cebfbb1f0aad5202c02e6e37f2c12614f57e727efa275f3926348f567107ee6d8914dd71e6060271", + "0xaf0fbc1ba05b4b5b63399686df3619968be5d40073de0313cbf5f913d3d4b518d4c249cdd2176468ccaa36040a484f58", + "0xa0c414f23f46ca6d69ce74c6f8a00c036cb0edd098af0c1a7d39c802b52cfb2d5dbdf93fb0295453d4646e2af7954d45", + "0x909cf39e11b3875bb63b39687ae1b5d1f5a15445e39bf164a0b14691b4ddb39a8e4363f584ef42213616abc4785b5d66", + "0xa92bac085d1194fbd1c88299f07a061d0bdd3f980b663e81e6254dbb288bf11478c0ee880e28e01560f12c5ccb3c0103", + "0x841705cd5cd76b943e2b7c5e845b9dd3c8defe8ef67e93078d6d5e67ade33ad4b0fd413bc196f93b0a4073c855cd97d4", + "0x8e7eb8364f384a9161e81d3f1d52ceca9b65536ae49cc35b48c3e2236322ba4ae9973e0840802d9fa4f4d82ea833544f", + "0xaed3ab927548bc8bec31467ba80689c71a168e34f50dcb6892f19a33a099f5aa6b3f9cb79f5c0699e837b9a8c7f27efe", + "0xb8fbf7696210a36e20edabd77839f4dfdf50d6d015cdf81d587f90284a9bcef7d2a1ff520728d7cc69a4843d6c20dedd", + "0xa9d533769ce6830211c884ae50a82a7bf259b44ac71f9fb11f0296fdb3981e6b4c1753fe744647b247ebc433a5a61436", + "0x8b4bdf90d33360b7f428c71cde0a49fb733badba8c726876945f58c620ce7768ae0e98fc8c31fa59d8955a4823336bb1", + "0x808d42238e440e6571c59e52a35ae32547d502dc24fd1759d8ea70a7231a95859baf30b490a4ba55fa2f3aaa11204597", + "0x85594701f1d2fee6dc1956bc44c7b31db93bdeec2f3a7d622c1a08b26994760773e3d57521a44cfd7e407ac3fd430429", + "0xa66de045ce7173043a6825e9dc440ac957e2efb6df0a337f4f8003eb0c719d873a52e6eba3cb0d69d977ca37d9187674", + "0x87a1c6a1fdff993fa51efa5c3ba034c079c0928a7d599b906336af7c2dcab9721ceaf3108c646490af9dff9a754f54b3", + "0x926424223e462ceb75aed7c22ade8a7911a903b7e5dd4bc49746ddce8657f4616325cd12667d4393ac52cdd866396d0e", + "0xb5dc96106593b42b30f06f0b0a1e0c1aafc70432e31807252d3674f0b1ea5e58eac8424879d655c9488d85a879a3e572", + "0x997ca0987735cc716507cb0124b1d266d218b40c9d8e0ecbf26a1d65719c82a637ce7e8be4b4815d307df717bde7c72a", + "0x92994d3f57a569b7760324bb5ae4e8e14e1633d175dab06aa57b8e391540e05f662fdc08b8830f489a063f59b689a688", + "0xa8087fcc6aa4642cb998bea11facfe87eb33b90a9aa428ab86a4124ad032fc7d2e57795311a54ec9f55cc120ebe42df1", + "0xa9bd7d1de6c0706052ca0b362e2e70e8c8f70f1f026ea189b4f87a08ce810297ebfe781cc8004430776c54c1a05ae90c", + "0x856d33282e8a8e33a3d237fb0a0cbabaf77ba9edf2fa35a831fdafcadf620561846aa6cbb6bdc5e681118e1245834165", + "0x9524a7aa8e97a31a6958439c5f3339b19370f03e86b89b1d02d87e4887309dbbe9a3a8d2befd3b7ed5143c8da7e0a8ad", + "0x824fdf433e090f8acbd258ac7429b21f36f9f3b337c6d0b71d1416a5c88a767883e255b2888b7c906dd2e9560c4af24c", + "0x88c7fee662ca7844f42ed5527996b35723abffd0d22d4ca203b9452c639a5066031207a5ae763dbc0865b3299d19b1ec", + "0x919dca5c5595082c221d5ab3a5bc230f45da7f6dec4eb389371e142c1b9c6a2c919074842479c2844b72c0d806170c0c", + "0xb939be8175715e55a684578d8be3ceff3087f60fa875fff48e52a6e6e9979c955efef8ff67cfa2b79499ea23778e33b0", + "0x873b6db725e7397d11bc9bed9ac4468e36619135be686790a79bc6ed4249058f1387c9a802ea86499f692cf635851066", + "0xaeae06db3ec47e9e5647323fa02fac44e06e59b885ad8506bf71b184ab3895510c82f78b6b22a5d978e8218e7f761e9f", + "0xb99c0a8359c72ab88448bae45d4bf98797a26bca48b0d4460cd6cf65a4e8c3dd823970ac3eb774ae5d0cea4e7fadf33e", + "0x8f10c8ec41cdfb986a1647463076a533e6b0eec08520c1562401b36bb063ac972aa6b28a0b6ce717254e35940b900e3c", + "0xa106d9be199636d7add43b942290269351578500d8245d4aae4c083954e4f27f64740a3138a66230391f2d0e6043a8de", + "0xa469997908244578e8909ff57cffc070f1dbd86f0098df3cfeb46b7a085cfecc93dc69ee7cad90ff1dc5a34d50fe580c", + "0xa4ef087bea9c20eb0afc0ee4caba7a9d29dfa872137828c721391273e402fb6714afc80c40e98bbd8276d3836bffa080", + "0xb07a013f73cd5b98dae0d0f9c1c0f35bff8a9f019975c4e1499e9bee736ca6fcd504f9bc32df1655ff333062382cff04", + "0xb0a77188673e87cc83348c4cc5db1eecf6b5184e236220c8eeed7585e4b928db849944a76ec60ef7708ef6dac02d5592", + "0xb1284b37e59b529f0084c0dacf0af6c0b91fc0f387bf649a8c74819debf606f7b07fc3e572500016fb145ec2b24e9f17", + "0x97b20b5b4d6b9129da185adfbf0d3d0b0faeba5b9715f10299e48ea0521709a8296a9264ce77c275a59c012b50b6519a", + "0xb9d37e946fae5e4d65c1fbfacc8a62e445a1c9d0f882e60cca649125af303b3b23af53c81d7bac544fb7fcfc7a314665", + "0x8e5acaac379f4bb0127efbef26180f91ff60e4c525bc9b798fc50dfaf4fe8a5aa84f18f3d3cfb8baead7d1e0499af753", + "0xb0c0b8ab1235bf1cda43d4152e71efc1a06c548edb964eb4afceb201c8af24240bf8ab5cae30a08604e77432b0a5faf0", + "0x8cc28d75d5c8d062d649cbc218e31c4d327e067e6dbd737ec0a35c91db44fbbd0d40ec424f5ed79814add16947417572", + "0x95ae6219e9fd47efaa9cb088753df06bc101405ba50a179d7c9f7c85679e182d3033f35b00dbba71fdcd186cd775c52e", + "0xb5d28fa09f186ebc5aa37453c9b4d9474a7997b8ae92748ecb940c14868792292ac7d10ade01e2f8069242b308cf97e5", + "0x8c922a0faa14cc6b7221f302df3342f38fc8521ec6c653f2587890192732c6da289777a6cd310747ea7b7d104af95995", + "0xb9ad5f660b65230de54de535d4c0fcae5bc6b59db21dea5500fdc12eea4470fb8ea003690fdd16d052523418d5e01e8c", + "0xa39a9dd41a0ff78c82979483731f1cd68d3921c3e9965869662c22e02dde3877802e180ba93f06e7346f96d9fa9261d2", + "0x8b32875977ec372c583b24234c27ed73aef00cdff61eb3c3776e073afbdeade548de9497c32ec6d703ff8ad0a5cb7fe4", + "0x9644cbe755a5642fe9d26cfecf170d3164f1848c2c2e271d5b6574a01755f3980b3fc870b98cf8528fef6ecef4210c16", + "0x81ea9d1fdd9dd66d60f40ce0712764b99da9448ae0b300f8324e1c52f154e472a086dda840cb2e0b9813dc8ce8afd4b5", + "0x906aaa4a7a7cdf01909c5cfbc7ded2abc4b869213cbf7c922d4171a4f2e637e56f17020b852ad339d83b8ac92f111666", + "0x939b5f11acbdeff998f2a080393033c9b9d8d5c70912ea651c53815c572d36ee822a98d6dfffb2e339f29201264f2cf4", + "0xaba4898bf1ccea9b9e2df1ff19001e05891581659c1cbbde7ee76c349c7fc7857261d9785823c9463a8aea3f40e86b38", + "0x83ca1a56b8a0be4820bdb5a9346357c68f9772e43f0b887729a50d2eb2a326bbcede676c8bf2e51d7c89bbd8fdb778a6", + "0x94e86e9fe6addfe2c3ee3a547267ed921f4230d877a85bb4442c2d9350c2fa9a9c54e6fe662de82d1a2407e4ab1691c2", + "0xa0cc3bdef671a59d77c6984338b023fa2b431b32e9ed2abe80484d73edc6540979d6f10812ecc06d4d0c5d4eaca7183c", + "0xb5343413c1b5776b55ea3c7cdd1f3af1f6bd802ea95effe3f2b91a523817719d2ecc3f8d5f3cc2623ace7e35f99ca967", + "0x92085d1ed0ed28d8cabe3e7ff1905ed52c7ceb1eac5503760c52fb5ee3a726aba7c90b483c032acc3f166b083d7ec370", + "0x8ec679520455275cd957fca8122724d287db5df7d29f1702a322879b127bff215e5b71d9c191901465d19c86c8d8d404", + "0xb65eb2c63d8a30332eb24ee8a0c70156fc89325ebbb38bacac7cf3f8636ad8a472d81ccca80423772abc00192d886d8a", + "0xa9fe1c060b974bee4d590f2873b28635b61bfcf614e61ff88b1be3eee4320f4874e21e8d666d8ac8c9aba672efc6ecae", + "0xb3fe2a9a389c006a831dea7e777062df84b5c2803f9574d7fbe10b7e1c125817986af8b6454d6be9d931a5ac94cfe963", + "0x95418ad13b734b6f0d33822d9912c4c49b558f68d08c1b34a0127fcfa666bcae8e6fda8832d2c75bb9170794a20e4d7c", + "0xa9a7df761e7f18b79494bf429572140c8c6e9d456c4d4e336184f3f51525a65eb9582bea1e601bdb6ef8150b7ca736a5", + "0xa0de03b1e75edf7998c8c1ac69b4a1544a6fa675a1941950297917366682e5644a4bda9cdeedfaf9473d7fccd9080b0c", + "0xa61838af8d95c95edf32663a68f007d95167bf6e41b0c784a30b22d8300cfdd5703bd6d16e86396638f6db6ae7e42a85", + "0x8866d62084d905c145ff2d41025299d8b702ac1814a7dec4e277412c161bc9a62fed735536789cb43c88693c6b423882", + "0x91da22c378c81497fe363e7f695c0268443abee50f8a6625b8a41e865638a643f07b157ee566de09ba09846934b4e2d7", + "0x941d21dd57c9496aa68f0c0c05507405fdd413acb59bc668ce7e92e1936c68ec4b065c3c30123319884149e88228f0b2", + "0xa77af9b094bc26966ddf2bf9e1520c898194a5ccb694915950dadc204facbe3066d3d89f50972642d76b14884cfbaa21", + "0x8e76162932346869f4618bde744647f7ab52ab498ad654bdf2a4feeb986ac6e51370841e5acbb589e38b6e7142bb3049", + "0xb60979ace17d6937ece72e4f015da4657a443dd01cebc7143ef11c09e42d4aa8855999a65a79e2ea0067f31c9fc2ab0f", + "0xb3e2ffdd5ee6fd110b982fd4fad4b93d0fca65478f986d086eeccb0804960bfaa1919afa743c2239973ea65091fe57d2", + "0x8ce0ce05e7d7160d44574011da687454dbd3c8b8290aa671731b066e2c82f8cf2d63cb8e932d78c6122ec610e44660e6", + "0xab005dd8d297045c39e2f72fb1c48edb501ccf3575d3d04b9817b3afee3f0bb0f3f53f64bda37d1d9cde545aae999bae", + "0x95bd7edb4c4cd60e3cb8a72558845a3cce6bb7032ccdf33d5a49ebb6ddf203bc3c79e7b7e550735d2d75b04c8b2441e8", + "0x889953ee256206284094e4735dbbb17975bafc7c3cb94c9fbfee4c3e653857bfd49e818f64a47567f721b98411a3b454", + "0xb188423e707640ab0e75a061e0b62830cde8afab8e1ad3dae30db69ffae4e2fc005bababbdcbd7213b918ed4f70e0c14", + "0xa97e0fafe011abd70d4f99a0b36638b3d6e7354284588f17a88970ed48f348f88392779e9a038c6cbc9208d998485072", + "0x87db11014a91cb9b63e8dfaa82cdebca98272d89eb445ee1e3ff9dbaf2b3fad1a03b888cffc128e4fe208ed0dddece0f", + "0xaad2e40364edd905d66ea4ac9d51f9640d6fda9a54957d26ba233809851529b32c85660fa401dbee3679ec54fa6dd966", + "0x863e99336ca6edf03a5a259e59a2d0f308206e8a2fb320cfc0be06057366df8e0f94b33a28f574092736b3c5ada84270", + "0xb34bcc56a057589f34939a1adc51de4ff6a9f4fee9c7fa9aa131e28d0cf0759a0c871b640162acdfbf91f3f1b59a3703", + "0x935dd28f2896092995c5eff1618e5b6efe7a40178888d7826da9b0503c2d6e68a28e7fac1a334e166d0205f0695ef614", + "0xb842cd5f8f5de5ca6c68cb4a5c1d7b451984930eb4cc18fd0934d52fdc9c3d2d451b1c395594d73bc3451432bfba653f", + "0x9014537885ce2debad736bc1926b25fdab9f69b216bf024f589c49dc7e6478c71d595c3647c9f65ff980b14f4bb2283b", + "0x8e827ccca1dd4cd21707140d10703177d722be0bbe5cac578db26f1ef8ad2909103af3c601a53795435b27bf95d0c9ed", + "0x8a0b8ad4d466c09d4f1e9167410dbe2edc6e0e6229d4b3036d30f85eb6a333a18b1c968f6ca6d6889bb08fecde017ef4", + "0x9241ee66c0191b06266332dc9161dede384c4bb4e116dbd0890f3c3790ec5566da4568243665c4725b718ac0f6b5c179", + "0xaeb4d5fad81d2b505d47958a08262b6f1b1de9373c2c9ba6362594194dea3e002ab03b8cbb43f867be83065d3d370f19", + "0x8781bc83bb73f7760628629fe19e4714b494dbed444c4e4e4729b7f6a8d12ee347841a199888794c2234f51fa26fc2b9", + "0xb58864f0acd1c2afa29367e637cbde1968d18589245d9936c9a489c6c495f54f0113ecdcbe4680ac085dd3c397c4d0c3", + "0x94a24284afaeead61e70f3e30f87248d76e9726759445ca18cdb9360586c60cc9f0ec1c397f9675083e0b56459784e2e", + "0xaed358853f2b54dcbddf865e1816c2e89be12e940e1abfa661e2ee63ffc24a8c8096be2072fa83556482c0d89e975124", + "0xb95374e6b4fc0765708e370bc881e271abf2e35c08b056a03b847e089831ef4fe3124b9c5849d9c276eb2e35b3daf264", + "0xb834cdbcfb24c8f84bfa4c552e7fadc0028a140952fd69ed13a516e1314a4cd35d4b954a77d51a1b93e1f5d657d0315d", + "0x8fb6d09d23bfa90e7443753d45a918d91d75d8e12ec7d016c0dfe94e5c592ba6aaf483d2f16108d190822d955ad9cdc3", + "0xaa315cd3c60247a6ad4b04f26c5404c2713b95972843e4b87b5a36a89f201667d70f0adf20757ebe1de1b29ae27dda50", + "0xa116862dca409db8beff5b1ccd6301cdd0c92ca29a3d6d20eb8b87f25965f42699ca66974dd1a355200157476b998f3b", + "0xb4c2f5fe173c4dc8311b60d04a65ce1be87f070ac42e13cd19c6559a2931c6ee104859cc2520edebbc66a13dc7d30693", + "0x8d4a02bf99b2260c334e7d81775c5cf582b00b0c982ce7745e5a90624919028278f5e9b098573bad5515ce7fa92a80c8", + "0x8543493bf564ce6d97bd23be9bff1aba08bd5821ca834f311a26c9139c92a48f0c2d9dfe645afa95fec07d675d1fd53b", + "0x9344239d13fde08f98cb48f1f87d34cf6abe8faecd0b682955382a975e6eed64e863fa19043290c0736261622e00045c", + "0xaa49d0518f343005ca72b9e6c7dcaa97225ce6bb8b908ebbe7b1a22884ff8bfb090890364e325a0d414ad180b8f161d1", + "0x907d7fd3e009355ab326847c4a2431f688627faa698c13c03ffdd476ecf988678407f029b8543a475dcb3dafdf2e7a9c", + "0x845f1f10c6c5dad2adc7935f5cd2e2b32f169a99091d4f1b05babe7317b9b1cdce29b5e62f947dc621b9acbfe517a258", + "0x8f3be8e3b380ea6cdf9e9c237f5e88fd5a357e5ded80ea1fc2019810814de82501273b4da38916881125b6fa0cfd4459", + "0xb9c7f487c089bf1d20c822e579628db91ed9c82d6ca652983aa16d98b4270c4da19757f216a71b9c13ddee3e6e43705f", + "0x8ba2d8c88ad2b872db104ea8ddbb006ec2f3749fd0e19298a804bb3a5d94de19285cc7fb19fee58a66f7851d1a66c39f", + "0x9375ecd3ed16786fe161af5d5c908f56eeb467a144d3bbddfc767e90065b7c94fc53431adebecba2b6c9b5821184d36e", + "0xa49e069bfadb1e2e8bff6a4286872e2a9765d62f0eaa4fcb0e5af4bbbed8be3510fb19849125a40a8a81d1e33e81c3eb", + "0x9522cc66757b386aa6b88619525c8ce47a5c346d590bb3647d12f991e6c65c3ab3c0cfc28f0726b6756c892eae1672be", + "0xa9a0f1f51ff877406fa83a807aeb17b92a283879f447b8a2159653db577848cc451cbadd01f70441e351e9ed433c18bc", + "0x8ff7533dcff6be8714df573e33f82cf8e9f2bcaaa43e939c4759d52b754e502717950de4b4252fb904560fc31dce94a4", + "0x959724671e265a28d67c29d95210e97b894b360da55e4cf16e6682e7912491ed8ca14bfaa4dce9c25a25b16af580494f", + "0x92566730c3002f4046c737032487d0833c971e775de59fe02d9835c9858e2e3bc37f157424a69764596c625c482a2219", + "0xa84b47ceff13ed9c3e5e9cdf6739a66d3e7c2bd8a6ba318fefb1a9aecf653bb2981da6733ddb33c4b0a4523acc429d23", + "0xb4ddf571317e44f859386d6140828a42cf94994e2f1dcbcc9777f4eebbfc64fc1e160b49379acc27c4672b8e41835c5d", + "0x8ab95c94072b853d1603fdd0a43b30db617d13c1d1255b99075198e1947bfa5f59aed2b1147548a1b5e986cd9173d15c", + "0x89511f2eab33894fd4b3753d24249f410ff7263052c1fef6166fc63a79816656b0d24c529e45ccce6be28de6e375d916", + "0xa0866160ca63d4f2be1b4ea050dac6b59db554e2ebb4e5b592859d8df339b46fd7cb89aaed0951c3ee540aee982c238a", + "0x8fcc5cbba1b94970f5ff2eb1922322f5b0aa7d918d4b380c9e7abfd57afd8b247c346bff7b87af82efbce3052511cd1b", + "0x99aeb2a5e846b0a2874cca02c66ed40d5569eb65ab2495bc3f964a092e91e1517941f2688e79f8cca49cd3674c4e06dc", + "0xb7a096dc3bad5ca49bee94efd884aa3ff5615cf3825cf95fbe0ce132e35f46581d6482fa82666c7ef5f1643eaee8f1ca", + "0x94393b1da6eaac2ffd186b7725eca582f1ddc8cdd916004657f8a564a7c588175cb443fc6943b39029f5bbe0add3fad8", + "0x884b85fe012ccbcd849cb68c3ad832d83b3ef1c40c3954ffdc97f103b1ed582c801e1a41d9950f6bddc1d11f19d5ec76", + "0xb00061c00131eded8305a7ce76362163deb33596569afb46fe499a7c9d7a0734c084d336b38d168024c2bb42b58e7660", + "0xa439153ac8e6ca037381e3240e7ba08d056c83d7090f16ed538df25901835e09e27de2073646e7d7f3c65056af6e4ce7", + "0x830fc9ca099097d1f38b90e6843dc86f702be9d20bdacc3e52cae659dc41df5b8d2c970effa6f83a5229b0244a86fe22", + "0xb81ea2ffaaff2bb00dd59a9ab825ba5eed4db0d8ac9c8ed1a632ce8f086328a1cddd045fbe1ace289083c1325881b7e7", + "0xb51ea03c58daf2db32c99b9c4789b183365168cb5019c72c4cc91ac30b5fb7311d3db76e6fa41b7cd4a8c81e2f6cdc94", + "0xa4170b2c6d09ca5beb08318730419b6f19215ce6c631c854116f904be3bc30dd85a80c946a8ab054d3e307afaa3f8fbc", + "0x897cc42ff28971ff54d2a55dd6b35cfb8610ac902f3c06e3a5cea0e0a257e870c471236a8e84709211c742a09c5601a6", + "0xa18f2e98d389dace36641621488664ecbb422088ab03b74e67009b8b8acacaaa24fdcf42093935f355207d934adc52a8", + "0x92adcfb678cc2ba19c866f3f2b988fdcb4610567f3ab436cc0cb9acaf5a88414848d71133ebdbec1983e38e6190f1b5f", + "0xa86d43c2ce01b366330d3b36b3ca85f000c3548b8297e48478da1ee7d70d8576d4650cba7852ed125c0d7cb6109aa7f3", + "0x8ed31ceed9445437d7732dce78a762d72ff32a7636bfb3fd7974b7ae15db414d8184a1766915244355deb354fbc5803b", + "0x9268f70032584f416e92225d65af9ea18c466ebc7ae30952d56a4e36fd9ea811dde0a126da9220ba3c596ec54d8a335e", + "0x9433b99ee94f2d3fbdd63b163a2bdf440379334c52308bd24537f7defd807145a062ff255a50d119a7f29f4b85d250e3", + "0x90ce664f5e4628a02278f5cf5060d1a34f123854634b1870906e5723ac9afd044d48289be283b267d45fcbf3f4656aaf", + "0xaaf21c4d59378bb835d42ae5c5e5ab7a3c8c36a59e75997989313197752b79a472d866a23683b329ea69b048b87fa13e", + "0xb83c0589b304cec9ede549fde54f8a7c2a468c6657da8c02169a6351605261202610b2055c639b9ed2d5b8c401fb8f56", + "0x9370f326ea0f170c2c05fe2c5a49189f20aec93b6b18a5572a818cd4c2a6adb359e68975557b349fb54f065d572f4c92", + "0xac3232fa5ce6f03fca238bef1ce902432a90b8afce1c85457a6bee5571c033d4bceefafc863af04d4e85ac72a4d94d51", + "0x80d9ea168ff821b22c30e93e4c7960ce3ad3c1e6deeebedd342a36d01bd942419b187e2f382dbfd8caa34cca08d06a48", + "0xa387a3c61676fb3381eefa2a45d82625635a666e999aba30e3b037ec9e040f414f9e1ad9652abd3bcad63f95d85038db", + "0xa1b229fe32121e0b391b0f6e0180670b9dc89d79f7337de4c77ea7ad0073e9593846f06797c20e923092a08263204416", + "0x92164a9d841a2b828cedf2511213268b698520f8d1285852186644e9a0c97512cafa4bfbe29af892c929ebccd102e998", + "0x82ee2fa56308a67c7db4fd7ef539b5a9f26a1c2cc36da8c3206ba4b08258fbb3cec6fe5cdbd111433fb1ba2a1e275927", + "0x8c77bfe9e191f190a49d46f05600603fa42345592539b82923388d72392404e0b29a493a15e75e8b068dddcd444c2928", + "0x80b927f93ccf79dcf5c5b20bcf5a7d91d7a17bc0401bb7cc9b53a6797feac31026eb114257621f5a64a52876e4474cc1", + "0xb6b68b6501c37804d4833d5a063dd108a46310b1400549074e3cac84acc6d88f73948b7ad48d686de89c1ec043ae8c1a", + "0xab3da00f9bdc13e3f77624f58a3a18fc3728956f84b5b549d62f1033ae4b300538e53896e2d943f160618e05af265117", + "0xb6830e87233b8eace65327fdc764159645b75d2fd4024bf8f313b2dd5f45617d7ecfb4a0b53ccafb5429815a9a1adde6", + "0xb9251cfe32a6dc0440615aadcd98b6b1b46e3f4e44324e8f5142912b597ee3526bea2431e2b0282bb58f71be5b63f65e", + "0xaf8d70711e81cdddfb39e67a1b76643292652584c1ce7ce4feb1641431ad596e75c9120e85f1a341e7a4da920a9cdd94", + "0x98cd4e996594e89495c078bfd52a4586b932c50a449a7c8dfdd16043ca4cda94dafbaa8ad1b44249c99bbcc52152506e", + "0xb9fc6d1c24f48404a4a64fbe3e43342738797905db46e4132aee5f086aaa4c704918ad508aaefa455cfe1b36572e6242", + "0xa365e871d30ba9291cedaba1be7b04e968905d003e9e1af7e3b55c5eb048818ae5b913514fb08b24fb4fbdccbb35d0b8", + "0x93bf99510971ea9af9f1e364f1234c898380677c8e8de9b0dd24432760164e46c787bc9ec42a7ad450500706cf247b2d", + "0xb872f825a5b6e7b9c7a9ddfeded3516f0b1449acc9b4fd29fc6eba162051c17416a31e5be6d3563f424d28e65bab8b8f", + "0xb06b780e5a5e8eb4f4c9dc040f749cf9709c8a4c9ef15e925f442b696e41e5095db0778a6c73bcd329b265f2c6955c8b", + "0x848f1a981f5fc6cd9180cdddb8d032ad32cdfa614fc750d690dbae36cc0cd355cbf1574af9b3ffc8b878f1b2fafb9544", + "0xa03f48cbff3e9e8a3a655578051a5ae37567433093ac500ed0021c6250a51b767afac9bdb194ee1e3eac38a08c0eaf45", + "0xb5be78ce638ff8c4aa84352b536628231d3f7558c5be3bf010b28feac3022e64691fa672f358c8b663904aebe24a54ed", + "0xa9d4da70ff676fa55d1728ba6ab03b471fa38b08854d99e985d88c2d050102d8ccffbe1c90249a5607fa7520b15fe791", + "0x8fe9f7092ffb0b69862c8e972fb1ecf54308c96d41354ed0569638bb0364f1749838d6d32051fff1599112978c6e229c", + "0xae6083e95f37770ecae0df1e010456f165d96cfe9a7278c85c15cffd61034081ce5723e25e2bede719dc9341ec8ed481", + "0xa260891891103089a7afbd9081ea116cfd596fd1015f5b65e10b0961eb37fab7d09c69b7ce4be8bf35e4131848fb3fe4", + "0x8d729fa32f6eb9fd2f6a140bef34e8299a2f3111bffd0fe463aa8622c9d98bfd31a1df3f3e87cd5abc52a595f96b970e", + "0xa30ec6047ae4bc7da4daa7f4c28c93aedb1112cfe240e681d07e1a183782c9ff6783ac077c155af23c69643b712a533f", + "0xac830726544bfe7b5467339e5114c1a75f2a2a8d89453ce86115e6a789387e23551cd64620ead6283dfa4538eb313d86", + "0x8445c135b7a48068d8ed3e011c6d818cfe462b445095e2fbf940301e50ded23f272d799eea47683fc027430ce14613ef", + "0x95785411715c9ae9d8293ce16a693a2aa83e3cb1b4aa9f76333d0da2bf00c55f65e21e42e50e6c5772ce213dd7b4f7a0", + "0xb273b024fa18b7568c0d1c4d2f0c4e79ec509dafac8c5951f14192d63ddbcf2d8a7512c1c1b615cc38fa3e336618e0c5", + "0xa78b9d3ea4b6a90572eb27956f411f1d105fdb577ee2ffeec9f221da9b45db84bfe866af1f29597220c75e0c37a628d8", + "0xa4be2bf058c36699c41513c4d667681ce161a437c09d81383244fc55e1c44e8b1363439d0cce90a3e44581fb31d49493", + "0xb6eef13040f17dd4eba22aaf284d2f988a4a0c4605db44b8d2f4bf9567ac794550b543cc513c5f3e2820242dd704152e", + "0x87eb00489071fa95d008c5244b88e317a3454652dcb1c441213aa16b28cd3ecaa9b22fec0bdd483c1df71c37119100b1", + "0x92d388acdcb49793afca329cd06e645544d2269234e8b0b27d2818c809c21726bc9cf725651b951e358a63c83dedee24", + "0xae27e219277a73030da27ab5603c72c8bd81b6224b7e488d7193806a41343dff2456132274991a4722fdb0ef265d04cd", + "0x97583e08ecb82bbc27c0c8476d710389fa9ffbead5c43001bd36c1b018f29faa98de778644883e51870b69c5ffb558b5", + "0x90a799a8ce73387599babf6b7da12767c0591cadd36c20a7990e7c05ea1aa2b9645654ec65308ee008816623a2757a6a", + "0xa1b47841a0a2b06efd9ab8c111309cc5fc9e1d5896b3e42ed531f6057e5ade8977c29831ce08dbda40348386b1dcc06d", + "0xb92b8ef59bbddb50c9457691bc023d63dfcc54e0fd88bd5d27a09e0d98ac290fc90e6a8f6b88492043bf7c87fac8f3e4", + "0xa9d6240b07d62e22ec8ab9b1f6007c975a77b7320f02504fc7c468b4ee9cfcfd945456ff0128bc0ef2174d9e09333f8d", + "0x8e96534c94693226dc32bca79a595ca6de503af635f802e86442c67e77564829756961d9b701187fe91318da515bf0e6", + "0xb6ba290623cd8dd5c2f50931c0045d1cfb0c30877bc8fe58cbc3ff61ee8da100045a39153916efa1936f4aee0892b473", + "0xb43baa7717fac02d4294f5b3bb5e58a65b3557747e3188b482410388daac7a9c177f762d943fd5dcf871273921213da8", + "0xb9cf00f8fb5e2ef2b836659fece15e735060b2ea39b8e901d3dcbdcf612be8bf82d013833718c04cd46ffaa70b85f42e", + "0x8017d0c57419e414cbba504368723e751ef990cc6f05dad7b3c2de6360adc774ad95512875ab8337d110bf39a42026fa", + "0xae7401048b838c0dcd4b26bb6c56d79d51964a0daba780970b6c97daee4ea45854ea0ac0e4139b3fe60dac189f84df65", + "0x887b237b0cd0f816b749b21db0b40072f9145f7896c36916296973f9e6990ede110f14e5976c906d08987c9836cca57f", + "0xa88c3d5770148aee59930561ca1223aceb2c832fb5417e188dca935905301fc4c6c2c9270bc1dff7add490a125eb81c6", + "0xb6cf9b02c0cd91895ad209e38c54039523f137b5848b9d3ad33ae43af6c20c98434952db375fe378de7866f2d0e8b18a", + "0x84ef3d322ff580c8ad584b1fe4fe346c60866eb6a56e982ba2cf3b021ecb1fdb75ecc6c29747adda86d9264430b3f816", + "0xa0561c27224baf0927ad144cb71e31e54a064c598373fcf0d66aebf98ab7af1d8e2f343f77baefff69a6da750a219e11", + "0xaa5cc43f5b8162b016f5e1b61214c0c9d15b1078911c650b75e6cdfb49b85ee04c6739f5b1687d15908444f691f732de", + "0xad4ac099b935589c7b8fdfdf3db332b7b82bb948e13a5beb121ebd7db81a87d278024a1434bcf0115c54ca5109585c3d", + "0x8a00466abf3f109a1dcd19e643b603d3af23d42794ef8ca2514dd507ecea44a031ac6dbc18bd02f99701168b25c1791e", + "0xb00b5900dfad79645f8bee4e5adc7b84eb22e5b1e67df77ccb505b7fc044a6c08a8ea5faca662414eb945f874f884cea", + "0x950e204e5f17112250b22ea6bb8423baf522fc0af494366f18fe0f949f51d6e6812074a80875cf1ed9c8e7420058d541", + "0x91e5cbf8bb1a1d50c81608c9727b414d0dd2fb467ebc92f100882a3772e54f94979cfdf8e373fdef7c7fcdd60fec9e00", + "0xa093f6a857b8caaff80599c2e89c962b415ecbaa70d8fd973155fa976a284c6b29a855f5f7a3521134d00d2972755188", + "0xb4d55a3551b00da54cc010f80d99ddd2544bde9219a3173dfaadf3848edc7e4056ab532fb75ac26f5f7141e724267663", + "0xa03ea050fc9b011d1b04041b5765d6f6453a93a1819cd9bd6328637d0b428f08526466912895dcc2e3008ee58822e9a7", + "0x99b12b3665e473d01bc6985844f8994fb65cb15745024fb7af518398c4a37ff215da8f054e8fdf3286984ae36a73ca5e", + "0x9972c7e7a7fb12e15f78d55abcaf322c11249cd44a08f62c95288f34f66b51f146302bce750ff4d591707075d9123bd2", + "0xa64b4a6d72354e596d87cda213c4fc2814009461570ccb27d455bbe131f8d948421a71925425b546d8cf63d5458cd64b", + "0x91c215c73b195795ede2228b7ed1f6e37892e0c6b0f4a0b5a16c57aa1100c84df9239054a173b6110d6c2b7f4bf1ce52", + "0x88807198910ec1303480f76a3683870246a995e36adaeadc29c22f0bdba8152fe705bd070b75de657b04934f7d0ccf80", + "0xb37c0026c7b32eb02cacac5b55cb5fe784b8e48b2945c64d3037af83ece556a117f0ff053a5968c2f5fa230e291c1238", + "0x94c768384ce212bc2387e91ce8b45e4ff120987e42472888a317abc9dcdf3563b62e7a61c8e98d7cdcbe272167d91fc6", + "0xa10c2564936e967a390cb14ef6e8f8b04ea9ece5214a38837eda09e79e0c7970b1f83adf017c10efd6faa8b7ffa2c567", + "0xa5085eed3a95f9d4b1269182ea1e0d719b7809bf5009096557a0674bde4201b0ddc1f0f16a908fc468846b3721748ce3", + "0x87468eb620b79a0a455a259a6b4dfbc297d0d53336537b771254dd956b145dc816b195b7002647ea218552e345818a3f", + "0xace2b77ffb87366af0a9cb5d27d6fc4a14323dbbf1643f5f3c4559306330d86461bb008894054394cbfaefeaa0bc2745", + "0xb27f56e840a54fbd793f0b7a7631aa4cee64b5947e4382b2dfb5eb1790270288884c2a19afebe5dc0c6ef335d4531c1c", + "0x876e438633931f7f895062ee16c4b9d10428875f7bc79a8e156a64d379a77a2c45bf5430c5ab94330f03da352f1e9006", + "0xa2512a252587d200d2092b44c914df54e04ff8bcef36bf631f84bde0cf5a732e3dc7f00f662842cfd74b0b0f7f24180e", + "0x827f1bc8f54a35b7a4bd8154f79bcc055e45faed2e74adf7cf21cca95df44d96899e847bd70ead6bb27b9c0ed97bbd8b", + "0xa0c92cf5a9ed843714f3aea9fe7b880f622d0b4a3bf66de291d1b745279accf6ba35097849691370f41732ba64b5966b", + "0xa63f5c1e222775658421c487b1256b52626c6f79cb55a9b7deb2352622cedffb08502042d622eb3b02c97f9c09f9c957", + "0x8cc093d52651e65fb390e186db6cc4de559176af4624d1c44cb9b0e836832419dacac7b8db0627b96288977b738d785d", + "0xaa7b6a17dfcec146134562d32a12f7bd7fe9522e300859202a02939e69dbd345ed7ff164a184296268f9984f9312e8fc", + "0x8ac76721f0d2b679f023d06cbd28c85ae5f4b43c614867ccee88651d4101d4fd352dbdb65bf36bfc3ebc0109e4b0c6f9", + "0x8d350f7c05fc0dcd9a1170748846fb1f5d39453e4cb31e6d1457bed287d96fc393b2ecc53793ca729906a33e59c6834a", + "0xb9913510dfc5056d7ec5309f0b631d1ec53e3a776412ada9aefdaf033c90da9a49fdde6719e7c76340e86599b1f0eec2", + "0x94955626bf4ce87612c5cfffcf73bf1c46a4c11a736602b9ba066328dc52ad6d51e6d4f53453d4ed55a51e0aad810271", + "0xb0fcab384fd4016b2f1e53f1aafd160ae3b1a8865cd6c155d7073ecc1664e05b1d8bca1def39c158c7086c4e1103345e", + "0x827de3f03edfbde08570b72de6662c8bfa499b066a0a27ebad9b481c273097d17a5a0a67f01553da5392ec3f149b2a78", + "0xab7940384c25e9027c55c40df20bd2a0d479a165ced9b1046958353cd69015eeb1e44ed2fd64e407805ba42df10fc7bf", + "0x8ad456f6ff8cd58bd57567d931f923d0c99141978511b17e03cab7390a72b9f62498b2893e1b05c7c22dd274e9a31919", + "0xac75399e999effe564672db426faa17a839e57c5ef735985c70cd559a377adec23928382767b55ed5a52f7b11b54b756", + "0xb17f975a00b817299ac7af5f2024ea820351805df58b43724393bfb3920a8cd747a3bbd4b8286e795521489db3657168", + "0xa2bed800a6d95501674d9ee866e7314063407231491d794f8cf57d5be020452729c1c7cefd8c50dc1540181f5caab248", + "0x9743f5473171271ffdd3cc59a3ae50545901a7b45cd4bc3570db487865f3b73c0595bebabbfe79268809ee1862e86e4a", + "0xb7eab77c2d4687b60d9d7b04e842b3880c7940140012583898d39fcc22d9b9b0a9be2c2e3788b3e6f30319b39c338f09", + "0x8e2b8f797a436a1b661140e9569dcf3e1eea0a77c7ff2bc4ff0f3e49af04ed2de95e255df8765f1d0927fb456a9926b1", + "0x8aefea201d4a1f4ff98ffce94e540bb313f2d4dfe7e9db484a41f13fc316ed02b282e1acc9bc6f56cad2dc2e393a44c9", + "0xb950c17c0e5ca6607d182144aa7556bb0efe24c68f06d79d6413a973b493bfdf04fd147a4f1ab03033a32004cc3ea66f", + "0xb7b8dcbb179a07165f2dc6aa829fad09f582a71b05c3e3ea0396bf9e6fe73076f47035c031c2101e8e38e0d597eadd30", + "0xa9d77ed89c77ec1bf8335d08d41c3c94dcca9fd1c54f22837b4e54506b212aa38d7440126c80648ab7723ff18e65ed72", + "0xa819d6dfd4aef70e52b8402fe5d135f8082d40eb7d3bb5c4d7997395b621e2bb10682a1bad2c9caa33dd818550fc3ec6", + "0x8f6ee34128fac8bbf13ce2d68b2bb363eb4fd65b297075f88e1446ddeac242500eeb4ef0735e105882ff5ba8c44c139b", + "0xb4440e48255c1644bcecf3a1e9958f1ec4901cb5b1122ee5b56ffd02cad1c29c4266999dbb85aa2605c1b125490074d4", + "0xa43304a067bede5f347775d5811cf65a6380a8d552a652a0063580b5c5ef12a0867a39c7912fa219e184f4538eba1251", + "0xa891ad67a790089ffc9f6d53e6a3d63d3556f5f693e0cd8a7d0131db06fd4520e719cfcc3934f0a8f62a95f90840f1d4", + "0xaea6df8e9bb871081aa0fc5a9bafb00be7d54012c5baf653791907d5042a326aeee966fd9012a582cc16695f5baf7042", + "0x8ffa2660dc52ed1cd4eff67d6a84a8404f358a5f713d04328922269bee1e75e9d49afeec0c8ad751620f22352a438e25", + "0x87ec6108e2d63b06abed350f8b363b7489d642486f879a6c3aa90e5b0f335efc2ff2834eef9353951a42136f8e6a1b32", + "0x865619436076c2760d9e87ddc905023c6de0a8d56eef12c98a98c87837f2ca3f27fd26a2ad752252dbcbe2b9f1d5a032", + "0x980437dce55964293cb315c650c5586ffd97e7a944a83f6618af31c9d92c37b53ca7a21bb5bc557c151b9a9e217e7098", + "0x95d128fc369df4ad8316b72aea0ca363cbc7b0620d6d7bb18f7076a8717a6a46956ff140948b0cc4f6d2ce33b5c10054", + "0x8c7212d4a67b9ec70ebbca04358ad2d36494618d2859609163526d7b3acc2fc935ca98519380f55e6550f70a9bc76862", + "0x893a2968819401bf355e85eee0f0ed0406a6d4a7d7f172d0017420f71e00bb0ba984f6020999a3cdf874d3cd8ebcd371", + "0x9103c1af82dece25d87274e89ea0acd7e68c2921c4af3d8d7c82ab0ed9990a5811231b5b06113e7fa43a6bd492b4564f", + "0x99cfd87a94eab7d35466caa4ed7d7bb45e5c932b2ec094258fb14bf205659f83c209b83b2f2c9ccb175974b2a33e7746", + "0x874b6b93e4ee61be3f00c32dd84c897ccd6855c4b6251eb0953b4023634490ed17753cd3223472873cbc6095b2945075", + "0x84a32c0dc4ea60d33aac3e03e70d6d639cc9c4cc435c539eff915017be3b7bdaba33349562a87746291ebe9bc5671f24", + "0xa7057b24208928ad67914e653f5ac1792c417f413d9176ba635502c3f9c688f7e2ee81800d7e3dc0a340c464da2fd9c5", + "0xa03fb9ed8286aacfa69fbd5d953bec591c2ae4153400983d5dbb6cd9ea37fff46ca9e5cceb9d117f73e9992a6c055ad2", + "0x863b2de04e89936c9a4a2b40380f42f20aefbae18d03750fd816c658aee9c4a03df7b12121f795c85d01f415baaeaa59", + "0x8526eb9bd31790fe8292360d7a4c3eed23be23dd6b8b8f01d2309dbfdc0cfd33ad1568ddd7f8a610f3f85a9dfafc6a92", + "0xb46ab8c5091a493d6d4d60490c40aa27950574a338ea5bbc045be3a114af87bdcb160a8c80435a9b7ad815f3cb56a3f3", + "0xaeadc47b41a8d8b4176629557646202f868b1d728b2dda58a347d937e7ffc8303f20d26d6c00b34c851b8aeec547885d", + "0xaebb19fc424d72c1f1822aa7adc744cd0ef7e55727186f8df8771c784925058c248406ebeeaf3c1a9ee005a26e9a10c6", + "0x8ff96e81c1a4a2ab1b4476c21018fae0a67e92129ee36120cae8699f2d7e57e891f5c624902cb1b845b944926a605cc3", + "0x8251b8d2c43fadcaa049a9e7aff838dae4fb32884018d58d46403ac5f3beb5c518bfd45f03b8abb710369186075eb71c", + "0xa8b2a64f865f51a5e5e86a66455c093407933d9d255d6b61e1fd81ffafc9538d73caaf342338a66ba8ee166372a3d105", + "0xaad915f31c6ba7fdc04e2aaac62e84ef434b7ee76a325f07dc430d12c84081999720181067b87d792efd0117d7ee1eab", + "0xa13db3bb60389883fd41d565c54fb5180d9c47ce2fe7a169ae96e01d17495f7f4fa928d7e556e7c74319c4c25d653eb2", + "0xa4491b0198459b3f552855d680a59214eb74e6a4d6c5fa3b309887dc50ebea2ecf6d26c040550f7dc478b452481466fb", + "0x8f017f13d4b1e3f0c087843582b52d5f8d13240912254d826dd11f8703a99a2f3166dfbdfdffd9a3492979d77524276b", + "0x96c3d5dcd032660d50d7cd9db2914f117240a63439966162b10c8f1f3cf74bc83b0f15451a43b31dbd85e4a7ce0e4bb1", + "0xb479ec4bb79573d32e0ec93b92bdd7ec8c26ddb5a2d3865e7d4209d119fd3499eaac527615ffac78c440e60ef3867ae0", + "0xb2c49c4a33aa94b52b6410b599e81ff15490aafa7e43c8031c865a84e4676354a9c81eb4e7b8be6825fdcefd1e317d44", + "0x906dc51d6a90c089b6704b47592805578a6eed106608eeb276832f127e1b8e858b72e448edcbefb497d152447e0e68ff", + "0xb0e81c63b764d7dfbe3f3fddc9905aef50f3633e5d6a4af6b340495124abedcff5700dfd1577bbbed7b6bf97d02719cb", + "0x9304c64701e3b4ed6d146e48a881f7d83a17f58357cca0c073b2bb593afd2d94f6e2a7a1ec511d0a67ad6ff4c3be5937", + "0xb6fdbd12ba05aa598d80b83f70a15ef90e5cba7e6e75fa038540ee741b644cd1f408a6cecfd2a891ef8d902de586c6b5", + "0xb80557871a6521b1b3c74a1ba083ae055b575df607f1f7b04c867ba8c8c181ea68f8d90be6031f4d25002cca27c44da2", + "0xaa7285b8e9712e06b091f64163f1266926a36607f9d624af9996856ed2aaf03a580cb22ce407d1ade436c28b44ca173f", + "0x8148d72b975238b51e6ea389e5486940d22641b48637d7dfadfa603a605bfc6d74a016480023945d0b85935e396aea5d", + "0x8a014933a6aea2684b5762af43dcf4bdbb633cd0428d42d71167a2b6fc563ece5e618bff22f1db2ddb69b845b9a2db19", + "0x990d91740041db770d0e0eb9d9d97d826f09fd354b91c41e0716c29f8420e0e8aac0d575231efba12fe831091ec38d5a", + "0x9454d0d32e7e308ddec57cf2522fb1b67a2706e33fb3895e9e1f18284129ab4f4c0b7e51af25681d248d7832c05eb698", + "0xa5bd434e75bac105cb3e329665a35bce6a12f71dd90c15165777d64d4c13a82bceedb9b48e762bd24034e0fc9fbe45f4", + "0xb09e3b95e41800d4dc29c6ffdaab2cd611a0050347f6414f154a47ee20ee59bf8cf7181454169d479ebce1eb5c777c46", + "0xb193e341d6a047d15eea33766d656d807b89393665a783a316e9ba10518e5515c8e0ade3d6e15641d917a8a172a5a635", + "0xade435ec0671b3621dde69e07ead596014f6e1daa1152707a8c18877a8b067bde2895dd47444ffa69db2bbef1f1d8816", + "0xa7fd3d6d87522dfc56fb47aef9ce781a1597c56a8bbfd796baba907afdc872f753d732bfda1d3402aee6c4e0c189f52d", + "0xa298cb4f4218d0464b2fab393e512bbc477c3225aa449743299b2c3572f065bc3a42d07e29546167ed9e1b6b3b3a3af3", + "0xa9ee57540e1fd9c27f4f0430d194b91401d0c642456c18527127d1f95e2dba41c2c86d1990432eb38a692fda058fafde", + "0x81d6c1a5f93c04e6d8e5a7e0678c1fc89a1c47a5c920bcd36180125c49fcf7c114866b90e90a165823560b19898a7c16", + "0xa4b7a1ec9e93c899b9fd9aaf264c50e42c36c0788d68296a471f7a3447af4dbc81e4fa96070139941564083ec5b5b5a1", + "0xb3364e327d381f46940c0e11e29f9d994efc6978bf37a32586636c0070b03e4e23d00650c1440f448809e1018ef9f6d8", + "0x8056e0913a60155348300e3a62e28b5e30629a90f7dd4fe11289097076708110a1d70f7855601782a3cdc5bdb1ca9626", + "0xb4980fd3ea17bac0ba9ee1c470b17e575bb52e83ebdd7d40c93f4f87bebeaff1c8a679f9d3d09d635f068d37d5bd28bd", + "0x905a9299e7e1853648e398901dfcd437aa575c826551f83520df62984f5679cb5f0ea86aa45ed3e18b67ddc0dfafe809", + "0xab99553bf31a84f2e0264eb34a08e13d8d15e2484aa9352354becf9a15999c76cc568d68274b70a65e49703fc23540d0", + "0xa43681597bc574d2dae8964c9a8dc1a07613d7a1272bdcb818d98c85d44e16d744250c33f3b5e4d552d97396b55e601f", + "0xa54e5a31716fccb50245898c99865644405b8dc920ded7a11f3d19bdc255996054b268e16f2e40273f11480e7145f41e", + "0x8134f3ad5ef2ad4ba12a8a4e4d8508d91394d2bcdc38b7c8c8c0b0a820357ac9f79d286c65220f471eb1adca1d98fc68", + "0x94e2f755e60471578ab2c1adb9e9cea28d4eec9b0e92e0140770bca7002c365fcabfe1e5fb4fe6cfe79a0413712aa3ef", + "0xad48f8d0ce7eb3cc6e2a3086ad96f562e5bed98a360721492ae2e74dc158586e77ec8c35d5fd5927376301b7741bad2b", + "0x8614f0630bdd7fbad3a31f55afd9789f1c605dc85e7dc67e2edfd77f5105f878bb79beded6e9f0b109e38ea7da67e8d5", + "0x9804c284c4c5e77dabb73f655b12181534ca877c3e1e134aa3f47c23b7ec92277db34d2b0a5d38d2b69e5d1c3008a3e3", + "0xa51b99c3088e473afdaa9e0a9f7e75a373530d3b04e44e1148da0726b95e9f5f0c7e571b2da000310817c36f84b19f7f", + "0xac4ff909933b3b76c726b0a382157cdc74ab851a1ac6cef76953c6444441804cc43abb883363f416592e8f6cfbc4550b", + "0xae7d915eb9fc928b65a29d6edbc75682d08584d0014f7bcf17d59118421ae07d26a02137d1e4de6938bcd1ab8ef48fad", + "0x852f7e453b1af89b754df6d11a40d5d41ea057376e8ecacd705aacd2f917457f4a093d6b9a8801837fa0f62986ad7149", + "0x92c6bf5ada5d0c3d4dd8058483de36c215fa98edab9d75242f3eff9db07c734ad67337da6f0eefe23a487bf75a600dee", + "0xa2b42c09d0db615853763552a48d2e704542bbd786aae016eb58acbf6c0226c844f5fb31e428cb6450b9db855f8f2a6f", + "0x880cc07968266dbfdcfbc21815cd69e0eddfee239167ac693fb0413912d816f2578a74f7716eecd6deefa68c6eccd394", + "0xb885b3ace736cd373e8098bf75ba66fa1c6943ca1bc4408cd98ac7074775c4478594f91154b8a743d9c697e1b29f5840", + "0xa51ce78de512bd87bfa0835de819941dffbf18bec23221b61d8096fc9436af64e0693c335b54e7bfc763f287bdca2db6", + "0xa3c76166a3bdb9b06ef696e57603b58871bc72883ee9d45171a30fe6e1d50e30bc9c51b4a0f5a7270e19a77b89733850", + "0xacefc5c6f8a1e7c24d7b41e0fc7f6f3dc0ede6cf3115ffb9a6e54b1d954cbca9bda8ad7a084be9be245a1b8e9770d141", + "0xb420ed079941842510e31cfad117fa11fb6b4f97dfbc6298cb840f27ebaceba23eeaf3f513bcffbf5e4aae946310182d", + "0x95c3bb5ef26c5ed2f035aa5d389c6b3c15a6705b9818a3fefaed28922158b35642b2e8e5a1a620fdad07e75ad4b43af4", + "0x825149f9081ecf07a2a4e3e8b5d21bade86c1a882475d51c55ee909330b70c5a2ac63771c8600c6f38df716af61a3ea1", + "0x873b935aae16d9f08adbc25353cee18af2f1b8d5f26dec6538d6bbddc515f2217ed7d235dcfea59ae61b428798b28637", + "0x9294150843a2bedcedb3bb74c43eb28e759cf9499582c5430bccefb574a8ddd4f11f9929257ff4c153990f9970a2558f", + "0xb619563a811cc531da07f4f04e5c4c6423010ff9f8ed7e6ec9449162e3d501b269fb1c564c09c0429431879b0f45df02", + "0x91b509b87eb09f007d839627514658c7341bc76d468920fe8a740a8cb96a7e7e631e0ea584a7e3dc1172266f641d0f5c", + "0x8b8aceace9a7b9b4317f1f01308c3904d7663856946afbcea141a1c615e21ccad06b71217413e832166e9dd915fbe098", + "0x87b3b36e725833ea0b0f54753c3728c0dbc87c52d44d705ffc709f2d2394414c652d3283bab28dcce09799504996cee0", + "0xb2670aad5691cbf308e4a6a77a075c4422e6cbe86fdba24e9f84a313e90b0696afb6a067eebb42ba2d10340d6a2f6e51", + "0x876784a9aff3d54faa89b2bacd3ff5862f70195d0b2edc58e8d1068b3c9074c0da1cfa23671fe12f35e33b8a329c0ccd", + "0x8b48b9e758e8a8eae182f5cbec96f67d20cca6d3eee80a2d09208eb1d5d872e09ef23d0df8ebbb9b01c7449d0e3e3650", + "0xb79303453100654c04a487bdcadc9e3578bc80930c489a7069a52e8ca1dba36c492c8c899ce025f8364599899baa287d", + "0x961b35a6111da54ece6494f24dacd5ea46181f55775b5f03df0e370c34a5046ac2b4082925855325bb42bc2a2c98381d", + "0xa31feb1be3f5a0247a1f7d487987eb622e34fca817832904c6ee3ee60277e5847945a6f6ea1ac24542c72e47bdf647df", + "0xa12a2aa3e7327e457e1aae30e9612715dd2cfed32892c1cd6dcda4e9a18203af8a44afb46d03b2eed89f6b9c5a2c0c23", + "0xa08265a838e69a2ca2f80fead6ccf16f6366415b920c0b22ee359bcd8d4464ecf156f400a16a7918d52e6d733dd64211", + "0xb723d6344e938d801cca1a00032af200e541d4471fd6cbd38fb9130daa83f6a1dffbbe7e67fc20f9577f884acd7594b2", + "0xa6733d83ec78ba98e72ddd1e7ff79b7adb0e559e256760d0c590a986e742445e8cdf560d44b29439c26d87edd0b07c8c", + "0xa61c2c27d3f7b9ff4695a17afedf63818d4bfba390507e1f4d0d806ce8778d9418784430ce3d4199fd3bdbc2504d2af3", + "0x8332f3b63a6dc985376e8b1b25eeae68be6160fbe40053ba7bcf6f073204f682da72321786e422d3482fd60c9e5aa034", + "0xa280f44877583fbb6b860d500b1a3f572e3ee833ec8f06476b3d8002058e25964062feaa1e5bec1536d734a5cfa09145", + "0xa4026a52d277fcea512440d2204f53047718ebfcae7b48ac57ea7f6bfbc5de9d7304db9a9a6cbb273612281049ddaec5", + "0x95cdf69c831ab2fad6c2535ede9c07e663d2ddccc936b64e0843d2df2a7b1c31f1759c3c20f1e7a57b1c8f0dbb21b540", + "0x95c96cec88806469c277ab567863c5209027cecc06c7012358e5f555689c0d9a5ffb219a464f086b45817e8536b86d2f", + "0xafe38d4684132a0f03d806a4c8df556bf589b25271fbc6fe2e1ed16de7962b341c5003755da758d0959d2e6499b06c68", + "0xa9b77784fda64987f97c3a23c5e8f61b918be0f7c59ba285084116d60465c4a2aaafc8857eb16823282cc83143eb9126", + "0xa830f05881ad3ce532a55685877f529d32a5dbe56cea57ffad52c4128ee0fad0eeaf0da4362b55075e77eda7babe70e5", + "0x992b3ad190d6578033c13ed5abfee4ef49cbc492babb90061e3c51ee4b5790cdd4c8fc1abff1fa2c00183b6b64f0bbbe", + "0xb1015424d9364aeff75de191652dc66484fdbec3e98199a9eb9671ec57bec6a13ff4b38446e28e4d8aedb58dd619cd90", + "0xa745304604075d60c9db36cada4063ac7558e7ec2835d7da8485e58d8422e817457b8da069f56511b02601289fbb8981", + "0xa5ba4330bc5cb3dbe0486ddf995632a7260a46180a08f42ae51a2e47778142132463cc9f10021a9ad36986108fefa1a9", + "0xb419e9fd4babcaf8180d5479db188bb3da232ae77a1c4ed65687c306e6262f8083070a9ac32220cddb3af2ec73114092", + "0xa49e23dc5f3468f3bf3a0bb7e4a114a788b951ff6f23a3396ae9e12cbff0abd1240878a3d1892105413dbc38818e807c", + "0xb7ecc7b4831f650202987e85b86bc0053f40d983f252e9832ef503aea81c51221ce93279da4aa7466c026b2d2070e55d", + "0x96a8c35cb87f84fa84dcd6399cc2a0fd79cc9158ef4bdde4bae31a129616c8a9f2576cd19baa3f497ca34060979aed7d", + "0x8681b2c00aa62c2b519f664a95dcb8faef601a3b961bb4ce5d85a75030f40965e2983871d41ea394aee934e859581548", + "0x85c229a07efa54a713d0790963a392400f55fbb1a43995a535dc6c929f20d6a65cf4efb434e0ad1cb61f689b8011a3bc", + "0x90856f7f3444e5ad44651c28e24cc085a5db4d2ffe79aa53228c26718cf53a6e44615f3c5cda5aa752d5f762c4623c66", + "0x978999b7d8aa3f28a04076f74d11c41ef9c89fdfe514936c4238e0f13c38ec97e51a5c078ebc6409e517bfe7ccb42630", + "0xa099914dd7ed934d8e0d363a648e9038eb7c1ec03fa04dbcaa40f7721c618c3ef947afef7a16b4d7ac8c12aa46637f03", + "0xab2a104fed3c83d16f2cda06878fa5f30c8c9411de71bfb67fd2fc9aa454dcbcf3d299d72f8cc12e919466a50fcf7426", + "0xa4471d111db4418f56915689482f6144efc4664cfb0311727f36c864648d35734351becc48875df96f4abd3cfcf820f9", + "0x83be11727cd30ea94ccc8fa31b09b81c9d6a9a5d3a4686af9da99587332fe78c1f94282f9755854bafd6033549afec91", + "0x88020ff971dc1a01a9e993cd50a5d2131ffdcbb990c1a6aaa54b20d8f23f9546a70918ea57a21530dcc440c1509c24ad", + "0xae24547623465e87905eaffa1fa5d52bb7c453a8dbd89614fa8819a2abcedaf455c2345099b7324ae36eb0ad7c8ef977", + "0xb59b0c60997de1ee00b7c388bc7101d136c9803bf5437b1d589ba57c213f4f835a3e4125b54738e78abbc21b000f2016", + "0xa584c434dfe194546526691b68fa968c831c31da42303a1d735d960901c74011d522246f37f299555416b8cf25c5a548", + "0x80408ce3724f4837d4d52376d255e10f69eb8558399ae5ca6c11b78b98fe67d4b93157d2b9b639f1b5b64198bfe87713", + "0xabb941e8d406c2606e0ddc35c113604fdd9d249eacc51cb64e2991e551b8639ce44d288cc92afa7a1e7fc599cfc84b22", + "0xb223173f560cacb1c21dba0f1713839e348ad02cbfdef0626748604c86f89e0f4c919ed40b583343795bdd519ba952c8", + "0xaf1c70512ec3a19d98b8a1fc3ff7f7f5048a27d17d438d43f561974bbdd116fcd5d5c21040f3447af3f0266848d47a15", + "0x8a44809568ebe50405bede19b4d2607199159b26a1b33e03d180e6840c5cf59d991a4fb150d111443235d75ecad085b7", + "0xb06207cdca46b125a27b3221b5b50cf27af4c527dd7c80e2dbcebbb09778a96df3af67e50f07725239ce3583dad60660", + "0x993352d9278814ec89b26a11c4a7c4941bf8f0e6781ae79559d14749ee5def672259792db4587f85f0100c7bb812f933", + "0x9180b8a718b971fd27bc82c8582d19c4b4f012453e8c0ffeeeffe745581fc6c07875ab28be3af3fa3896d19f0c89ac5b", + "0x8b8e1263eb48d0fe304032dd5ea1f30e73f0121265f7458ba9054d3626894e8a5fef665340abd2ede9653045c2665938", + "0x99a2beee4a10b7941c24b2092192faf52b819afd033e4a2de050fd6c7f56d364d0cf5f99764c3357cf32399e60fc5d74", + "0x946a4aad7f8647ea60bee2c5fcdeb6f9a58fb2cfca70c4d10e458027a04846e13798c66506151be3df9454b1e417893f", + "0xa672a88847652d260b5472d6908d1d57e200f1e492d30dd1cecc441cdfc9b76e016d9bab560efd4d7f3c30801de884a9", + "0x9414e1959c156cde1eb24e628395744db75fc24b9df4595350aaad0bc38e0246c9b4148f6443ef68b8e253a4a6bcf11c", + "0x9316e9e4ec5fab4f80d6540df0e3a4774db52f1d759d2e5b5bcd3d7b53597bb007eb1887cb7dc61f62497d51ffc8d996", + "0x902d6d77bb49492c7a00bc4b70277bc28c8bf9888f4307bb017ac75a962decdedf3a4e2cf6c1ea9f9ba551f4610cbbd7", + "0xb07025a18b0e32dd5e12ec6a85781aa3554329ea12c4cd0d3b2c22e43d777ef6f89876dd90a9c8fb097ddf61cf18adc5", + "0xb355a849ad3227caa4476759137e813505ec523cbc2d4105bc7148a4630f9e81918d110479a2d5f5e4cd9ccec9d9d3e3", + "0xb49532cfdf02ee760109881ad030b89c48ee3bb7f219ccafc13c93aead754d29bdafe345be54c482e9d5672bd4505080", + "0x9477802410e263e4f938d57fa8f2a6cac7754c5d38505b73ee35ea3f057aad958cb9722ba6b7b3cfc4524e9ca93f9cdc", + "0x9148ea83b4436339580f3dbc9ba51509e9ab13c03063587a57e125432dd0915f5d2a8f456a68f8fff57d5f08c8f34d6e", + "0xb00b6b5392b1930b54352c02b1b3b4f6186d20bf21698689bbfc7d13e86538a4397b90e9d5c93fd2054640c4dbe52a4f", + "0x926a9702500441243cd446e7cbf15dde16400259726794694b1d9a40263a9fc9e12f7bcbf12a27cb9aaba9e2d5848ddc", + "0xa0c6155f42686cbe7684a1dc327100962e13bafcf3db97971fc116d9f5c0c8355377e3d70979cdbd58fd3ea52440901c", + "0xa277f899f99edb8791889d0817ea6a96c24a61acfda3ad8c3379e7c62b9d4facc4b965020b588651672fd261a77f1bfc", + "0x8f528cebb866b501f91afa50e995234bef5bf20bff13005de99cb51eaac7b4f0bf38580cfd0470de40f577ead5d9ba0f", + "0x963fc03a44e9d502cc1d23250efef44d299befd03b898d07ce63ca607bb474b5cf7c965a7b9b0f32198b04a8393821f7", + "0xab087438d0a51078c378bf4a93bd48ef933ff0f1fa68d02d4460820df564e6642a663b5e50a5fe509527d55cb510ae04", + "0xb0592e1f2c54746bb076be0fa480e1c4bebc4225e1236bcda3b299aa3853e3afb401233bdbcfc4a007b0523a720fbf62", + "0x851613517966de76c1c55a94dc4595f299398a9808f2d2f0a84330ba657ab1f357701d0895f658c18a44cb00547f6f57", + "0xa2fe9a1dd251e72b0fe4db27be508bb55208f8f1616b13d8be288363ec722826b1a1fd729fc561c3369bf13950bf1fd6", + "0xb896cb2bc2d0c77739853bc59b0f89b2e008ba1f701c9cbe3bef035f499e1baee8f0ff1e794854a48c320586a2dfc81a", + "0xa1b60f98e5e5106785a9b81a85423452ee9ef980fa7fa8464f4366e73f89c50435a0c37b2906052b8e58e212ebd366cf", + "0xa853b0ebd9609656636df2e6acd5d8839c0fda56f7bf9288a943b06f0b67901a32b95e016ca8bc99bd7b5eab31347e72", + "0xb290fa4c1346963bd5225235e6bdf7c542174dab4c908ab483d1745b9b3a6015525e398e1761c90e4b49968d05e30eea", + "0xb0f65a33ad18f154f1351f07879a183ad62e5144ad9f3241c2d06533dad09cbb2253949daff1bb02d24d16a3569f7ef0", + "0xa00db59b8d4218faf5aeafcd39231027324408f208ec1f54d55a1c41228b463b88304d909d16b718cfc784213917b71e", + "0xb8d695dd33dc2c3bc73d98248c535b2770ad7fa31aa726f0aa4b3299efb0295ba9b4a51c71d314a4a1bd5872307534d1", + "0xb848057cca2ca837ee49c42b88422303e58ea7d2fc76535260eb5bd609255e430514e927cc188324faa8e657396d63ec", + "0x92677836061364685c2aaf0313fa32322746074ed5666fd5f142a7e8f87135f45cd10e78a17557a4067a51dfde890371", + "0xa854b22c9056a3a24ab164a53e5c5cf388616c33e67d8ebb4590cb16b2e7d88b54b1393c93760d154208b5ca822dc68f", + "0x86fff174920388bfab841118fb076b2b0cdec3fdb6c3d9a476262f82689fb0ed3f1897f7be9dbf0932bb14d346815c63", + "0x99661cf4c94a74e182752bcc4b98a8c2218a8f2765642025048e12e88ba776f14f7be73a2d79bd21a61def757f47f904", + "0x8a8893144d771dca28760cba0f950a5d634195fd401ec8cf1145146286caffb0b1a6ba0c4c1828d0a5480ce49073c64c", + "0x938a59ae761359ee2688571e7b7d54692848eb5dde57ffc572b473001ea199786886f8c6346a226209484afb61d2e526", + "0x923f68a6aa6616714cf077cf548aeb845bfdd78f2f6851d8148cba9e33a374017f2f3da186c39b82d14785a093313222", + "0xac923a93d7da7013e73ce8b4a2b14b8fd0cc93dc29d5de941a70285bdd19be4740fedfe0c56b046689252a3696e9c5bc", + "0xb49b32c76d4ec1a2c68d4989285a920a805993bc6fcce6dacd3d2ddae73373050a5c44ba8422a3781050682fa0ef6ba2", + "0x8a367941c07c3bdca5712524a1411bad7945c7c48ffc7103b1d4dff2c25751b0624219d1ccde8c3f70c465f954be5445", + "0xb838f029df455efb6c530d0e370bbbf7d87d61a9aea3d2fe5474c5fe0a39cf235ceecf9693c5c6c5820b1ba8f820bd31", + "0xa8983b7c715eaac7f13a001d2abc462dfc1559dab4a6b554119c271aa8fe00ffcf6b6949a1121f324d6d26cb877bcbae", + "0xa2afb24ad95a6f14a6796315fbe0d8d7700d08f0cfaf7a2abe841f5f18d4fecf094406cbd54da7232a159f9c5b6e805e", + "0x87e8e95ad2d62f947b2766ff405a23f7a8afba14e7f718a691d95369c79955cdebe24c54662553c60a3f55e6322c0f6f", + "0x87c2cbcecb754e0cc96128e707e5c5005c9de07ffd899efa3437cadc23362f5a1d3fcdd30a1f5bdc72af3fb594398c2a", + "0x91afd6ee04f0496dc633db88b9370d41c428b04fd991002502da2e9a0ef051bcd7b760e860829a44fbe5539fa65f8525", + "0x8c50e5d1a24515a9dd624fe08b12223a75ca55196f769f24748686315329b337efadca1c63f88bee0ac292dd0a587440", + "0x8a07e8f912a38d94309f317c32068e87f68f51bdfa082d96026f5f5f8a2211621f8a3856dda8069386bf15fb2d28c18f", + "0x94ad1dbe341c44eeaf4dc133eed47d8dbfe752575e836c075745770a6679ff1f0e7883b6aa917462993a7f469d74cab5", + "0x8745f8bd86c2bb30efa7efb7725489f2654f3e1ac4ea95bd7ad0f3cfa223055d06c187a16192d9d7bdaea7b050c6a324", + "0x900d149c8d79418cda5955974c450a70845e02e5a4ecbcc584a3ca64d237df73987c303e3eeb79da1af83bf62d9e579f", + "0x8f652ab565f677fb1a7ba03b08004e3cda06b86c6f1b0b9ab932e0834acf1370abb2914c15b0d08327b5504e5990681c", + "0x9103097d088be1f75ab9d3da879106c2f597e2cc91ec31e73430647bdd5c33bcfd771530d5521e7e14df6acda44f38a6", + "0xb0fec7791cfb0f96e60601e1aeced9a92446b61fedab832539d1d1037558612d78419efa87ff5f6b7aab8fd697d4d9de", + "0xb9d2945bdb188b98958854ba287eb0480ef614199c4235ce5f15fc670b8c5ffe8eeb120c09c53ea8a543a022e6a321ac", + "0xa9461bb7d5490973ebaa51afc0bb4a5e42acdccb80e2f939e88b77ac28a98870e103e1042899750f8667a8cc9123bae9", + "0xa37fdf11d4bcb2aed74b9f460a30aa34afea93386fa4cdb690f0a71bc58f0b8df60bec56e7a24f225978b862626fa00e", + "0xa214420e183e03d531cf91661466ea2187d84b6e814b8b20b3730a9400a7d25cf23181bb85589ebc982cec414f5c2923", + "0xad09a45a698a6beb3e0915f540ef16e9af7087f53328972532d6b5dfe98ce4020555ece65c6cbad8bd6be8a4dfefe6fd", + "0xab6742800b02728c92d806976764cb027413d6f86edd08ad8bb5922a2969ee9836878cd39db70db0bd9a2646862acc4f", + "0x974ca9305bd5ea1dc1755dff3b63e8bfe9f744321046c1395659bcea2a987b528e64d5aa96ac7b015650b2253b37888d", + "0x84eee9d6bce039c52c2ebc4fccc0ad70e20c82f47c558098da4be2f386a493cbc76adc795b5488c8d11b6518c2c4fab8", + "0x875d7bda46efcb63944e1ccf760a20144df3b00d53282b781e95f12bfc8f8316dfe6492c2efbf796f1150e36e436e9df", + "0xb68a2208e0c587b5c31b5f6cb32d3e6058a9642e2d9855da4f85566e1412db528475892060bb932c55b3a80877ad7b4a", + "0xba006368ecab5febb6ab348644d9b63de202293085ed468df8bc24d992ae8ce468470aa37f36a73630c789fb9c819b30", + "0x90a196035150846cd2b482c7b17027471372a8ce7d914c4d82b6ea7fa705d8ed5817bd42d63886242585baf7d1397a1c", + "0xa223b4c85e0daa8434b015fd9170b5561fe676664b67064974a1e9325066ecf88fc81f97ab5011c59fad28cedd04b240", + "0x82e8ec43139cf15c6bbeed484b62e06cded8a39b5ce0389e4cbe9c9e9c02f2f0275d8d8d4e8dfec8f69a191bef220408", + "0x81a3fc07a7b68d92c6ee4b6d28f5653ee9ec85f7e2ee1c51c075c1b130a8c5097dc661cf10c5aff1c7114b1a6a19f11a", + "0x8ed2ef8331546d98819a5dd0e6c9f8cb2630d0847671314a28f277faf68da080b53891dd75c82cbcf7788b255490785d", + "0xacecabf84a6f9bbed6b2fc2e7e4b48f02ef2f15e597538a73aea8f98addc6badda15e4695a67ecdb505c1554e8f345ec", + "0xb8f51019b2aa575f8476e03dcadf86cc8391f007e5f922c2a36b2daa63f5a503646a468990cd5c65148d323942193051", + "0xaaa595a84b403ec65729bc1c8055a94f874bf9adddc6c507b3e1f24f79d3ad359595a672b93aab3394db4e2d4a7d8970", + "0x895144c55fcbd0f64d7dd69e6855cfb956e02b5658eadf0f026a70703f3643037268fdd673b0d21b288578a83c6338dd", + "0xa2e92ae6d0d237d1274259a8f99d4ea4912a299816350b876fba5ebc60b714490e198a916e1c38c6e020a792496fa23c", + "0xa45795fda3b5bb0ad1d3c628f6add5b2a4473a1414c1a232e80e70d1cfffd7f8a8d9861f8df2946999d7dbb56bf60113", + "0xb6659bf7f6f2fef61c39923e8c23b8c70e9c903028d8f62516d16755cd3fba2fe41c285aa9432dc75ab08f8a1d8a81fc", + "0xa735609a6bc5bfd85e58234fc439ff1f58f1ff1dd966c5921d8b649e21f006bf2b8642ad8a75063c159aaf6935789293", + "0xa3c622eb387c9d15e7bda2e3e84d007cb13a6d50d655c3f2f289758e49d3b37b9a35e4535d3cc53d8efd51f407281f19", + "0x8afe147b53ad99220f5ef9d763bfc91f9c20caecbcf823564236fb0e6ede49414c57d71eec4772c8715cc65a81af0047", + "0xb5f0203233cf71913951e9c9c4e10d9243e3e4a1f2cb235bf3f42009120ba96e04aa414c9938ea8873b63148478927e8", + "0x93c52493361b458d196172d7ba982a90a4f79f03aa8008edc322950de3ce6acf4c3977807a2ffa9e924047e02072b229", + "0xb9e72b805c8ac56503f4a86c82720afbd5c73654408a22a2ac0b2e5caccdfb0e20b59807433a6233bc97ae58cf14c70a", + "0xaf0475779b5cee278cca14c82da2a9f9c8ef222eb885e8c50cca2315fea420de6e04146590ed0dd5a29c0e0812964df5", + "0xb430ccab85690db02c2d0eb610f3197884ca12bc5f23c51e282bf3a6aa7e4a79222c3d8761454caf55d6c01a327595f9", + "0x830032937418b26ee6da9b5206f3e24dc76acd98589e37937e963a8333e5430abd6ce3dd93ef4b8997bd41440eed75d6", + "0x8820a6d73180f3fe255199f3f175c5eb770461ad5cfdde2fb11508041ed19b8c4ce66ad6ecebf7d7e836cc2318df47ca", + "0xaef1393e7d97278e77bbf52ef6e1c1d5db721ccf75fe753cf47a881fa034ca61eaa5098ee5a344c156d2b14ff9e284ad", + "0x8a4a26c07218948c1196c45d927ef4d2c42ade5e29fe7a91eaebe34a29900072ce5194cf28d51f746f4c4c649daf4396", + "0x84011dc150b7177abdcb715efbd8c201f9cb39c36e6069af5c50a096021768ba40cef45b659c70915af209f904ede3b6", + "0xb1bd90675411389bb66910b21a4bbb50edce5330850c5ab0b682393950124252766fc81f5ecfc72fb7184387238c402e", + "0x8dfdcd30583b696d2c7744655f79809f451a60c9ad5bf1226dc078b19f4585d7b3ef7fa9d54e1ac09520d95cbfd20928", + "0xb351b4dc6d98f75b8e5a48eb7c6f6e4b78451991c9ba630e5a1b9874c15ac450cd409c1a024713bf2cf82dc400e025ef", + "0xa462b8bc97ac668b97b28b3ae24b9f5de60e098d7b23ecb600d2194cd35827fb79f77c3e50d358f5bd72ee83fef18fa0", + "0xa183753265c5f7890270821880cce5f9b2965b115ba783c6dba9769536f57a04465d7da5049c7cf8b3fcf48146173c18", + "0xa8a771b81ed0d09e0da4d79f990e58eabcd2be3a2680419502dd592783fe52f657fe55125b385c41d0ba3b9b9cf54a83", + "0xa71ec577db46011689d073245e3b1c3222a9b1fe6aa5b83629adec5733dd48617ebea91346f0dd0e6cdaa86e4931b168", + "0xa334b8b244f0d598a02da6ae0f918a7857a54dce928376c4c85df15f3b0f2ba3ac321296b8b7c9dd47d770daf16c8f8c", + "0xa29037f8ef925c417c90c4df4f9fb27fb977d04e2b3dd5e8547d33e92ab72e7a00f5461de21e28835319eae5db145eb7", + "0xb91054108ae78b00e3298d667b913ebc44d8f26e531eae78a8fe26fdfb60271c97efb2dee5f47ef5a3c15c8228138927", + "0x926c13efbe90604f6244be9315a34f72a1f8d1aab7572df431998949c378cddbf2fe393502c930fff614ff06ae98a0ce", + "0x995c758fd5600e6537089b1baa4fbe0376ab274ff3e82a17768b40df6f91c2e443411de9cafa1e65ea88fb8b87d504f4", + "0x9245ba307a7a90847da75fca8d77ec03fdfc812c871e7a2529c56a0a79a6de16084258e7a9ac4ae8a3756f394336e21c", + "0x99e0cfa2bb57a7e624231317044c15e52196ecce020db567c8e8cb960354a0be9862ee0c128c60b44777e65ac315e59f", + "0xad4f6b3d27bbbb744126601053c3dc98c07ff0eb0b38a898bd80dce778372846d67e5ab8fb34fb3ad0ef3f235d77ba7f", + "0xa0f12cae3722bbbca2e539eb9cc7614632a2aefe51410430070a12b5bc5314ecec5857b7ff8f41e9980cac23064f7c56", + "0xb487f1bc59485848c98222fd3bc36c8c9bb3d2912e2911f4ceca32c840a7921477f9b1fe00877e05c96c75d3eecae061", + "0xa6033db53925654e18ecb3ce715715c36165d7035db9397087ac3a0585e587998a53973d011ac6d48af439493029cee6", + "0xa6b4d09cd01c70a3311fd131d3710ccf97bde3e7b80efd5a8c0eaeffeb48cca0f951ced905290267b115b06d46f2693b", + "0xa9dff1df0a8f4f218a98b6f818a693fb0d611fed0fc3143537cbd6578d479af13a653a8155e535548a2a0628ae24fa58", + "0xa58e469f65d366b519f9a394cacb7edaddac214463b7b6d62c2dbc1316e11c6c5184ce45c16de2d77f990dcdd8b55430", + "0x989e71734f8119103586dc9a3c5f5033ddc815a21018b34c1f876cdfc112efa868d5751bf6419323e4e59fa6a03ece1c", + "0xa2da00e05036c884369e04cf55f3de7d659cd5fa3f849092b2519dd263694efe0f051953d9d94b7e121f0aee8b6174d7", + "0x968f3c029f57ee31c4e1adea89a7f92e28483af9a74f30fbdb995dc2d40e8e657dff8f8d340d4a92bf65f54440f2859f", + "0x932778df6f60ac1639c1453ef0cbd2bf67592759dcccb3e96dcc743ff01679e4c7dd0ef2b0833dda548d32cb4eba49e2", + "0xa805a31139f8e0d6dae1ac87d454b23a3dc9fc653d4ca18d4f8ebab30fc189c16e73981c2cb7dd6f8c30454a5208109d", + "0xa9ba0991296caa2aaa4a1ceacfb205544c2a2ec97088eace1d84ee5e2767656a172f75d2f0c4e16a3640a0e0dec316e0", + "0xb1e49055c968dced47ec95ae934cf45023836d180702e20e2df57e0f62fb85d7ac60d657ba3ae13b8560b67210449459", + "0xa94e1da570a38809c71e37571066acabff7bf5632737c9ab6e4a32856924bf6211139ab3cedbf083850ff2d0e0c0fcfc", + "0x88ef1bb322000c5a5515b310c838c9af4c1cdbb32eab1c83ac3b2283191cd40e9573747d663763a28dad0d64adc13840", + "0xa987ce205f923100df0fbd5a85f22c9b99b9b9cbe6ddfa8dfda1b8fe95b4f71ff01d6c5b64ca02eb24edb2b255a14ef0", + "0x84fe8221a9e95d9178359918a108de4763ebfa7a6487facb9c963406882a08a9a93f492f8e77cf9e7ea41ae079c45993", + "0xaa1cf3dc7c5dcfa15bbbc811a4bb6dbac4fba4f97fb1ed344ab60264d7051f6eef19ea9773441d89929ee942ed089319", + "0x8f6a7d610d59d9f54689bbe6a41f92d9f6096cde919c1ab94c3c7fcecf0851423bc191e5612349e10f855121c0570f56", + "0xb5af1fa7894428a53ea520f260f3dc3726da245026b6d5d240625380bfb9c7c186df0204bb604efac5e613a70af5106e", + "0xa5bce6055ff812e72ce105f147147c7d48d7a2313884dd1f488b1240ee320f13e8a33f5441953a8e7a3209f65b673ce1", + "0xb9b55b4a1422677d95821e1d042ab81bbf0bf087496504021ec2e17e238c2ca6b44fb3b635a5c9eac0871a724b8d47c3", + "0x941c38e533ce4a673a3830845b56786585e5fe49c427f2e5c279fc6db08530c8f91db3e6c7822ec6bb4f956940052d18", + "0xa38e191d66c625f975313c7007bbe7431b5a06ed2da1290a7d5d0f2ec73770d476efd07b8e632de64597d47df175cbb0", + "0x94ba76b667abf055621db4c4145d18743a368d951565632ed4e743dd50dd3333507c0c34f286a5c5fdbf38191a2255cd", + "0xa5ca38c60be5602f2bfa6e00c687ac96ac36d517145018ddbee6f12eb0faa63dd57909b9eeed26085fe5ac44e55d10ab", + "0xb00fea3b825e60c1ed1c5deb4b551aa65a340e5af36b17d5262c9cd2c508711e4dc50dc2521a2c16c7c901902266e64a", + "0x971b86fc4033485e235ccb0997a236206ba25c6859075edbcdf3c943116a5030b7f75ebca9753d863a522ba21a215a90", + "0xb3b31f52370de246ee215400975b674f6da39b2f32514fe6bd54e747752eedca22bb840493b44a67df42a3639c5f901f", + "0xaffbbfac9c1ba7cbfa1839d2ae271dd6149869b75790bf103230637da41857fc326ef3552ff31c15bda0694080198143", + "0xa95d42aa7ef1962520845aa3688f2752d291926f7b0d73ea2ee24f0612c03b43f2b0fe3c9a9a99620ffc8d487b981bc2", + "0x914a266065caf64985e8c5b1cb2e3f4e3fe94d7d085a1881b1fefa435afef4e1b39a98551d096a62e4f5cc1a7f0fdc2e", + "0x81a0b4a96e2b75bc1bf2dbd165d58d55cfd259000a35504d1ffb18bc346a3e6f07602c683723864ffb980f840836fd8d", + "0x91c1556631cddd4c00b65b67962b39e4a33429029d311c8acf73a18600e362304fb68bccb56fde40f49e95b7829e0b87", + "0x8befbacc19e57f7c885d1b7a6028359eb3d80792fe13b92a8400df21ce48deb0bb60f2ddb50e3d74f39f85d7eab23adc", + "0x92f9458d674df6e990789690ec9ca73dacb67fc9255b58c417c555a8cc1208ace56e8e538f86ba0f3615573a0fbac00d", + "0xb4b1b3062512d6ae7417850c08c13f707d5838e43d48eb98dd4621baf62eee9e82348f80fe9b888a12874bfa538771f8", + "0xa13c4a3ac642ede37d9c883f5319e748d2b938f708c9d779714108a449b343f7b71a6e3ef4080fee125b416762920273", + "0xaf44983d5fc8cceee0551ef934e6e653f2d3efa385e5c8a27a272463a6f333e290378cc307c2b664eb923c78994e706e", + "0xa389fd6c59fe2b4031cc244e22d3991e541bd203dd5b5e73a6159e72df1ab41d49994961500dcde7989e945213184778", + "0x8d2141e4a17836c548de9598d7b298b03f0e6c73b7364979a411c464e0628e21cff6ac3d6decdba5d1c4909eff479761", + "0x980b22ef53b7bdf188a3f14bc51b0dbfdf9c758826daa3cbc1e3986022406a8aa9a6a79e400567120b88c67faa35ce5f", + "0xa28882f0a055f96df3711de5d0aa69473e71245f4f3e9aa944e9d1fb166e02caa50832e46da6d3a03b4801735fd01b29", + "0x8db106a37d7b88f5d995c126abb563934dd8de516af48e85695d02b1aea07f79217e3cdd03c6f5ca57421830186c772b", + "0xb5a7e50da0559a675c472f7dfaee456caab6695ab7870541b2be8c2b118c63752427184aad81f0e1afc61aef1f28c46f", + "0x9962118780e20fe291d10b64f28d09442a8e1b5cffd0f3dd68d980d0614050a626c616b44e9807fbee7accecae00686a", + "0xb38ddf33745e8d2ad6a991aefaf656a33c5f8cbe5d5b6b6fd03bd962153d8fd0e01b5f8f96d80ae53ab28d593ab1d4e7", + "0x857dc12c0544ff2c0c703761d901aba636415dee45618aba2e3454ff9cbc634a85c8b05565e88520ff9be2d097c8b2b1", + "0xa80d465c3f8cc63af6d74a6a5086b626c1cb4a8c0fee425964c3bd203d9d7094e299f81ce96d58afc20c8c9a029d9dae", + "0x89e1c8fbde8563763be483123a3ed702efac189c6d8ab4d16c85e74bbaf856048cc42d5d6e138633a38572ba5ec3f594", + "0x893a594cf495535f6d216508f8d03c317dcf03446668cba688da90f52d0111ac83d76ad09bf5ea47056846585ee5c791", + "0xaadbd8be0ae452f7f9450c7d2957598a20cbf10139a4023a78b4438172d62b18b0de39754dd2f8862dbd50a3a0815e53", + "0xae7d39670ecca3eb6db2095da2517a581b0e8853bdfef619b1fad9aacd443e7e6a40f18209fadd44038a55085c5fe8b2", + "0x866ef241520eacb6331593cfcb206f7409d2f33d04542e6e52cba5447934e02d44c471f6c9a45963f9307e9809ab91d9", + "0xb1a09911ad3864678f7be79a9c3c3eb5c84a0a45f8dcb52c67148f43439aeaaa9fd3ed3471276b7e588b49d6ebe3033a", + "0xadd07b7f0dbb34049cd8feeb3c18da5944bf706871cfd9f14ff72f6c59ad217ebb1f0258b13b167851929387e4e34cfe", + "0xae048892d5c328eefbdd4fba67d95901e3c14d974bfc0a1fc68155ca9f0d59e61d7ba17c6c9948b120cf35fd26e6fee9", + "0x9185b4f3b7da0ddb4e0d0f09b8a9e0d6943a4611e43f13c3e2a767ed8592d31e0ba3ebe1914026a3627680274291f6e5", + "0xa9c022d4e37b0802284ce3b7ee9258628ab4044f0db4de53d1c3efba9de19d15d65cc5e608dbe149c21c2af47d0b07b5", + "0xb24dbd5852f8f24921a4e27013b6c3fa8885b973266cb839b9c388efad95821d5d746348179dcc07542bd0d0aefad1ce", + "0xb5fb4f279300876a539a27a441348764908bc0051ebd66dc51739807305e73db3d2f6f0f294ffb91b508ab150eaf8527", + "0xace50841e718265b290c3483ed4b0fdd1175338c5f1f7530ae9a0e75d5f80216f4de37536adcbc8d8c95982e88808cd0", + "0xb19cadcde0f63bd1a9c24bd9c2806f53c14c0b9735bf351601498408ba503ddbd2037c891041cbba47f58b8c483f3b21", + "0xb6061e63558d312eb891b97b39aa552fa218568d79ee26fe6dd5b864aea9e3216d8f2e2f3b093503be274766dac41426", + "0x89730fdb2876ab6f0fe780d695f6e12090259027e789b819956d786e977518057e5d1d7f5ab24a3ae3d5d4c97773bd2b", + "0xb6fa841e81f9f2cad0163a02a63ae96dc341f7ae803b616efc6e1da2fbea551c1b96b11ad02c4afbdf6d0cc9f23da172", + "0x8fb66187182629c861ddb6896d7ed3caf2ad050c3dba8ab8eb0d7a2c924c3d44c48d1a148f9e33fb1f061b86972f8d21", + "0x86022ac339c1f84a7fa9e05358c1a5b316b4fc0b83dbe9c8c7225dc514f709d66490b539359b084ce776e301024345fa", + "0xb50b9c321468da950f01480bb62b6edafd42f83c0001d6e97f2bd523a1c49a0e8574fb66380ea28d23a7c4d54784f9f0", + "0xa31c05f7032f30d1dac06678be64d0250a071fd655e557400e4a7f4c152be4d5c7aa32529baf3e5be7c4bd49820054f6", + "0xb95ac0848cd322684772119f5b682d90a66bbf9dac411d9d86d2c34844bbd944dbaf8e47aa41380455abd51687931a78", + "0xae4a6a5ce9553b65a05f7935e61e496a4a0f6fd8203367a2c627394c9ce1e280750297b74cdc48fd1d9a31e93f97bef4", + "0xa22daf35f6e9b05e52e0b07f7bd1dbbebd2c263033fb0e1b2c804e2d964e2f11bc0ece6aca6af079dd3a9939c9c80674", + "0x902150e0cb1f16b9b59690db35281e28998ce275acb313900da8b2d8dfd29fa1795f8ca3ff820c31d0697de29df347c1", + "0xb17b5104a5dc665cdd7d47e476153d715eb78c6e5199303e4b5445c21a7fa7cf85fe7cfd08d7570f4e84e579b005428c", + "0xa03f49b81c15433f121680aa02d734bb9e363af2156654a62bcb5b2ba2218398ccb0ff61104ea5d7df5b16ea18623b1e", + "0x802101abd5d3c88876e75a27ffc2f9ddcce75e6b24f23dba03e5201281a7bd5cc7530b6a003be92d225093ca17d3c3bb", + "0xa4d183f63c1b4521a6b52226fc19106158fc8ea402461a5cccdaa35fee93669df6a8661f45c1750cd01308149b7bf08e", + "0x8d17c22e0c8403b69736364d460b3014775c591032604413d20a5096a94d4030d7c50b9fe3240e31d0311efcf9816a47", + "0x947225acfcce5992eab96276f668c3cbe5f298b90a59f2bb213be9997d8850919e8f496f182689b5cbd54084a7332482", + "0x8df6f4ed216fc8d1905e06163ba1c90d336ab991a18564b0169623eb39b84e627fa267397da15d3ed754d1f3423bff07", + "0x83480007a88f1a36dea464c32b849a3a999316044f12281e2e1c25f07d495f9b1710b4ba0d88e9560e72433addd50bc2", + "0xb3019d6e591cf5b33eb972e49e06c6d0a82a73a75d78d383dd6f6a4269838289e6e07c245f54fed67f5c9bb0fd5e1c5f", + "0x92e8ce05e94927a9fb02debadb99cf30a26172b2705003a2c0c47b3d8002bf1060edb0f6a5750aad827c98a656b19199", + "0xac2aff801448dbbfc13cca7d603fd9c69e82100d997faf11f465323b97255504f10c0c77401e4d1890339d8b224f5803", + "0xb0453d9903d08f508ee27e577445dc098baed6cde0ac984b42e0f0efed62760bd58d5816cf1e109d204607b7b175e30c", + "0xae68dc4ba5067e825d46d2c7c67f1009ceb49d68e8d3e4c57f4bcd299eb2de3575d42ea45e8722f8f28497a6e14a1cfe", + "0xb22486c2f5b51d72335ce819bbafb7fa25eb1c28a378a658f13f9fc79cd20083a7e573248d911231b45a5cf23b561ca7", + "0x89d1201d1dbd6921867341471488b4d2fd0fc773ae1d4d074c78ae2eb779a59b64c00452c2a0255826fca6b3d03be2b1", + "0xa2998977c91c7a53dc6104f5bc0a5b675e5350f835e2f0af69825db8af4aeb68435bdbcc795f3dd1f55e1dd50bc0507f", + "0xb0be4937a925b3c05056ed621910d535ccabf5ab99fd3b9335080b0e51d9607d0fd36cb5781ff340018f6acfca4a9736", + "0xaea145a0f6e0ba9df8e52e84bb9c9de2c2dc822f70d2724029b153eb68ee9c17de7d35063dcd6a39c37c59fdd12138f7", + "0x91cb4545d7165ee8ffbc74c874baceca11fdebbc7387908d1a25877ca3c57f2c5def424dab24148826832f1e880bede0", + "0xb3b579cb77573f19c571ad5eeeb21f65548d7dff9d298b8d7418c11f3e8cd3727c5b467f013cb87d6861cfaceee0d2e3", + "0xb98a1eeec2b19fecc8378c876d73645aa52fb99e4819903735b2c7a885b242787a30d1269a04bfb8573d72d9bbc5f0f0", + "0x940c1f01ed362bd588b950c27f8cc1d52276c71bb153d47f07ec85b038c11d9a8424b7904f424423e714454d5e80d1cd", + "0xaa343a8ecf09ce11599b8cf22f7279cf80f06dbf9f6d62cb05308dbbb39c46fd0a4a1240b032665fbb488a767379b91b", + "0x87c3ac72084aca5974599d3232e11d416348719e08443acaba2b328923af945031f86432e170dcdd103774ec92e988c9", + "0x91d6486eb5e61d2b9a9e742c20ec974a47627c6096b3da56209c2b4e4757f007e793ebb63b2b246857c9839b64dc0233", + "0xaebcd3257d295747dd6fc4ff910d839dd80c51c173ae59b8b2ec937747c2072fa85e3017f9060aa509af88dfc7529481", + "0xb3075ba6668ca04eff19efbfa3356b92f0ab12632dcda99cf8c655f35b7928c304218e0f9799d68ef9f809a1492ff7db", + "0x93ba7468bb325639ec2abd4d55179c69fd04eaaf39fc5340709227bbaa4ad0a54ea8b480a1a3c8d44684e3be0f8d1980", + "0xa6aef86c8c0d92839f38544d91b767c582568b391071228ff5a5a6b859c87bf4f81a7d926094a4ada1993ddbd677a920", + "0x91dcd6d14207aa569194aa224d1e5037b999b69ade52843315ca61ba26abe9a76412c9e88259bc5cf5d7b95b97d9c3bc", + "0xb3b483d31c88f78d49bd065893bc1e3d2aa637e27dedb46d9a7d60be7660ce7a10aaaa7deead362284a52e6d14021178", + "0x8e5730070acf8371461ef301cc4523e8e672aa0e3d945d438a0e0aa6bdf8cb9c685dcf38df429037b0c8aff3955c6f5b", + "0xb8c6d769890a8ee18dc4f9e917993315877c97549549b34785a92543cbeec96a08ae3a28d6e809c4aacd69de356c0012", + "0x95ca86cd384eaceaa7c077c5615736ca31f36824bd6451a16142a1edc129fa42b50724aeed7c738f08d7b157f78b569e", + "0x94df609c6d71e8eee7ab74226e371ccc77e01738fe0ef1a6424435b4570fe1e5d15797b66ed0f64eb88d4a3a37631f0e", + "0x89057b9783212add6a0690d6bb99097b182738deff2bd9e147d7fd7d6c8eacb4c219923633e6309ad993c24572289901", + "0x83a0f9f5f265c5a0e54defa87128240235e24498f20965009fef664f505a360b6fb4020f2742565dfc7746eb185bcec0", + "0x91170da5306128931349bc3ed50d7df0e48a68b8cc8420975170723ac79d8773e4fa13c5f14dc6e3fafcad78379050b1", + "0xb7178484d1b55f7e56a4cc250b6b2ec6040437d96bdfddfa7b35ed27435860f3855c2eb86c636f2911b012eb83b00db8", + "0xac0b00c4322d1e4208e09cd977b4e54d221133ff09551f75b32b0b55d0e2be80941dda26257b0e288c162e63c7e9cf68", + "0x9690ed9e7e53ed37ff362930e4096b878b12234c332fd19d5d064824084245952eda9f979e0098110d6963e468cf513e", + "0xb6fa547bb0bb83e5c5be0ed462a8783fba119041c136a250045c09d0d2af330c604331e7de960df976ff76d67f8000cd", + "0x814603907c21463bcf4e59cfb43066dfe1a50344ae04ef03c87c0f61b30836c3f4dea0851d6fa358c620045b7f9214c8", + "0x9495639e3939fad2a3df00a88603a5a180f3c3a0fe4d424c35060e2043e0921788003689887b1ed5be424d9a89bb18bb", + "0xaba4c02d8d57f2c92d5bc765885849e9ff8393d6554f5e5f3e907e5bfac041193a0d8716d7861104a4295d5a03c36b03", + "0x8ead0b56c1ca49723f94a998ba113b9058059321da72d9e395a667e6a63d5a9dac0f5717cec343f021695e8ced1f72af", + "0xb43037f7e3852c34ed918c5854cd74e9d5799eeddfe457d4f93bb494801a064735e326a76e1f5e50a339844a2f4a8ec9", + "0x99db8422bb7302199eb0ff3c3d08821f8c32f53a600c5b6fb43e41205d96adae72be5b460773d1280ad1acb806af9be8", + "0x8a9be08eae0086c0f020838925984df345c5512ff32e37120b644512b1d9d4fecf0fd30639ca90fc6cf334a86770d536", + "0x81b43614f1c28aa3713a309a88a782fb2bdfc4261dd52ddc204687791a40cf5fd6a263a8179388596582cccf0162efc2", + "0xa9f3a8b76912deb61d966c75daf5ddb868702ebec91bd4033471c8e533183df548742a81a2671de5be63a502d827437d", + "0x902e2415077f063e638207dc7e14109652e42ab47caccd6204e2870115791c9defac5425fd360b37ac0f7bd8fe7011f8", + "0xaa18e4fdc1381b59c18503ae6f6f2d6943445bd00dd7d4a2ad7e5adad7027f2263832690be30d456e6d772ad76f22350", + "0xa348b40ba3ba7d81c5d4631f038186ebd5e5f314f1ea737259151b07c3cc8cf0c6ed4201e71bcc1c22fefda81a20cde6", + "0xaa1306f7ac1acbfc47dc6f7a0cb6d03786cec8c8dc8060388ccda777bca24bdc634d03e53512c23dba79709ff64f8620", + "0x818ccfe46e700567b7f3eb400e5a35f6a5e39b3db3aa8bc07f58ace35d9ae5a242faf8dbccd08d9a9175bbce15612155", + "0xb7e3da2282b65dc8333592bb345a473f03bd6df69170055fec60222de9897184536bf22b9388b08160321144d0940279", + "0xa4d976be0f0568f4e57de1460a1729129252b44c552a69fceec44e5b97c96c711763360d11f9e5bf6d86b4976bf40d69", + "0x85d185f0397c24c2b875b09b6328a23b87982b84ee880f2677a22ff4c9a1ba9f0fea000bb3f7f66375a00d98ebafce17", + "0xb4ccbb8c3a2606bd9b87ce022704663af71d418351575f3b350d294f4efc68c26f9a2ce49ff81e6ff29c3b63d746294e", + "0x93ffd3265fddb63724dfde261d1f9e22f15ecf39df28e4d89e9fea03221e8e88b5dd9b77628bacaa783c6f91802d47cc", + "0xb1fd0f8d7a01378e693da98d03a2d2fda6b099d03454b6f2b1fa6472ff6bb092751ce6290059826b74ac0361eab00e1e", + "0xa89f440c71c561641589796994dd2769616b9088766e983c873fae0716b95c386c8483ab8a4f367b6a68b72b7456dd32", + "0xaf4fe92b01d42d03dd5d1e7fa55e96d4bbcb7bf7d4c8c197acd16b3e0f3455807199f683dcd263d74547ef9c244b35cc", + "0xa8227f6e0a344dfe76bfbe7a1861be32c4f4bed587ccce09f9ce2cf481b2dda8ae4f566154bc663d15f962f2d41761bd", + "0xa7b361663f7495939ed7f518ba45ea9ff576c4e628995b7aea026480c17a71d63fc2c922319f0502eb7ef8f14a406882", + "0x8ddcf382a9f39f75777160967c07012cfa89e67b19714a7191f0c68eaf263935e5504e1104aaabd0899348c972a8d3c6", + "0x98c95b9f6f5c91f805fb185eedd06c6fc4457d37dd248d0be45a6a168a70031715165ea20606245cbdf8815dc0ac697f", + "0x805b44f96e001e5909834f70c09be3efcd3b43632bcac5b6b66b6d227a03a758e4b1768ce2a723045681a1d34562aaeb", + "0xb0e81b07cdc45b3dca60882676d9badb99f25c461b7efe56e3043b80100bb62d29e1873ae25eb83087273160ece72a55", + "0xb0c53f0abe78ee86c7b78c82ae1f7c070bb0b9c45c563a8b3baa2c515d482d7507bb80771e60b38ac13f78b8af92b4a9", + "0xa7838ef6696a9e4d2e5dfd581f6c8d6a700467e8fd4e85adabb5f7a56f514785dd4ab64f6f1b48366f7d94728359441b", + "0x88c76f7700a1d23c30366a1d8612a796da57b2500f97f88fdf2d76b045a9d24e7426a8ffa2f4e86d3046937a841dad58", + "0xad8964baf98c1f02e088d1d9fcb3af6b1dfa44cdfe0ed2eae684e7187c33d3a3c28c38e8f4e015f9c04d451ed6f85ff6", + "0x90e9d00a098317ececaa9574da91fc149eda5b772dedb3e5a39636da6603aa007804fa86358550cfeff9be5a2cb7845e", + "0xa56ff4ddd73d9a6f5ab23bb77efa25977917df63571b269f6a999e1ad6681a88387fcc4ca3b26d57badf91b236503a29", + "0x97ad839a6302c410a47e245df84c01fb9c4dfef86751af3f9340e86ff8fc3cd52fa5ff0b9a0bd1d9f453e02ca80658a6", + "0xa4c8c44cbffa804129e123474854645107d1f0f463c45c30fd168848ebea94880f7c0c5a45183e9eb837f346270bdb35", + "0xa72e53d0a1586d736e86427a93569f52edd2f42b01e78aee7e1961c2b63522423877ae3ac1227a2cf1e69f8e1ff15bc3", + "0x8559f88a7ef13b4f09ac82ae458bbae6ab25671cfbf52dae7eac7280d6565dd3f0c3286aec1a56a8a16dc3b61d78ce47", + "0x8221503f4cdbed550876c5dc118a3f2f17800c04e8be000266633c83777b039a432d576f3a36c8a01e8fd18289ebc10b", + "0x99bfbe5f3e46d4d898a578ba86ed26de7ed23914bd3bcdf3c791c0bcd49398a52419077354a5ab75cea63b6c871c6e96", + "0xaa134416d8ff46f2acd866c1074af67566cfcf4e8be8d97329dfa0f603e1ff208488831ce5948ac8d75bfcba058ddcaa", + "0xb02609d65ebfe1fe8e52f21224a022ea4b5ea8c1bd6e7b9792eed8975fc387cdf9e3b419b8dd5bcce80703ab3a12a45f", + "0xa4f14798508698fa3852e5cac42a9db9797ecee7672a54988aa74037d334819aa7b2ac7b14efea6b81c509134a6b7ad2", + "0x884f01afecbcb987cb3e7c489c43155c416ed41340f61ecb651d8cba884fb9274f6d9e7e4a46dd220253ae561614e44c", + "0xa05523c9e71dce1fe5307cc71bd721feb3e1a0f57a7d17c7d1c9fb080d44527b7dbaa1f817b1af1c0b4322e37bc4bb1e", + "0x8560aec176a4242b39f39433dd5a02d554248c9e49d3179530815f5031fee78ba9c71a35ceeb2b9d1f04c3617c13d8f0", + "0x996aefd402748d8472477cae76d5a2b92e3f092fc834d5222ae50194dd884c9fb8b6ed8e5ccf8f6ed483ddbb4e80c747", + "0x8fd09900320000cbabc40e16893e2fcf08815d288ec19345ad7b6bb22f7d78a52b6575a3ca1ca2f8bc252d2eafc928ec", + "0x939e51f73022bc5dc6862a0adf8fb8a3246b7bfb9943cbb4b27c73743926cc20f615a036c7e5b90c80840e7f1bfee0e7", + "0xa0a6258700cadbb9e241f50766573bf9bdb7ad380b1079dc3afb4054363d838e177b869cad000314186936e40359b1f2", + "0x972699a4131c8ed27a2d0e2104d54a65a7ff1c450ad9da3a325c662ab26869c21b0a84d0700b98c8b5f6ce3b746873d7", + "0xa454c7fe870cb8aa6491eafbfb5f7872d6e696033f92e4991d057b59d70671f2acdabef533e229878b60c7fff8f748b1", + "0xa167969477214201f09c79027b10221e4707662e0c0fde81a0f628249f2f8a859ce3d30a7dcc03b8ecca8f7828ad85c7", + "0x8ff6b7265175beb8a63e1dbf18c9153fb2578c207c781282374f51b40d57a84fd2ef2ea2b9c6df4a54646788a62fd17f", + "0xa3d7ebeccde69d73d8b3e76af0da1a30884bb59729503ff0fb0c3bccf9221651b974a6e72ea33b7956fc3ae758226495", + "0xb71ef144c9a98ce5935620cb86c1590bd4f48e5a2815d25c0cdb008fde628cf628c31450d3d4f67abbfeb16178a74cfd", + "0xb5e0a16d115134f4e2503990e3f2035ed66b9ccf767063fe6747870d97d73b10bc76ed668550cb82eedc9a2ca6f75524", + "0xb30ffaaf94ee8cbc42aa2c413175b68afdb207dbf351fb20be3852cb7961b635c22838da97eaf43b103aff37e9e725cc", + "0x98aa7d52284f6c1f22e272fbddd8c8698cf8f5fbb702d5de96452141fafb559622815981e50b87a72c2b1190f59a7deb", + "0x81fbacda3905cfaf7780bb4850730c44166ed26a7c8d07197a5d4dcd969c09e94a0461638431476c16397dd7bdc449f9", + "0x95e47021c1726eac2e5853f570d6225332c6e48e04c9738690d53e07c6b979283ebae31e2af1fc9c9b3e59f87e5195b1", + "0xac024a661ba568426bb8fce21780406537f518075c066276197300841e811860696f7588188bc01d90bace7bc73d56e3", + "0xa4ebcaf668a888dd404988ab978594dee193dad2d0aec5cdc0ccaf4ec9a7a8228aa663db1da8ddc52ec8472178e40c32", + "0xa20421b8eaf2199d93b083f2aff37fb662670bd18689d046ae976d1db1fedd2c2ff897985ecc6277b396db7da68bcb27", + "0x8bc33d4b40197fd4d49d1de47489d10b90d9b346828f53a82256f3e9212b0cbc6930b895e879da9cec9fedf026aadb3e", + "0xaaafdd1bec8b757f55a0433eddc0a39f818591954fd4e982003437fcceb317423ad7ee74dbf17a2960380e7067a6b4e2", + "0xaad34277ebaed81a6ec154d16736866f95832803af28aa5625bf0461a71d02b1faba02d9d9e002be51c8356425a56867", + "0x976e9c8b150d08706079945bd0e84ab09a648ecc6f64ded9eb5329e57213149ae409ae93e8fbd8eda5b5c69f5212b883", + "0x8097fae1653247d2aed4111533bc378171d6b2c6d09cbc7baa9b52f188d150d645941f46d19f7f5e27b7f073c1ebd079", + "0x83905f93b250d3184eaba8ea7d727c4464b6bdb027e5cbe4f597d8b9dc741dcbea709630bd4fd59ce24023bec32fc0f3", + "0x8095030b7045cff28f34271386e4752f9a9a0312f8df75de4f424366d78534be2b8e1720a19cb1f9a2d21105d790a225", + "0xa7b7b73a6ae2ed1009c49960374b0790f93c74ee03b917642f33420498c188a169724945a975e5adec0a1e83e07fb1b2", + "0x856a41c54df393b6660b7f6354572a4e71c8bfca9cabaffb3d4ef2632c015e7ee2bc10056f3eccb3dbed1ad17d939178", + "0xa8f7a55cf04b38cd4e330394ee6589da3a07dc9673f74804fdf67b364e0b233f14aec42e783200a2e4666f7c5ff62490", + "0x82c529f4e543c6bca60016dc93232c115b359eaee2798a9cf669a654b800aafe6ab4ba58ea8b9cdda2b371c8d62fa845", + "0x8caab020c1baddce77a6794113ef1dfeafc5f5000f48e97f4351b588bf02f1f208101745463c480d37f588d5887e6d8c", + "0x8fa91b3cc400f48b77b6fd77f3b3fbfb3f10cdff408e1fd22d38f77e087b7683adad258804409ba099f1235b4b4d6fea", + "0x8aa02787663d6be9a35677d9d8188b725d5fcd770e61b11b64e3def8808ea5c71c0a9afd7f6630c48634546088fcd8e2", + "0xb5635b7b972e195cab878b97dea62237c7f77eb57298538582a330b1082f6207a359f2923864630136d8b1f27c41b9aa", + "0x8257bb14583551a65975946980c714ecd6e5b629672bb950b9caacd886fbd22704bc9e3ba7d30778adab65dc74f0203a", + "0xab5fe1cd12634bfa4e5c60d946e2005cbd38f1063ec9a5668994a2463c02449a0a185ef331bd86b68b6e23a8780cb3ba", + "0xa7d3487da56cda93570cc70215d438204f6a2709bfb5fda6c5df1e77e2efc80f4235c787e57fbf2c74aaff8cbb510a14", + "0xb61cff7b4c49d010e133319fb828eb900f8a7e55114fc86b39c261a339c74f630e1a7d7e1350244ada566a0ff3d46c4b", + "0x8d4d1d55d321d278db7a85522ccceca09510374ca81d4d73e3bb5249ace7674b73900c35a531ec4fa6448fabf7ad00dc", + "0x966492248aee24f0f56c8cfca3c8ec6ba3b19abb69ae642041d4c3be8523d22c65c4dafcab4c58989ccc4e0bd2f77919", + "0xb20c320a90cb220b86e1af651cdc1e21315cd215da69f6787e28157172f93fc8285dcd59b039c626ed8ca4633cba1a47", + "0xaae9e6b22f018ceb5c0950210bb8182cb8cb61014b7e14581a09d36ebd1bbfebdb2b82afb7fdb0cf75e58a293d9c456d", + "0x875547fb67951ad37b02466b79f0c9b985ccbc500cfb431b17823457dc79fb9597ec42cd9f198e15523fcd88652e63a4", + "0x92afce49773cb2e20fb21e4f86f18e0959ebb9c33361547ddb30454ee8e36b1e234019cbdca0e964cb292f7f77df6b90", + "0x8af85343dfe1821464c76ba11c216cbef697b5afc69c4d821342e55afdac047081ec2e3f7b09fc14b518d9a23b78c003", + "0xb7de4a1648fd63f3a918096ea669502af5357438e69dac77cb8102b6e6c15c76e033cfaa80dafc806e535ede5c1a20aa", + "0xac80e9b545e8bd762951d96c9ce87f629d01ffcde07efc2ef7879ca011f1d0d8a745abf26c9d452541008871304fac00", + "0xa4cf0f7ed724e481368016c38ea5816698a5f68eb21af4d3c422d2ba55f96a33e427c2aa40de1b56a7cfac7f7cf43ab0", + "0x899b0a678bb2db2cae1b44e75a661284844ebcdd87abf308fedeb2e4dbe5c5920c07db4db7284a7af806a2382e8b111a", + "0xaf0588a2a4afce2b1b13c1230816f59e8264177e774e4a341b289a101dcf6af813638fed14fb4d09cb45f35d5d032609", + "0xa4b8df79e2be76e9f5fc5845f06fe745a724cf37c82fcdb72719b77bdebea3c0e763f37909373e3a94480cc5e875cba0", + "0x83e42c46d88930c8f386b19fd999288f142d325e2ebc86a74907d6d77112cb0d449bc511c95422cc810574031a8cbba9", + "0xb5e39534070de1e5f6e27efbdd3dc917d966c2a9b8cf2d893f964256e95e954330f2442027dc148c776d63a95bcde955", + "0x958607569dc28c075e658cd4ae3927055c6bc456eef6212a6fea8205e48ed8777a8064f584cda38fe5639c371e2e7fba", + "0x812adf409fa63575113662966f5078a903212ffb65c9b0bbe62da0f13a133443a7062cb8fd70f5e5dd5559a32c26d2c8", + "0xa679f673e5ce6a3cce7fa31f22ee3785e96bcb55e5a776e2dd3467bef7440e3555d1a9b87cb215e86ee9ed13a090344b", + "0xafedbb34508b159eb25eb2248d7fe328f86ef8c7d84c62d5b5607d74aae27cc2cc45ee148eb22153b09898a835c58df4", + "0xb75505d4f6b67d31e665cfaf5e4acdb5838ae069166b7fbcd48937c0608a59e40a25302fcc1873d2e81c1782808c70f0", + "0xb62515d539ec21a155d94fc00ea3c6b7e5f6636937bce18ed5b618c12257fb82571886287fd5d1da495296c663ebc512", + "0xab8e1a9446bbdd588d1690243b1549d230e6149c28f59662b66a8391a138d37ab594df38e7720fae53217e5c3573b5be", + "0xb31e8abf4212e03c3287bb2c0a153065a7290a16764a0bac8f112a72e632185a654bb4e88fdd6053e6c7515d9719fadb", + "0xb55165477fe15b6abd2d0f4fddaa9c411710dcc4dd712daba3d30e303c9a3ee5415c256f9dc917ecf18c725b4dbab059", + "0xa0939d4f57cacaae549b78e87cc234de4ff6a35dc0d9cd5d7410abc30ebcd34c135e008651c756e5a9d2ca79c40ef42b", + "0x8cf10e50769f3443340844aad4d56ec790850fed5a41fcbd739abac4c3015f0a085a038fbe7fae9f5ad899cce5069f6b", + "0x924055e804d82a99ea4bb160041ea4dc14b568abf379010bc1922fde5d664718c31d103b8b807e3a1ae809390e708c73", + "0x8ec0f9d26f71b0f2e60a179e4fd1778452e2ffb129d50815e5d7c7cb9415fa69ae5890578086e8ef6bfde35ad2a74661", + "0x98c7f12b15ec4426b59f737f73bf5faea4572340f4550b7590dfb7f7ffedb2372e3e555977c63946d579544c53210ad0", + "0x8a935f7a955c78f69d66f18eee0092e5e833fa621781c9581058e219af4d7ceee48b84e472e159dda6199715fb2f9acf", + "0xb78d4219f95a2dbfaa7d0c8a610c57c358754f4f43c2af312ab0fe8f10a5f0177e475332fb8fd23604e474fc2abeb051", + "0x8d086a14803392b7318c28f1039a17e3cfdcece8abcaca3657ec3d0ac330842098a85c0212f889fabb296dfb133ce9aa", + "0xa53249f417aac82f2c2a50c244ce21d3e08a5e5a8bd33bec2a5ab0d6cd17793e34a17edfa3690899244ce201e2fb9986", + "0x8619b0264f9182867a1425be514dc4f1ababc1093138a728a28bd7e4ecc99b9faaff68c23792264bc6e4dce5f52a5c52", + "0x8c171edbbbde551ec19e31b2091eb6956107dd9b1f853e1df23bff3c10a3469ac77a58335eee2b79112502e8e163f3de", + "0xa9d19ec40f0ca07c238e9337c6d6a319190bdba2db76fb63902f3fb459aeeb50a1ac30db5b25ee1b4201f3ca7164a7f4", + "0xb9c6ec14b1581a03520b8d2c1fbbc31fb8ceaef2c0f1a0d0080b6b96e18442f1734bea7ef7b635d787c691de4765d469", + "0x8cb437beb4cfa013096f40ccc169a713dc17afee6daa229a398e45fd5c0645a9ad2795c3f0cd439531a7151945d7064d", + "0xa6e8740cc509126e146775157c2eb278003e5bb6c48465c160ed27888ca803fa12eee1f6a8dd7f444f571664ed87fdc1", + "0xb75c1fecc85b2732e96b3f23aefb491dbd0206a21d682aee0225838dc057d7ed3b576176353e8e90ae55663f79e986e4", + "0xad8d249b0aea9597b08358bce6c77c1fd552ef3fbc197d6a1cfe44e5e6f89b628b12a6fb04d5dcfcbacc51f46e4ae7bb", + "0xb998b2269932cbd58d04b8e898d373ac4bb1a62e8567484f4f83e224061bc0f212459f1daae95abdbc63816ae6486a55", + "0x827988ef6c1101cddc96b98f4a30365ff08eea2471dd949d2c0a9b35c3bbfa8c07054ad1f4c88c8fbf829b20bb5a9a4f", + "0x8692e638dd60babf7d9f2f2d2ce58e0ac689e1326d88311416357298c6a2bffbfebf55d5253563e7b3fbbf5072264146", + "0xa685d75b91aea04dbc14ab3c1b1588e6de96dae414c8e37b8388766029631b28dd860688079b12d09cd27f2c5af11adf", + "0xb57eced93eec3371c56679c259b34ac0992286be4f4ff9489d81cf9712403509932e47404ddd86f89d7c1c3b6391b28c", + "0xa1c8b4e42ebcbd8927669a97f1b72e236fb19249325659e72be7ddaaa1d9e81ca2abb643295d41a8c04a2c01f9c0efd7", + "0x877c33de20d4ed31674a671ba3e8f01a316581e32503136a70c9c15bf0b7cb7b1cba6cd4eb641fad165fb3c3c6c235fd", + "0xa2a469d84ec478da40838f775d11ad38f6596eb41caa139cc190d6a10b5108c09febae34ffdafac92271d2e73c143693", + "0x972f817caedb254055d52e963ed28c206848b6c4cfdb69dbc961c891f8458eaf582a6d4403ce1177d87bc2ea410ef60a", + "0xaccbd739e138007422f28536381decc54bb6bd71d93edf3890e54f9ef339f83d2821697d1a4ac1f5a98175f9a9ecb9b5", + "0x8940f8772e05389f823b62b3adc3ed541f91647f0318d7a0d3f293aeeb421013de0d0a3664ea53dd24e5fbe02d7efef6", + "0x8ecce20f3ef6212edef07ec4d6183fda8e0e8cad2c6ccd0b325e75c425ee1faba00b5c26b4d95204238931598d78f49d", + "0x97cc72c36335bd008afbed34a3b0c7225933faba87f7916d0a6d2161e6f82e0cdcda7959573a366f638ca75d30e9dab1", + "0x9105f5de8699b5bdb6bd3bb6cc1992d1eac23929c29837985f83b22efdda92af64d9c574aa9640475087201bbbe5fd73", + "0x8ffb33c4f6d05c413b9647eb6933526a350ed2e4278ca2ecc06b0e8026d8dbe829c476a40e45a6df63a633090a3f82ef", + "0x8bfc6421fdc9c2d2aaa68d2a69b1a2728c25b84944cc3e6a57ff0c94bfd210d1cbf4ff3f06702d2a8257024d8be7de63", + "0xa80e1dc1dddfb41a70220939b96dc6935e00b32fb8be5dff4eed1f1c650002ff95e4af481c43292e3827363b7ec4768a", + "0x96f714ebd54617198bd636ba7f7a7f8995a61db20962f2165078d9ed8ee764d5946ef3cbdc7ebf8435bb8d5dd4c1deac", + "0x8cdb0890e33144d66391d2ae73f5c71f5a861f72bc93bff6cc399fc25dd1f9e17d8772592b44593429718784802ac377", + "0x8ccf9a7f80800ee770b92add734ed45a73ecc31e2af0e04364eefc6056a8223834c7c0dc9dfc52495bdec6e74ce69994", + "0xaa0875f423bd68b5f10ba978ddb79d3b96ec093bfbac9ff366323193e339ed7c4578760fb60f60e93598bdf1e5cc4995", + "0xa9214f523957b59c7a4cb61a40251ad72aba0b57573163b0dc0f33e41d2df483fb9a1b85a5e7c080e9376c866790f8cb", + "0xb6224b605028c6673a536cc8ff9aeb94e7a22e686fda82cf16068d326469172f511219b68b2b3affb7933af0c1f80d07", + "0xb6d58968d8a017c6a34e24c2c09852f736515a2c50f37232ac6b43a38f8faa7572cc31dade543b594b61b5761c4781d0", + "0x8a97cefe5120020c38deeb861d394404e6c993c6cbd5989b6c9ebffe24f46ad11b4ba6348e2991cbf3949c28cfc3c99d", + "0x95bf046f8c3a9c0ce2634be4de3713024daec3fc4083e808903b25ce3ac971145af90686b451efcc72f6b22df0216667", + "0xa6a4e2f71b8fa28801f553231eff2794c0f10d12e7e414276995e21195abc9c2983a8997e41af41e78d19ff6fbb2680b", + "0x8e5e62a7ca9c2f58ebaab63db2ff1fb1ff0877ae94b7f5e2897f273f684ae639dff44cc65718f78a9c894787602ab26a", + "0x8542784383eec4f565fcb8b9fc2ad8d7a644267d8d7612a0f476fc8df3aff458897a38003d506d24142ad18f93554f2b", + "0xb7db68ba4616ea072b37925ec4fb39096358c2832cc6d35169e032326b2d6614479f765ae98913c267105b84afcb9bf2", + "0x8b31dbb9457d23d416c47542c786e07a489af35c4a87dadb8ee91bea5ac4a5315e65625d78dad2cf8f9561af31b45390", + "0xa8545a1d91ac17257732033d89e6b7111db8242e9c6ebb0213a88906d5ef407a2c6fdb444e29504b06368b6efb4f4839", + "0xb1bd85d29ebb28ccfb05779aad8674906b267c2bf8cdb1f9a0591dd621b53a4ee9f2942687ee3476740c0b4a7621a3ae", + "0xa2b54534e152e46c50d91fff03ae9cd019ff7cd9f4168b2fe7ac08ef8c3bbc134cadd3f9d6bd33d20ae476c2a8596c8a", + "0xb19b571ff4ae3e9f5d95acda133c455e72c9ea9973cae360732859836c0341c4c29ab039224dc5bc3deb824e031675d8", + "0x940b5f80478648bac025a30f3efeb47023ce20ee98be833948a248bca6979f206bb28fc0f17b90acf3bb4abd3d14d731", + "0x8f106b40588586ac11629b96d57808ad2808915d89539409c97414aded90b4ff23286a692608230a52bff696055ba5d6", + "0xae6bda03aa10da3d2abbc66d764ca6c8d0993e7304a1bdd413eb9622f3ca1913baa6da1e9f4f9e6cf847f14f44d6924d", + "0xa18e7796054a340ef826c4d6b5a117b80927afaf2ebd547794c400204ae2caf277692e2eabb55bc2f620763c9e9da66d", + "0x8d2d25180dc2c65a4844d3e66819ccfcf48858f0cc89e1c77553b463ec0f7feb9a4002ce26bc618d1142549b9850f232", + "0x863f413a394de42cc8166c1c75d513b91d545fff1de6b359037a742c70b008d34bf8e587afa2d62c844d0c6f0ea753e7", + "0x83cd0cf62d63475e7fcad18a2e74108499cdbf28af2113cfe005e3b5887794422da450b1944d0a986eb7e1f4c3b18f25", + "0xb4f8b350a6d88fea5ab2e44715a292efb12eb52df738c9b2393da3f1ddee68d0a75b476733ccf93642154bceb208f2b8", + "0xb3f52aaa4cd4221cb9fc45936cc67fd3864bf6d26bf3dd86aa85aa55ecfc05f5e392ecce5e7cf9406b4b1c4fce0398c8", + "0xb33137084422fb643123f40a6df2b498065e65230fc65dc31791c330e898c51c3a65ff738930f32c63d78f3c9315f85b", + "0x91452bfa75019363976bb7337fe3a73f1c10f01637428c135536b0cdc7da5ce558dae3dfc792aa55022292600814a8ef", + "0xad6ba94c787cd4361ca642c20793ea44f1f127d4de0bb4a77c7fbfebae0fcadbf28e2cb6f0c12c12a07324ec8c19761d", + "0x890aa6248b17f1501b0f869c556be7bf2b1d31a176f9978bb97ab7a6bd4138eed32467951c5ef1871944b7f620542f43", + "0x82111db2052194ee7dd22ff1eafffac0443cf969d3762cceae046c9a11561c0fdce9c0711f88ac01d1bed165f8a7cee3", + "0xb1527b71df2b42b55832f72e772a466e0fa05743aacc7814f4414e4bcc8d42a4010c9e0fd940e6f254cafedff3cd6543", + "0x922370fa49903679fc565f09c16a5917f8125e72acfeb060fcdbadbd1644eb9f4016229756019c93c6d609cda5d5d174", + "0xaa4c7d98a96cab138d2a53d4aee8ebff6ef903e3b629a92519608d88b3bbd94de5522291a1097e6acf830270e64c8ee1", + "0xb3dc21608a389a72d3a752883a382baaafc61ecc44083b832610a237f6a2363f24195acce529eb4aed4ef0e27a12b66e", + "0x94619f5de05e07b32291e1d7ab1d8b7337a2235e49d4fb5f3055f090a65e932e829efa95db886b32b153bdd05a53ec8c", + "0xade1e92722c2ffa85865d2426fb3d1654a16477d3abf580cfc45ea4b92d5668afc9d09275d3b79283e13e6b39e47424d", + "0xb7201589de7bed094911dd62fcd25c459a8e327ac447b69f541cdba30233063e5ddffad0b67e9c3e34adcffedfd0e13d", + "0x809d325310f862d6549e7cb40f7e5fc9b7544bd751dd28c4f363c724a0378c0e2adcb5e42ec8f912f5f49f18f3365c07", + "0xa79c20aa533de7a5d671c99eb9eb454803ba54dd4f2efa3c8fec1a38f8308e9905c71e9282955225f686146388506ff6", + "0xa85eeacb5e8fc9f3ed06a3fe2dc3108ab9f8c5877b148c73cf26e4e979bf5795edbe2e63a8d452565fd1176ed40402b2", + "0x97ef55662f8a1ec0842b22ee21391227540adf7708f491436044f3a2eb18c471525e78e1e14fa292507c99d74d7437c6", + "0x93110d64ed5886f3d16ce83b11425576a3a7a9bb831cd0de3f9a0b0f2270a730d68136b4ef7ff035ede004358f419b5c", + "0xac9ed0a071517f0ae4f61ce95916a90ba9a77a3f84b0ec50ef7298acdcd44d1b94525d191c39d6bd1bb68f4471428760", + "0x98abd6a02c7690f5a339adf292b8c9368dfc12e0f8069cf26a5e0ce54b4441638f5c66ea735142f3c28e00a0024267e6", + "0xb51efb73ba6d44146f047d69b19c0722227a7748b0e8f644d0fc9551324cf034c041a2378c56ce8b58d06038fb8a78de", + "0x8f115af274ef75c1662b588b0896b97d71f8d67986ae846792702c4742ab855952865ce236b27e2321967ce36ff93357", + "0xb3c4548f14d58b3ab03c222da09e4381a0afe47a72d18d50a94e0008797f78e39e99990e5b4757be62310d400746e35a", + "0xa9b1883bd5f31f909b8b1b6dcb48c1c60ed20aa7374b3ffa7f5b2ed036599b5bef33289d23c80a5e6420d191723b92f7", + "0x85d38dffd99487ae5bb41ab4a44d80a46157bbbe8ef9497e68f061721f74e4da513ccc3422936b059575975f6787c936", + "0xadf870fcb96e972c033ab7a35d28ae79ee795f82bc49c3bd69138f0e338103118d5529c53f2d72a9c0d947bf7d312af2", + "0xab4c7a44e2d9446c6ff303eb49aef0e367a58b22cc3bb27b4e69b55d1d9ee639c9234148d2ee95f9ca8079b1457d5a75", + "0xa386420b738aba2d7145eb4cba6d643d96bda3f2ca55bb11980b318d43b289d55a108f4bc23a9606fb0bccdeb3b3bb30", + "0x847020e0a440d9c4109773ecca5d8268b44d523389993b1f5e60e541187f7c597d79ebd6e318871815e26c96b4a4dbb1", + "0xa530aa7e5ca86fcd1bec4b072b55cc793781f38a666c2033b510a69e110eeabb54c7d8cbcb9c61fee531a6f635ffa972", + "0x87364a5ea1d270632a44269d686b2402da737948dac27f51b7a97af80b66728b0256547a5103d2227005541ca4b7ed04", + "0x8816fc6e16ea277de93a6d793d0eb5c15e9e93eb958c5ef30adaf8241805adeb4da8ce19c3c2167f971f61e0b361077d", + "0x8836a72d301c42510367181bb091e4be377777aed57b73c29ef2ce1d475feedd7e0f31676284d9a94f6db01cc4de81a2", + "0xb0d9d8b7116156d9dde138d28aa05a33e61f8a85839c1e9071ccd517b46a5b4b53acb32c2edd7150c15bc1b4bd8db9e3", + "0xae931b6eaeda790ba7f1cd674e53dc87f6306ff44951fa0df88d506316a5da240df9794ccbd7215a6470e6b31c5ea193", + "0x8c6d5bdf87bd7f645419d7c6444e244fe054d437ed1ba0c122fde7800603a5fadc061e5b836cb22a6cfb2b466f20f013", + "0x90d530c6d0cb654999fa771b8d11d723f54b8a8233d1052dc1e839ea6e314fbed3697084601f3e9bbb71d2b4eaa596df", + "0xb0d341a1422588c983f767b1ed36c18b141774f67ef6a43cff8e18b73a009da10fc12120938b8bba27f225bdfd3138f9", + "0xa131b56f9537f460d304e9a1dd75702ace8abd68cb45419695cb8dee76998139058336c87b7afd6239dc20d7f8f940cc", + "0xaa6c51fa28975f709329adee1bbd35d49c6b878041841a94465e8218338e4371f5cb6c17f44a63ac93644bf28f15d20f", + "0x88440fb584a99ebd7f9ea04aaf622f6e44e2b43bbb49fb5de548d24a238dc8f26c8da2ccf03dd43102bda9f16623f609", + "0x9777b8695b790e702159a4a750d5e7ff865425b95fa0a3c15495af385b91c90c00a6bd01d1b77bffe8c47d01baae846f", + "0x8b9d764ece7799079e63c7f01690c8eff00896a26a0d095773dea7a35967a8c40db7a6a74692f0118bf0460c26739af4", + "0x85808c65c485520609c9e61fa1bb67b28f4611d3608a9f7a5030ee61c3aa3c7e7dc17fff48af76b4aecee2cb0dbd22ac", + "0xad2783a76f5b3db008ef5f7e67391fda4e7e36abde6b3b089fc4835b5c339370287935af6bd53998bed4e399eda1136d", + "0x96f18ec03ae47c205cc4242ca58e2eff185c9dca86d5158817e2e5dc2207ab84aadda78725f8dc080a231efdc093b940", + "0x97de1ab6c6cc646ae60cf7b86df73b9cf56cc0cd1f31b966951ebf79fc153531af55ca643b20b773daa7cab784b832f7", + "0x870ba266a9bfa86ef644b1ef025a0f1b7609a60de170fe9508de8fd53170c0b48adb37f19397ee8019b041ce29a16576", + "0xad990e888d279ac4e8db90619d663d5ae027f994a3992c2fbc7d262b5990ae8a243e19157f3565671d1cb0de17fe6e55", + "0x8d9d5adcdd94c5ba3be4d9a7428133b42e485f040a28d16ee2384758e87d35528f7f9868de9bd23d1a42a594ce50a567", + "0x85a33ed75d514ece6ad78440e42f7fcdb59b6f4cff821188236d20edae9050b3a042ce9bc7d2054296e133d033e45022", + "0x92afd2f49a124aaba90de59be85ff269457f982b54c91b06650c1b8055f9b4b0640fd378df02a00e4fc91f7d226ab980", + "0x8c0ee09ec64bd831e544785e3d65418fe83ed9c920d9bb4d0bf6dd162c1264eb9d6652d2def0722e223915615931581c", + "0x8369bedfa17b24e9ad48ebd9c5afea4b66b3296d5770e09b00446c5b0a8a373d39d300780c01dcc1c6752792bccf5fd0", + "0x8b9e960782576a59b2eb2250d346030daa50bbbec114e95cdb9e4b1ba18c3d34525ae388f859708131984976ca439d94", + "0xb682bface862008fea2b5a07812ca6a28a58fd151a1d54c708fc2f8572916e0d678a9cb8dc1c10c0470025c8a605249e", + "0xa38d5e189bea540a824b36815fc41e3750760a52be0862c4cac68214febdc1a754fb194a7415a8fb7f96f6836196d82a", + "0xb9e7fbda650f18c7eb8b40e42cc42273a7298e65e8be524292369581861075c55299ce69309710e5b843cb884de171bd", + "0xb6657e5e31b3193874a1bace08f42faccbd3c502fb73ad87d15d18a1b6c2a146f1baa929e6f517db390a5a47b66c0acf", + "0xae15487312f84ed6265e4c28327d24a8a0f4d2d17d4a5b7c29b974139cf93223435aaebe3af918f5b4bb20911799715f", + "0x8bb4608beb06bc394e1a70739b872ce5a2a3ffc98c7547bf2698c893ca399d6c13686f6663f483894bccaabc3b9c56ad", + "0xb58ac36bc6847077584308d952c5f3663e3001af5ecf2e19cb162e1c58bd6c49510205d453cffc876ca1dc6b8e04a578", + "0x924f65ced61266a79a671ffb49b300f0ea44c50a0b4e3b02064faa99fcc3e4f6061ea8f38168ab118c5d47bd7804590e", + "0x8d67d43b8a06b0ff4fafd7f0483fa9ed1a9e3e658a03fb49d9d9b74e2e24858dc1bed065c12392037b467f255d4e5643", + "0xb4d4f87813125a6b355e4519a81657fa97c43a6115817b819a6caf4823f1d6a1169683fd68f8d025cdfa40ebf3069acb", + "0xa7fd4d2c8e7b59b8eed3d4332ae94b77a89a2616347402f880bc81bde072220131e6dbec8a605be3a1c760b775375879", + "0x8d4a7d8fa6f55a30df37bcf74952e2fa4fd6676a2e4606185cf154bdd84643fd01619f8fb8813a564f72e3f574f8ce30", + "0x8086fb88e6260e9a9c42e9560fde76315ff5e5680ec7140f2a18438f15bc2cc7d7d43bfb5880b180b738c20a834e6134", + "0x916c4c54721de03934fee6f43de50bb04c81f6f8dd4f6781e159e71c40c60408aa54251d457369d133d4ba3ed7c12cb4", + "0x902e5bf468f11ed9954e2a4a595c27e34abe512f1d6dc08bbca1c2441063f9af3dc5a8075ab910a10ff6c05c1c644a35", + "0xa1302953015e164bf4c15f7d4d35e3633425a78294406b861675667eec77765ff88472306531e5d3a4ec0a2ff0dd6a9e", + "0x87874461df3c9aa6c0fa91325576c0590f367075f2f0ecfeb34afe162c04c14f8ce9d608c37ac1adc8b9985bc036e366", + "0x84b50a8a61d3cc609bfb0417348133e698fe09a6d37357ce3358de189efcf35773d78c57635c2d26c3542b13cc371752", + "0xacaed2cff8633d12c1d12bb7270c54d65b0b0733ab084fd47f81d0a6e1e9b6f300e615e79538239e6160c566d8bb8d29", + "0x889e6a0e136372ca4bac90d1ab220d4e1cad425a710e8cdd48b400b73bb8137291ceb36a39440fa84305783b1d42c72f", + "0x90952e5becec45b2b73719c228429a2c364991cf1d5a9d6845ae5b38018c2626f4308daa322cab1c72e0f6c621bb2b35", + "0x8f5a97a801b6e9dcd66ccb80d337562c96f7914e7169e8ff0fda71534054c64bf2a9493bb830623d612cfe998789be65", + "0x84f3df8b9847dcf1d63ca470dc623154898f83c25a6983e9b78c6d2d90a97bf5e622445be835f32c1e55e6a0a562ea78", + "0x91d12095cd7a88e7f57f254f02fdb1a1ab18984871dead2f107404bcf8069fe68258c4e6f6ebd2477bddf738135400bb", + "0xb771a28bc04baef68604d4723791d3712f82b5e4fe316d7adc2fc01b935d8e644c06d59b83bcb542afc40ebafbee0683", + "0x872f6341476e387604a7e93ae6d6117e72d164e38ebc2b825bc6df4fcce815004d7516423c190c1575946b5de438c08d", + "0x90d6b4aa7d40a020cdcd04e8b016d041795961a8e532a0e1f4041252131089114a251791bf57794cadb7d636342f5d1c", + "0x899023ba6096a181448d927fed7a0fe858be4eac4082a42e30b3050ee065278d72fa9b9d5ce3bc1372d4cbd30a2f2976", + "0xa28f176571e1a9124f95973f414d5bdbf5794d41c3839d8b917100902ac4e2171eb940431236cec93928a60a77ede793", + "0x838dbe5bcd29c4e465d02350270fa0036cd46f8730b13d91e77afb7f5ed16525d0021d3b2ae173a76c378516a903e0cb", + "0x8e105d012dd3f5d20f0f1c4a7e7f09f0fdd74ce554c3032e48da8cce0a77260d7d47a454851387770f5c256fa29bcb88", + "0x8f4df0f9feeb7a487e1d138d13ea961459a6402fd8f8cabb226a92249a0d04ded5971f3242b9f90d08da5ff66da28af6", + "0xad1cfda4f2122a20935aa32fb17c536a3653a18617a65c6836700b5537122af5a8206befe9eaea781c1244c43778e7f1", + "0x832c6f01d6571964ea383292efc8c8fa11e61c0634a25fa180737cc7ab57bc77f25e614aac9a2a03d98f27b3c1c29de2", + "0x903f89cc13ec6685ac7728521898781fecb300e9094ef913d530bf875c18bcc3ceed7ed51e7b482d45619ab4b025c2e9", + "0xa03c474bb915aad94f171e8d96f46abb2a19c9470601f4c915512ec8b9e743c3938450a2a5b077b4618b9df8809e1dc1", + "0x83536c8456f306045a5f38ae4be2e350878fa7e164ea408d467f8c3bc4c2ee396bd5868008c089183868e4dfad7aa50b", + "0x88f26b4ea1b236cb326cd7ad7e2517ec8c4919598691474fe15d09cabcfc37a8d8b1b818f4d112432ee3a716b0f37871", + "0xa44324e3fe96e9c12b40ded4f0f3397c8c7ee8ff5e96441118d8a6bfad712d3ac990b2a6a23231a8f691491ac1fd480f", + "0xb0de4693b4b9f932191a21ee88629964878680152a82996c0019ffc39f8d9369bbe2fe5844b68d6d9589ace54af947e4", + "0x8e5d8ba948aea5fd26035351a960e87f0d23efddd8e13236cc8e4545a3dda2e9a85e6521efb8577e03772d3637d213d9", + "0x93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556", + "0x8731176363ad7658a2862426ee47a5dce9434216cef60e6045fa57c40bb3ce1e78dac4510ae40f1f31db5967022ced32", + "0xb10c9a96745722c85bdb1a693100104d560433d45b9ac4add54c7646a7310d8e9b3ca9abd1039d473ae768a18e489845", + "0xa2ac374dfbb464bf850b4a2caf15b112634a6428e8395f9c9243baefd2452b4b4c61b0cb2836d8eae2d57d4900bf407e", + "0xb69fe3ded0c4f5d44a09a0e0f398221b6d1bf5dbb8bc4e338b93c64f1a3cac1e4b5f73c2b8117158030ec03787f4b452", + "0x8852cdbaf7d0447a8c6f211b4830711b3b5c105c0f316e3a6a18dcfbb9be08bd6f4e5c8ae0c3692da08a2dfa532f9d5c", + "0x93bbf6d7432a7d98ade3f94b57bf9f4da9bc221a180a370b113066dd42601bb9e09edd79e2e6e04e00423399339eebda", + "0xa80941c391f1eeafc1451c59e4775d6a383946ff22997aeaadf806542ba451d3b0f0c6864eeba954174a296efe2c1550", + "0xa045fe2bb011c2a2f71a0181a8f457a3078470fb74c628eab8b59aef69ffd0d649723bf74d6885af3f028bc5a104fb39", + "0xb9d8c35911009c4c8cad64692139bf3fc16b78f5a19980790cb6a7aea650a25df4231a4437ae0c351676a7e42c16134f", + "0x94c79501ded0cfcbab99e1841abe4a00a0252b3870e20774c3da16c982d74c501916ec28304e71194845be6e3113c7ab", + "0x900a66418b082a24c6348d8644ddb1817df5b25cb33044a519ef47cc8e1f7f1e38d2465b7b96d32ed472d2d17f8414c6", + "0xb26f45d393b8b2fcb29bdbb16323dc7f4b81c09618519ab3a39f8ee5bd148d0d9f3c0b5dfab55b5ce14a1cb9206d777b", + "0xaa1a87735fc493a80a96a9a57ca40a6d9c32702bfcaa9869ce1a116ae65d69cefe2f3e79a12454b4590353e96f8912b4", + "0xa922b188d3d0b69b4e4ea2a2aa076566962844637da12c0832105d7b31dea4a309eee15d12b7a336be3ea36fcbd3e3b7", + "0x8f3841fcf4105131d8c4d9885e6e11a46c448226401cf99356c291fadb864da9fa9d30f3a73c327f23f9fd99a11d633e", + "0x9791d1183fae270e226379af6c497e7da803ea854bb20afa74b253239b744c15f670ee808f708ede873e78d79a626c9a", + "0xa4cad52e3369491ada61bf28ada9e85de4516d21c882e5f1cd845bea9c06e0b2887b0c5527fcff6fc28acd3c04f0a796", + "0xb9ac86a900899603452bd11a7892a9bfed8054970bfcbeaa8c9d1930db891169e38d6977f5258c25734f96c8462eee3b", + "0xa3a154c28e5580656a859f4efc2f5ebfa7eaa84ca40e3f134fa7865e8581586db74992dbfa4036aa252fba103773ddde", + "0x95cc2a0c1885a029e094f5d737e3ecf4d26b99036453a8773c77e360101f9f98676ee246f6f732a377a996702d55691f", + "0x842651bbe99720438d8d4b0218feb60481280c05beb17750e9ca0d8c0599a60f873b7fbdcc7d8835ba9a6d57b16eec03", + "0x81ee54699da98f5620307893dcea8f64670609fa20e5622265d66283adeac122d458b3308c5898e6c57c298db2c8b24f", + "0xb97868b0b2bc98032d68352a535a1b341b9ff3c7af4e3a7f3ebc82d3419daa1b5859d6aedc39994939623c7cd878bd9b", + "0xb60325cd5d36461d07ef253d826f37f9ee6474a760f2fff80f9873d01fd2b57711543cdc8d7afa1c350aa753c2e33dea", + "0x8c205326c11d25a46717b780c639d89714c7736c974ae71287e3f4b02e6605ac2d9b4928967b1684f12be040b7bf2dd3", + "0x95a392d82db51e26ade6c2ccd3396d7e40aff68fa570b5951466580d6e56dda51775dce5cf3a74a7f28c3cb2eb551c4d", + "0x8f2cc8071eb56dffb70bda6dd433b556221dc8bba21c53353c865f00e7d4d86c9e39f119ea9a8a12ef583e9a55d9a6b6", + "0x9449a71af9672aaf8856896d7e3d788b22991a7103f75b08c0abbcc2bfe60fda4ed8ce502cea4511ff0ea52a93e81222", + "0x857090ab9fdb7d59632d068f3cc8cf27e61f0d8322d30e6b38e780a1f05227199b4cd746aac1311c36c659ef20931f28", + "0x98a891f4973e7d9aaf9ac70854608d4f7493dffc7e0987d7be9dd6029f6ea5636d24ef3a83205615ca1ff403750058e1", + "0xa486e1365bbc278dd66a2a25d258dc82f46b911103cb16aab3945b9c95ae87b386313a12b566df5b22322ede0afe25ad", + "0xa9a1eb399ed95d396dccd8d1ac718043446f8b979ec62bdce51c617c97a312f01376ab7fb87d27034e5f5570797b3c33", + "0xb7abc3858d7a74bb446218d2f5a037e0fae11871ed9caf44b29b69c500c1fa1dcfad64c9cdccc9d80d5e584f06213deb", + "0x8cfb09fe2e202faa4cebad932b1d35f5ca204e1c2a0c740a57812ac9a6792130d1312aabd9e9d4c58ca168bfebd4c177", + "0xa90a305c2cd0f184787c6be596fa67f436afd1f9b93f30e875f817ac2aae8bdd2e6e656f6be809467e6b3ad84adb86b1", + "0x80a9ef993c2b009ae172cc8f7ec036f5734cf4f4dfa06a7db4d54725e7fbfae5e3bc6f22687bdbb6961939d6f0c87537", + "0x848ade1901931e72b955d7db1893f07003e1708ff5d93174bac5930b9a732640f0578839203e9b77eb27965c700032d3", + "0x93fdf4697609c5ae9c33b9ca2f5f1af44abeb2b98dc4fdf732cf7388de086f410730dc384d9b7a7f447bb009653c8381", + "0x89ce3fb805aea618b5715c0d22a9f46da696b6fa86794f56fdf1d44155a33d42daf1920bcbe36cbacf3cf4c92df9cbc7", + "0x829ce2c342cf82aa469c65f724f308f7a750bd1494adc264609cd790c8718b8b25b5cab5858cf4ee2f8f651d569eea67", + "0xaf2f0cee7bf413204be8b9df59b9e4991bc9009e0d6dbe6815181df0ec2ca93ab8f4f3135b1c14d8f53d74bff0bd6f27", + "0xb87998cecf7b88cde93d1779f10a521edd5574a2fbd240102978639ec57433ba08cdb53849038a329cebbe74657268d2", + "0xa64542a1261a6ed3d720c2c3a802303aad8c4c110c95d0f12e05c1065e66f42da494792b6bfc5b9272363f3b1d457f58", + "0x86a6fd042e4f282fadf07a4bfee03fc96a3aea49f7a00f52bf249a20f1ec892326855410e61f37fbb27d9305eb2fc713", + "0x967ea5bc403b6db269682f7fd0df90659350d7e1aa66bc4fab4c9dfcd75ed0bba4b52f1cebc5f34dc8ba810793727629", + "0xa52990f9f3b8616ce3cdc2c74cd195029e6a969753dcf2d1630438700e7d6ebde36538532b3525ac516f5f2ce9dd27a3", + "0xa64f7ff870bab4a8bf0d4ef6f5c744e9bf1021ed08b4c80903c7ad318e80ba1817c3180cc45cb5a1cae1170f0241655f", + "0xb00f706fa4de1f663f021e8ad3d155e84ce6084a409374b6e6cd0f924a0a0b51bebaaaf1d228c77233a73b0a5a0df0e9", + "0x8b882cc3bff3e42babdb96df95fb780faded84887a0a9bab896bef371cdcf169d909f5658649e93006aa3c6e1146d62e", + "0x9332663ef1d1dcf805c3d0e4ce7a07d9863fb1731172e766b3cde030bf81682cc011e26b773fb9c68e0477b4ae2cfb79", + "0xa8aa8151348dbd4ef40aaeb699b71b4c4bfd3218560c120d85036d14f678f6736f0ec68e80ce1459d3d35feccc575164", + "0xa16cd8b729768f51881c213434aa28301fa78fcb554ddd5f9012ee1e4eae7b5cb3dd88d269d53146dea92d10790faf0b", + "0x86844f0ef9d37142faf3b1e196e44fbe280a3ba4189aa05c356778cb9e3b388a2bff95eed305ada8769935c9974e4c57", + "0xae2eec6b328fccf3b47bcdac32901ac2744a51beb410b04c81dea34dee4912b619466a4f5e2780d87ecefaebbe77b46d", + "0x915df4c38d301c8a4eb2dc5b1ba0ffaad67cbb177e0a80095614e9c711f4ef24a4cef133f9d982a63d2a943ba6c8669d", + "0xae6a2a4dedfc2d1811711a8946991fede972fdf2a389b282471280737536ffc0ac3a6d885b1f8bda0366eb0b229b9979", + "0xa9b628c63d08b8aba6b1317f6e91c34b2382a6c85376e8ef2410a463c6796740ae936fc4e9e0737cb9455d1daa287bd8", + "0x848e30bf7edf2546670b390d5cf9ab71f98fcb6add3c0b582cb34996c26a446dee5d1bde4fdcde4fc80c10936e117b29", + "0x907d6096c7c8c087d1808dd995d5d2b9169b3768c3f433475b50c2e2bd4b082f4d543afd8b0b0ddffa9c66222a72d51d", + "0xa59970a2493b07339124d763ac9d793c60a03354539ecbcf6035bc43d1ea6e35718202ae6d7060b7d388f483d971573c", + "0xb9cfef2af9681b2318f119d8611ff6d9485a68d8044581b1959ab1840cbca576dbb53eec17863d2149966e9feb21122f", + "0xad47271806161f61d3afa45cdfe2babceef5e90031a21779f83dc8562e6076680525b4970b2f11fe9b2b23c382768323", + "0x8e425a99b71677b04fe044625d338811fbb8ee32368a424f6ab2381c52e86ee7a6cecedf777dc97181519d41c351bc22", + "0x86b55b54d7adefc12954a9252ee23ae83efe8b5b4b9a7dc307904413e5d69868c7087a818b2833f9b004213d629be8ad", + "0xa14fda6b93923dd11e564ae4457a66f397741527166e0b16a8eb91c6701c244fd1c4b63f9dd3515193ec88fa6c266b35", + "0xa9b17c36ae6cd85a0ed7f6cabc5b47dc8f80ced605db327c47826476dc1fb8f8669aa7a7dc679fbd4ee3d8e8b4bd6a6f", + "0x82a0829469c1458d959c821148f15dacae9ea94bf56c59a6ab2d4dd8b3d16d73e313b5a3912a6c1f131d73a8f06730c4", + "0xb22d56d549a53eaef549595924bdb621ff807aa4513feedf3fdcbf7ba8b6b9cfa4481c2f67fc642db397a6b794a8b63a", + "0x974c59c24392e2cb9294006cbe3c52163e255f3bd0c2b457bdc68a6338e6d5b6f87f716854492f8d880a6b896ccf757c", + "0xb70d247ba7cad97c50b57f526c2ba915786e926a94e8f8c3eebc2e1be6f4255411b9670e382060049c8f4184302c40b2", + "0xad80201fe75ef21c3ddbd98cf23591e0d7a3ba1036dfe77785c32f44755a212c31f0ceb0a0b6f5ee9b6dc81f358d30c3", + "0x8c656e841f9bb90b9a42d425251f3fdbc022a604d75f5845f479ed4be23e02aaf9e6e56cde351dd7449c50574818a199", + "0x8b88dd3fa209d3063b7c5b058f7249ee9900fbc2287d16da61a0704a0a1d71e45d9c96e1cda7fdf9654534ec44558b22", + "0x961da00cc8750bd84d253c08f011970ae1b1158ad6778e8ed943d547bceaf52d6d5a212a7de3bf2706688c4389b827d2", + "0xa5dd379922549a956033e3d51a986a4b1508e575042b8eaa1df007aa77cf0b8c2ab23212f9c075702788fa9c53696133", + "0xac8fcfde3a349d1e93fc8cf450814e842005c545c4844c0401bc80e6b96cdb77f29285a14455e167c191d4f312e866cd", + "0xac63d79c799783a8466617030c59dd5a8f92ee6c5204676fd8d881ce5f7f8663bdbeb0379e480ea9b6340ab0dc88e574", + "0x805874fde19ce359041ae2bd52a39e2841acabfd31f965792f2737d7137f36d4e4722ede8340d8c95afa6af278af8acb", + "0x8d2f323a228aa8ba7b7dc1399138f9e6b41df1a16a7069003ab8104b8b68506a45141bc5fe66acf430e23e13a545190b", + "0xa1610c721a2d9af882bb6b39bea97cff1527a3aea041d25934de080214ae77c959e79957164440686d15ab301e897d4d", + "0xaba16d29a47fc36f12b654fde513896723e2c700c4190f11b26aa4011da57737ad717daa02794aa3246e4ae5f0b0cc3a", + "0xa406db2f15fdd135f346cc4846623c47edd195e80ba8c7cb447332095314d565e4040694ca924696bb5ee7f8996ea0ba", + "0x8b30e2cd9b47d75ba57b83630e40f832249af6c058d4f490416562af451993eec46f3e1f90bc4d389e4c06abd1b32a46", + "0xaacf9eb7036e248e209adbfc3dd7ce386569ea9b312caa4b240726549db3c68c4f1c8cbf8ed5ea9ea60c7e57c9df3b8e", + "0xb20fcac63bf6f5ee638a42d7f89be847f348c085ddcbec3fa318f4323592d136c230495f188ef2022aa355cc2b0da6f9", + "0x811eff750456a79ec1b1249d76d7c1547065b839d8d4aaad860f6d4528eb5b669473dcceeeea676cddbc3980b68461b7", + "0xb52d14ae33f4ab422f953392ae76a19c618cc31afc96290bd3fe2fb44c954b5c92c4789f3f16e8793f2c0c1691ade444", + "0xa7826dafeeba0db5b66c4dfcf2b17fd7b40507a5a53ac2e42942633a2cb30b95ba1739a6e9f3b7a0e0f1ec729bf274e2", + "0x8acfd83ddf7c60dd7c8b20c706a3b972c65d336b8f9b3d907bdd8926ced271430479448100050b1ef17578a49c8fa616", + "0xaf0c69f65184bb06868029ad46f8465d75c36814c621ac20a5c0b06a900d59305584f5a6709683d9c0e4b6cd08d650a6", + "0xb6cc8588191e00680ee6c3339bd0f0a17ad8fd7f4be57d5d7075bede0ea593a19e67f3d7c1a20114894ee5bfcab71063", + "0xa82fd4f58635129dbb6cc3eb9391cf2d28400018b105fc41500fbbd12bd890b918f97d3d359c29dd3b4c4e34391dfab0", + "0x92fc544ed65b4a3625cf03c41ddff7c039bc22d22c0d59dcc00efd5438401f2606adb125a1d5de294cca216ec8ac35a3", + "0x906f67e4a32582b71f15940523c0c7ce370336935e2646bdaea16a06995256d25e99df57297e39d6c39535e180456407", + "0x97510337ea5bbd5977287339197db55c60533b2ec35c94d0a460a416ae9f60e85cee39be82abeeacd5813cf54df05862", + "0x87e6894643815c0ea48cb96c607266c5ee4f1f82ba5fe352fb77f9b6ed14bfc2b8e09e80a99ac9047dfcf62b2ae26795", + "0xb6fd55dd156622ad7d5d51b7dde75e47bd052d4e542dd6449e72411f68275775c846dde301e84613312be8c7bce58b07", + "0xb98461ac71f554b2f03a94e429b255af89eec917e208a8e60edf5fc43b65f1d17a20de3f31d2ce9f0cb573c25f2f4d98", + "0x96f0dea40ca61cefbee41c4e1fe9a7d81fbe1f49bb153d083ab70f5d0488a1f717fd28cedcf6aa18d07cce2c62801898", + "0x8d7c3ab310184f7dc34b6ce4684e4d29a31e77b09940448ea4daac730b7eb308063125d4dd229046cf11bfd521b771e0", + "0x96f0564898fe96687918bbf0a6adead99cf72e3a35ea3347e124af9d006221f8e82e5a9d2fe80094d5e8d48e610f415e", + "0xad50fcb92c2675a398cf07d4c40a579e44bf8d35f27cc330b57e54d5ea59f7d898af0f75dccfe3726e5471133d70f92b", + "0x828beed62020361689ae7481dd8f116902b522fb0c6c122678e7f949fdef70ead011e0e6bffd25678e388744e17cdb69", + "0x8349decac1ca16599eee2efc95bcaabf67631107da1d34a2f917884bd70dfec9b4b08ab7bc4379d6c73b19c0b6e54fb8", + "0xb2a6a2e50230c05613ace9e58bb2e98d94127f196f02d9dddc53c43fc68c184549ca12d713cb1b025d8260a41e947155", + "0x94ff52181aadae832aed52fc3b7794536e2a31a21fc8be3ea312ca5c695750d37f08002f286b33f4023dba1e3253ecfa", + "0xa21d56153c7e5972ee9a319501be4faff199fdf09bb821ea9ce64aa815289676c00f105e6f00311b3a5b627091b0d0fc", + "0xa27a60d219f1f0c971db73a7f563b371b5c9fc3ed1f72883b2eac8a0df6698400c9954f4ca17d7e94e44bd4f95532afb", + "0xa2fc56fae99b1f18ba5e4fe838402164ce82f8a7f3193d0bbd360c2bac07c46f9330c4c7681ffb47074c6f81ee6e7ac6", + "0xb748e530cd3afb96d879b83e89c9f1a444f54e55372ab1dcd46a0872f95ce8f49cf2363fc61be82259e04f555937ed16", + "0x8bf8993e81080c7cbba1e14a798504af1e4950b2f186ab3335b771d6acaee4ffe92131ae9c53d74379d957cb6344d9cd", + "0x96774d0ef730d22d7ab6d9fb7f90b9ead44285219d076584a901960542756700a2a1603cdf72be4708b267200f6c36a9", + "0xb47703c2ab17be1e823cc7bf3460db1d6760c0e33862c90ca058845b2ff234b0f9834ddba2efb2ee1770eb261e7d8ffd", + "0x84319e67c37a9581f8b09b5e4d4ae88d0a7fb4cbb6908971ab5be28070c3830f040b1de83ee663c573e0f2f6198640e4", + "0x96811875fa83133e0b3c0e0290f9e0e28bca6178b77fdf5350eb19344d453dbd0d71e55a0ef749025a5a2ca0ad251e81", + "0x81a423423e9438343879f2bfd7ee9f1c74ebebe7ce3cfffc8a11da6f040cc4145c3b527bd3cf63f9137e714dbcb474ef", + "0xb8c3535701ddbeec2db08e17a4fa99ba6752d32ece5331a0b8743676f421fcb14798afc7c783815484f14693d2f70db8", + "0x81aee980c876949bf40782835eec8817d535f6f3f7e00bf402ddd61101fdcd60173961ae90a1cf7c5d060339a18c959d", + "0x87e67b928d97b62c49dac321ce6cb680233f3a394d4c9a899ac2e8db8ccd8e00418e66cdfd68691aa3cb8559723b580c", + "0x8eac204208d99a2b738648df96353bbb1b1065e33ee4f6bba174b540bbbd37d205855e1f1e69a6b7ff043ca377651126", + "0x848e6e7a54ad64d18009300b93ea6f459ce855971dddb419b101f5ac4c159215626fadc20cc3b9ab1701d8f6dfaddd8b", + "0x88aa123d9e0cf309d46dddb6acf634b1ade3b090a2826d6e5e78669fa1220d6df9a6697d7778cd9b627db17eea846126", + "0x9200c2a629b9144d88a61151b661b6c4256cc5dadfd1e59a8ce17a013c2d8f7e754aabe61663c3b30f1bc47784c1f8cf", + "0xb6e1a2827c3bdda91715b0e1b1f10dd363cef337e7c80cac1f34165fc0dea7c8b69747e310563db5818390146ce3e231", + "0x92c333e694f89f0d306d54105b2a5dcc912dbe7654d9e733edab12e8537350815be472b063e56cfde5286df8922fdecb", + "0xa6fac04b6d86091158ebb286586ccfec2a95c9786e14d91a9c743f5f05546073e5e3cc717635a0c602cad8334e922346", + "0xa581b4af77feebc1fb897d49b5b507c6ad513d8f09b273328efbb24ef0d91eb740d01b4d398f2738125dacfe550330cd", + "0x81c4860cccf76a34f8a2bc3f464b7bfd3e909e975cce0d28979f457738a56e60a4af8e68a3992cf273b5946e8d7f76e2", + "0x8d1eaa09a3180d8af1cbaee673db5223363cc7229a69565f592fa38ba0f9d582cedf91e15dabd06ebbf2862fc0feba54", + "0x9832f49b0147f4552402e54593cfa51f99540bffada12759b71fcb86734be8e500eea2d8b3d036710bdf04c901432de9", + "0x8bdb0e8ec93b11e5718e8c13cb4f5de545d24829fd76161216340108098dfe5148ed25e3b57a89a516f09fa79043734d", + "0xab96f06c4b9b0b2c0571740b24fca758e6976315053a7ecb20119150a9fa416db2d3a2e0f8168b390bb063f0c1caf785", + "0xab777f5c52acd62ecf4d1f168b9cc8e1a9b45d4ec6a8ff52c583e867c2239aba98d7d3af977289b367edce03d9c2dfb1", + "0xa09d3ce5e748da84802436951acc3d3ea5d8ec1d6933505ed724d6b4b0d69973ab0930daec9c6606960f6e541e4a3ce2", + "0x8ef94f7be4d85d5ad3d779a5cf4d7b2fc3e65c52fb8e1c3c112509a4af77a0b5be994f251e5e40fabeeb1f7d5615c22b", + "0xa7406a5bf5708d9e10922d3c5c45c03ef891b8d0d74ec9f28328a72be4cdc05b4f2703fa99366426659dfca25d007535", + "0xb7f52709669bf92a2e070bfe740f422f0b7127392c5589c7f0af71bb5a8428697c762d3c0d74532899da24ea7d8695c2", + "0xb9dfb0c8df84104dbf9239ccefa4672ef95ddabb8801b74997935d1b81a78a6a5669a3c553767ec19a1281f6e570f4ff", + "0xae4d5c872156061ce9195ac640190d8d71dd406055ee43ffa6f9893eb24b870075b74c94d65bc1d5a07a6573282b5520", + "0xafe6bd3eb72266d333f1807164900dcfa02a7eb5b1744bb3c86b34b3ee91e3f05e38fa52a50dc64eeb4bdb1dd62874b8", + "0x948043cf1bc2ef3c01105f6a78dc06487f57548a3e6ef30e6ebc51c94b71e4bf3ff6d0058c72b6f3ecc37efd7c7fa8c0", + "0xa22fd17c2f7ffe552bb0f23fa135584e8d2d8d75e3f742d94d04aded2a79e22a00dfe7acbb57d44e1cdb962fb22ae170", + "0x8cd0f4e9e4fb4a37c02c1bde0f69359c43ab012eb662d346487be0c3758293f1ca560122b059b091fddce626383c3a8f", + "0x90499e45f5b9c81426f3d735a52a564cafbed72711d9279fdd88de8038e953bc48c57b58cba85c3b2e4ce56f1ddb0e11", + "0x8c30e4c034c02958384564cac4f85022ef36ab5697a3d2feaf6bf105049675bbf23d01b4b6814711d3d9271abff04cac", + "0x81f7999e7eeea30f3e1075e6780bbf054f2fb6f27628a2afa4d41872a385b4216dd5f549da7ce6cf39049b2251f27fb7", + "0xb36a7191f82fc39c283ffe53fc1f5a9a00b4c64eee7792a8443475da9a4d226cf257f226ea9d66e329af15d8f04984ec", + "0xaad4da528fdbb4db504f3041c747455baff5fcd459a2efd78f15bdf3aea0bdb808343e49df88fe7a7c8620009b7964a3", + "0x99ebd8c6dd5dd299517fb6381cfc2a7f443e6e04a351440260dd7c2aee3f1d8ef06eb6c18820b394366ecdfd2a3ce264", + "0x8873725b81871db72e4ec3643084b1cdce3cbf80b40b834b092767728605825c19b6847ad3dcf328438607e8f88b4410", + "0xb008ee2f895daa6abd35bd39b6f7901ae4611a11a3271194e19da1cdcc7f1e1ea008fe5c5440e50d2c273784541ad9c5", + "0x9036feafb4218d1f576ef89d0e99124e45dacaa6d816988e34d80f454d10e96809791d5b78f7fd65f569e90d4d7238c5", + "0x92073c1d11b168e4fa50988b0288638b4868e48bbc668c5a6dddf5499875d53be23a285acb5e4bad60114f6cf6c556e9", + "0x88c87dfcb8ba6cbfe7e1be081ccfadbd589301db2cb7c99f9ee5d7db90aa297ed1538d5a867678a763f2deede5fd219a", + "0xb42a562805c661a50f5dea63108002c0f27c0da113da6a9864c9feb5552225417c0356c4209e8e012d9bcc9d182c7611", + "0x8e6317d00a504e3b79cd47feb4c60f9df186467fe9ca0f35b55c0364db30528f5ff071109dabb2fc80bb9cd4949f0c24", + "0xb7b1ea6a88694f8d2f539e52a47466695e39e43a5eb9c6f23bca15305fe52939d8755cc3ac9d6725e60f82f994a3772f", + "0xa3cd55161befe795af93a38d33290fb642b8d80da8b786c6e6fb02d393ea308fbe87f486994039cbd7c7b390414594b6", + "0xb416d2d45b44ead3b1424e92c73c2cf510801897b05d1724ff31cbd741920cd858282fb5d6040fe1f0aa97a65bc49424", + "0x950ee01291754feace97c2e933e4681e7ddfbc4fcd079eb6ff830b0e481d929c93d0c7fb479c9939c28ca1945c40da09", + "0x869bd916aee8d86efe362a49010382674825d49195b413b4b4018e88ce43fe091b475d0b863ff0ba2259400f280c2b23", + "0x9782f38cd9c9d3385ec286ebbc7cba5b718d2e65a5890b0a5906b10a89dc8ed80d417d71d7c213bf52f2af1a1f513ea7", + "0x91cd33bc2628d096269b23faf47ee15e14cb7fdc6a8e3a98b55e1031ea0b68d10ba30d97e660f7e967d24436d40fad73", + "0x8becc978129cc96737034c577ae7225372dd855da8811ae4e46328e020c803833b5bdbc4a20a93270e2b8bd1a2feae52", + "0xa36b1d8076783a9522476ce17f799d78008967728ce920531fdaf88303321bcaf97ecaa08e0c01f77bc32e53c5f09525", + "0xb4720e744943f70467983aa34499e76de6d59aa6fadf86f6b787fdce32a2f5b535b55db38fe2da95825c51002cfe142d", + "0x91ad21fc502eda3945f6de874d1b6bf9a9a7711f4d61354f9e5634fc73f9c06ada848de15ab0a75811d3250be862827d", + "0x84f78e2ebf5fc077d78635f981712daf17e2475e14c2a96d187913006ad69e234746184a51a06ef510c9455b38acb0d7", + "0x960aa7906e9a2f11db64a26b5892ac45f20d2ccb5480f4888d89973beb6fa0dfdc06d68d241ff5ffc7f1b82b1aac242d", + "0xa99365dcd1a00c66c9db6924b97c920f5c723380e823b250db85c07631b320ec4e92e586f7319e67a522a0578f7b6d6c", + "0xa25d92d7f70cf6a88ff317cfec071e13774516da664f5fac0d4ecaa65b8bf4eb87a64a4d5ef2bd97dfae98d388dbf5cc", + "0xa7af47cd0041295798f9779020a44653007444e8b4ef0712982b06d0dcdd434ec4e1f7c5f7a049326602cb605c9105b7", + "0xaefe172eac5568369a05980931cc476bebd9dea573ba276d59b9d8c4420784299df5a910033b7e324a6c2dfc62e3ef05", + "0xb69bc9d22ffa645baa55e3e02522e9892bb2daa7fff7c15846f13517d0799766883ee09ae0869df4139150c5b843ca8a", + "0x95a10856140e493354fdd12722c7fdded21b6a2ffbc78aa2697104af8ad0c8e2206f44b0bfee077ef3949d46bbf7c16b", + "0x891f2fcd2c47cbea36b7fa715968540c233313f05333f09d29aba23c193f462ed490dd4d00969656e89c53155fdfe710", + "0xa6c33e18115e64e385c843dde34e8a228222795c7ca90bc2cc085705d609025f3351d9be61822c69035a49fb3e48f2d5", + "0xb87fb12f12c0533b005adad0487f03393ff682e13575e3cb57280c3873b2c38ba96a63c49eef7a442753d26b7005230b", + "0xb905c02ba451bfd411c135036d92c27af3b0b1c9c2f1309d6948544a264b125f39dd41afeff4666b12146c545adc168a", + "0x8b29c513f43a78951cf742231cf5457a6d9d55edf45df5481a0f299a418d94effef561b15d2c1a01d1b8067e7153fda9", + "0xb9941cccd51dc645920d2781c81a317e5a33cb7cf76427b60396735912cb6d2ca9292bb4d36b6392467d390d2c58d9f3", + "0xa8546b627c76b6ef5c93c6a98538d8593dbe21cb7673fd383d5401b0c935eea0bdeeefeb1af6ad41bad8464fb87bbc48", + "0xaa286b27de2812de63108a1aec29d171775b69538dc6198640ac1e96767c2b83a50391f49259195957d457b493b667c9", + "0xa932fb229f641e9abbd8eb2bd874015d97b6658ab6d29769fc23b7db9e41dd4f850382d4c1f08af8f156c5937d524473", + "0xa1412840fcc86e2aeec175526f2fb36e8b3b8d21a78412b7266daf81e51b3f68584ed8bd42a66a43afdd8c297b320520", + "0x89c78be9efb624c97ebca4fe04c7704fa52311d183ffd87737f76b7dadc187c12c982bd8e9ed7cd8beb48cdaafd2fd01", + "0xa3f5ddec412a5bec0ce15e3bcb41c6214c2b05d4e9135a0d33c8e50a78eaba71e0a5a6ea8b45854dec5c2ed300971fc2", + "0x9721f9cec7a68b7758e3887548790de49fa6a442d0396739efa20c2f50352a7f91d300867556d11a703866def2d5f7b5", + "0xa23764e140a87e5991573521af039630dd28128bf56eed2edbed130fd4278e090b60cf5a1dca9de2910603d44b9f6d45", + "0xa1a6494a994215e48ab55c70efa8ffdddce6e92403c38ae7e8dd2f8288cad460c6c7db526bbdf578e96ca04d9fe12797", + "0xb1705ea4cb7e074efe0405fc7b8ee2ec789af0426142f3ec81241cacd4f7edcd88e39435e4e4d8e7b1df64f3880d6613", + "0x85595d061d677116089a6064418b93eb44ff79e68d12bd9625078d3bbc440a60d0b02944eff6054433ee34710ae6fbb4", + "0x9978d5e30bedb7526734f9a1febd973a70bfa20890490e7cc6f2f9328feab1e24f991285dbc3711d892514e2d7d005ad", + "0xaf30243c66ea43b9f87a061f947f7bce745f09194f6e95f379c7582b9fead920e5d6957eaf05c12ae1282ada4670652f", + "0xa1930efb473f88001e47aa0b2b2a7566848cccf295792e4544096ecd14ee5d7927c173a8576b405bfa2eec551cd67eb5", + "0xb0446d1c590ee5a45f7e22d269c044f3848c97aec1d226b44bfd0e94d9729c28a38bccddc3a1006cc5fe4e3c24f001f2", + "0xb8a8380172df3d84b06176df916cf557966d4f2f716d3e9437e415d75b646810f79f2b2b71d857181b7fc944018883a3", + "0xa563afec25b7817bfa26e19dc9908bc00aa8fc3d19be7d6de23648701659009d10e3e4486c28e9c6b13d48231ae29ac5", + "0xa5a8e80579de886fb7d6408f542791876885947b27ad6fa99a8a26e381f052598d7b4e647b0115d4b5c64297e00ce28e", + "0x8f87afcc7ad33c51ac719bade3cd92da671a37a82c14446b0a2073f4a0a23085e2c8d31913ed2d0be928f053297de8f6", + "0xa43c455ce377e0bc434386c53c752880687e017b2f5ae7f8a15c044895b242dffde4c92fb8f8bb50b18470b17351b156", + "0x8368f8b12a5bceb1dba25adb3a2e9c7dc9b1a77a1f328e5a693f5aec195cd1e06b0fe9476b554c1c25dac6c4a5b640a3", + "0x919878b27f3671fc78396f11531c032f3e2bd132d04cc234fa4858676b15fb1db3051c0b1db9b4fc49038216f11321ce", + "0xb48cd67fb7f1242696c1f877da4bdf188eac676cd0e561fbac1a537f7b8229aff5a043922441d603a26aae56a15faee4", + "0xa3e0fdfd4d29ea996517a16f0370b54787fefe543c2fe73bfc6f9e560c1fd30dad8409859e2d7fa2d44316f24746c712", + "0x8bb156ade8faf149df7bea02c140c7e392a4742ae6d0394d880a849127943e6f26312033336d3b9fdc0092d71b5efe87", + "0x8845e5d5cc555ca3e0523244300f2c8d7e4d02aaebcb5bd749d791208856c209a6f84dd99fd55968c9f0ab5f82916707", + "0xa3e90bb5c97b07789c2f32dff1aec61d0a2220928202f5ad5355ae71f8249237799d6c8a22602e32e572cb12eabe0c17", + "0xb150bcc391884c996149dc3779ce71f15dda63a759ee9cc05871f5a8379dcb62b047098922c0f26c7bd04deb394c33f9", + "0x95cd4ad88d51f0f2efcfd0c2df802fe252bb9704d1afbf9c26a248df22d55da87bdfaf41d7bc6e5df38bd848f0b13f42", + "0xa05a49a31e91dff6a52ac8b9c2cfdd646a43f0d488253f9e3cfbce52f26667166bbb9b608fc358763a65cbf066cd6d05", + "0xa59c3c1227fdd7c2e81f5e11ef5c406da44662987bac33caed72314081e2eed66055d38137e01b2268e58ec85dd986c0", + "0xb7020ec3bd73a99861f0f1d88cf5a19abab1cbe14b7de77c9868398c84bb8e18dbbe9831838a96b6d6ca06e82451c67b", + "0x98d1ff2525e9718ee59a21d8900621636fcd873d9a564b8dceb4be80a194a0148daf1232742730b3341514b2e5a5436c", + "0x886d97b635975fc638c1b6afc493e5998ca139edba131b75b65cfe5a8e814f11bb678e0eeee5e6e5cd913ad3f2fefdfc", + "0x8fb9fd928d38d5d813b671c924edd56601dd7163b686c13f158645c2f869d9250f3859aa5463a39258c90fef0f41190a", + "0xaac35e1cd655c94dec3580bb3800bd9c2946c4a9856f7d725af15fbea6a2d8ca51c8ad2772abed60ee0e3fb9cb24046b", + "0xb8d71fa0fa05ac9e443c9b4929df9e7f09a919be679692682e614d24227e04894bfc14a5c73a62fb927fedff4a0e4aa7", + "0xa45a19f11fbbb531a704badbb813ed8088ab827c884ee4e4ebf363fa1132ff7cfa9d28be9c85b143e4f7cdbc94e7cf1a", + "0x82b54703a4f295f5471b255ab59dce00f0fe90c9fb6e06b9ee48b15c91d43f4e2ef4a96c3118aeb03b08767be58181bb", + "0x8283264c8e6d2a36558f0d145c18576b6600ff45ff99cc93eca54b6c6422993cf392668633e5df396b9331e873d457e5", + "0x8c549c03131ead601bc30eb6b9537b5d3beb7472f5bb1bcbbfd1e9f3704477f7840ab3ab7f7dc13bbbbcdff886a462d4", + "0xafbb0c520ac1b5486513587700ad53e314cb74bfbc12e0b5fbdcfdaac36d342e8b59856196a0d84a25cff6e6e1d17e76", + "0x89e4c22ffb51f2829061b3c7c1983c5c750cad158e3a825d46f7cf875677da5d63f653d8a297022b5db5845c9271b32b", + "0xafb27a86c4c2373088c96b9adf4433f2ebfc78ac5c526e9f0510670b6e4e5e0057c0a4f75b185e1a30331b9e805c1c15", + "0xa18e16b57445f88730fc5d3567bf5a176861dc14c7a08ed2996fe80eed27a0e7628501bcb78a1727c5e9ac55f29c12c4", + "0x93d61bf88b192d6825cf4e1120af1c17aa0f994d158b405e25437eaeefae049f7b721a206e7cc8a04fdc29d3c42580a1", + "0xa99f2995a2e3ed2fd1228d64166112038de2f516410aa439f4c507044e2017ea388604e2d0f7121256fadf7fbe7023d1", + "0x914fd91cffc23c32f1c6d0e98bf660925090d873367d543034654389916f65f552e445b0300b71b61b721a72e9a5983c", + "0xb42a578a7787b71f924e7def425d849c1c777156b1d4170a8ee7709a4a914e816935131afd9a0412c4cb952957b20828", + "0x82fb30590e84b9e45db1ec475a39971cf554dc01bcc7050bc89265740725c02e2be5a972168c5170c86ae83e5b0ad2c0", + "0xb14f8d8e1e93a84976289e0cf0dfa6f3a1809e98da16ee5c4932d0e1ed6bf8a07697fdd4dd86a3df84fb0003353cdcc0", + "0x85d7a2f4bda31aa2cb208b771fe03291a4ebdaf6f1dc944c27775af5caec412584c1f45bc741fca2a6a85acb3f26ad7d", + "0xaf02e56ce886ff2253bc0a68faad76f25ead84b2144e5364f3fb9b648f03a50ee9dc0b2c33ebacf7c61e9e43201ef9ef", + "0x87e025558c8a0b0abd06dfc350016847ea5ced7af2d135a5c9eec9324a4858c4b21510fb0992ec52a73447f24945058e", + "0x80fff0bafcd058118f5e7a4d4f1ae0912efeb281d2cbe4d34ba8945cc3dbe5d8baf47fb077343b90b8d895c90b297aca", + "0xb6edcf3a40e7b1c3c0148f47a263cd819e585a51ef31c2e35a29ce6f04c53e413f743034c0d998d9c00a08ba00166f31", + "0xabb87ed86098c0c70a76e557262a494ff51a30fb193f1c1a32f8e35eafa34a43fcc07aa93a3b7a077d9e35afa07b1a3d", + "0xa280214cd3bb0fb7ecd2d8bcf518cbd9078417f2b91d2533ec2717563f090fb84f2a5fcfdbbeb2a2a1f8a71cc5aa5941", + "0xa63083ca7238ea2b57d15a475963cf1d4f550d8cd76db290014a0461b90351f1f26a67d674c837b0b773b330c7c3d534", + "0xa8fa39064cb585ece5263e2f42f430206476bf261bd50f18d2b694889bd79d04d56410664cecad62690e5c5a20b3f6ff", + "0x85ba52ce9d700a5dcf6c5b00559acbe599d671ce5512467ff4b6179d7fad550567ce2a9c126a50964e3096458ea87920", + "0xb913501e1008f076e5eac6d883105174f88b248e1c9801e568fefaffa1558e4909364fc6d9512aa4d125cbd7cc895f05", + "0x8eb33b5266c8f2ed4725a6ad147a322e44c9264cf261c933cbbe230a43d47fca0f29ec39756b20561dabafadd5796494", + "0x850ebc8b661a04318c9db5a0515066e6454fa73865aa4908767a837857ecd717387f614acb614a88e075d4edc53a2f5a", + "0xa08d6b92d866270f29f4ce23a3f5d99b36b1e241a01271ede02817c8ec3f552a5c562db400766c07b104a331835c0c64", + "0x8131804c89bb3e74e9718bfc4afa547c1005ff676bd4db9604335032b203390cfa54478d45c6c78d1fe31a436ed4be9f", + "0x9106d94f23cc1eacec8316f16d6f0a1cc160967c886f51981fdb9f3f12ee1182407d2bb24e5b873de58cb1a3ee915a6b", + "0xa13806bfc3eae7a7000c9d9f1bd25e10218d4e67f59ae798b145b098bca3edad2b1040e3fc1e6310e612fb8818f459ac", + "0x8c69fbca502046cb5f6db99900a47b34117aef3f4b241690cdb3b84ca2a2fc7833e149361995dc41fa78892525bce746", + "0x852c473150c91912d58ecb05769222fa18312800c3f56605ad29eec9e2d8667b0b81c379048d3d29100ed2773bb1f3c5", + "0xb1767f6074426a00e01095dbb1795beb4e4050c6411792cbad6537bc444c3165d1058bafd1487451f9c5ddd209e0ae7e", + "0x80c600a5fe99354ce59ff0f84c760923dc8ff66a30bf47dc0a086181785ceb01f9b951c4e66df800ea6d705e8bc47055", + "0xb5cf19002fbc88a0764865b82afcb4d64a50196ea361e5c71dff7de084f4dcbbc34ec94a45cc9e0247bd51da565981aa", + "0x93e67a254ea8ce25e112d93cc927fadaa814152a2c4ec7d9a56eaa1ed47aec99b7e9916b02e64452cc724a6641729bbb", + "0xace70b32491bda18eee4a4d041c3bc9effae9340fe7e6c2f5ad975ee0874c17f1a7da7c96bd85fccff9312c518fac6e9", + "0xab4cfa02065017dd7f1aadc66f2c92f78f0f11b8597c03a5d69d82cb2eaf95a4476a836ac102908f137662472c8d914b", + "0xa40b8cd8deb8ae503d20364d64cab7c2801b7728a9646ed19c65edea6a842756a2f636283494299584ad57f4bb12cd0b", + "0x8594e11d5fc2396bcd9dbf5509ce4816dbb2b7305168021c426171fb444d111da5a152d6835ad8034542277011c26c0e", + "0x8024de98c26b4c994a66628dc304bb737f4b6859c86ded552c5abb81fd4c6c2e19d5a30beed398a694b9b2fdea1dd06a", + "0x8843f5872f33f54df8d0e06166c1857d733995f67bc54abb8dfa94ad92407cf0179bc91b0a50bbb56cdc2b350d950329", + "0xb8bab44c7dd53ef9edf497dcb228e2a41282c90f00ba052fc52d57e87b5c8ab132d227af1fcdff9a12713d1f980bcaae", + "0x982b4d7b29aff22d527fd82d2a52601d95549bfb000429bb20789ed45e5abf1f4b7416c7b7c4b79431eb3574b29be658", + "0x8eb1f571b6a1878e11e8c1c757e0bc084bab5e82e897ca9be9b7f4b47b91679a8190bf0fc8f799d9b487da5442415857", + "0xa6e74b588e5af935c8b243e888582ef7718f8714569dd4992920740227518305eb35fab674d21a5551cca44b3e511ef2", + "0xa30fc2f3a4cb4f50566e82307de73cd7bd8fe2c1184e9293c136a9b9e926a018d57c6e4f308c95b9eb8299e94d90a2a1", + "0xa50c5869ca5d2b40722c056a32f918d47e0b65ca9d7863ca7d2fb4a7b64fe523fe9365cf0573733ceaadebf20b48fff8", + "0x83bbdd32c04d17581418cf360749c7a169b55d54f2427390defd9f751f100897b2d800ce6636c5bbc046c47508d60c8c", + "0xa82904bdf614de5d8deaff688c8a5e7ac5b3431687acbcda8fa53960b7c417a39c8b2e462d7af91ce6d79260f412db8e", + "0xa4362e31ff4b05d278b033cf5eebea20de01714ae16d4115d04c1da4754269873afc8171a6f56c5104bfd7b0db93c3e7", + "0xb5b8daa63a3735581e74a021b684a1038cea77168fdb7fdf83c670c2cfabcfc3ab2fc7359069b5f9048188351aef26b5", + "0xb48d723894b7782d96ac8433c48faca1bdfa5238019c451a7f47d958097cce3ae599b876cf274269236b9d6ff8b6d7ca", + "0x98ffff6a61a3a6205c7820a91ca2e7176fab5dba02bc194c4d14942ac421cb254183c705506ab279e4f8db066f941c6c", + "0xae7db24731da2eaa6efc4f7fcba2ecc26940ddd68038dce43acf2cee15b72dc4ef42a7bfdd32946d1ed78786dd7696b3", + "0xa656db14f1de9a7eb84f6301b4acb2fbf78bfe867f48a270e416c974ab92821eb4df1cb881b2d600cfed0034ac784641", + "0xaa315f8ecba85a5535e9a49e558b15f39520fce5d4bf43131bfbf2e2c9dfccc829074f9083e8d49f405fb221d0bc4c3c", + "0x90bffba5d9ff40a62f6c8e9fc402d5b95f6077ed58d030c93e321b8081b77d6b8dac3f63a92a7ddc01585cf2c127d66c", + "0xabdd733a36e0e0f05a570d0504e73801bf9b5a25ff2c78786f8b805704997acb2e6069af342538c581144d53149fa6d3", + "0xb4a723bb19e8c18a01bd449b1bb3440ddb2017f10bb153da27deb7a6a60e9bb37619d6d5435fbb1ba617687838e01dd0", + "0x870016b4678bab3375516db0187a2108b2e840bae4d264b9f4f27dbbc7cc9cac1d7dc582d7a04d6fd1ed588238e5e513", + "0x80d33d2e20e8fc170aa3cb4f69fffb72aeafb3b5bb4ea0bc79ab55da14142ca19b2d8b617a6b24d537366e3b49cb67c3", + "0xa7ee76aec273aaae03b3b87015789289551969fb175c11557da3ab77e39ab49d24634726f92affae9f4d24003050d974", + "0x8415ea4ab69d779ebd42d0fe0c6aef531d6a465a5739e429b1fcf433ec45aa8296c527e965a20f0ec9f340c9273ea3cf", + "0x8c7662520794e8b4405d0b33b5cac839784bc86a5868766c06cbc1fa306dbe334978177417b31baf90ce7b0052a29c56", + "0x902b2abecc053a3dbdea9897ee21e74821f3a1b98b2d560a514a35799f4680322550fd3a728d4f6d64e1de98033c32b8", + "0xa05e84ed9ecab8d508d670c39f2db61ad6e08d2795ec32a3c9d0d3737ef3801618f4fc2a95f90ec2f068606131e076c5", + "0x8b9208ff4d5af0c2e3f53c9375da666773ac57197dfabb0d25b1c8d0588ba7f3c15ee9661bb001297f322ea2fbf6928b", + "0xa3c827741b34a03254d4451b5ab74a96f2b9f7fb069e2f5adaf54fd97cc7a4d516d378db5ca07da87d8566d6eef13726", + "0x8509d8a3f4a0ed378e0a1e28ea02f6bf1d7f6c819c6c2f5297c7df54c895b848f841653e32ba2a2c22c2ff739571acb8", + "0xa0ce988b7d3c40b4e496aa83a09e4b5472a2d98679622f32bea23e6d607bc7de1a5374fb162bce0549a67dad948519be", + "0xaa8a3dd12bd60e3d2e05f9c683cdcb8eab17fc59134815f8d197681b1bcf65108cba63ac5c58ee632b1e5ed6bba5d474", + "0x8b955f1d894b3aefd883fb4b65f14cd37fc2b9db77db79273f1700bef9973bf3fd123897ea2b7989f50003733f8f7f21", + "0xac79c00ddac47f5daf8d9418d798d8af89fc6f1682e7e451f71ea3a405b0d36af35388dd2a332af790bc83ca7b819328", + "0xa0d44dd2a4438b809522b130d0938c3fe7c5c46379365dbd1810a170a9aa5818e1c783470dd5d0b6d4ac7edbb7330910", + "0xa30b69e39ad43dd540a43c521f05b51b5f1b9c4eed54b8162374ae11eac25da4f5756e7b70ce9f3c92c2eeceee7431ed", + "0xac43220b762c299c7951222ea19761ab938bf38e4972deef58ed84f4f9c68c230647cf7506d7cbfc08562fcca55f0485", + "0xb28233b46a8fb424cfa386a845a3b5399d8489ceb83c8f3e05c22c934798d639c93718b7b68ab3ce24c5358339e41cbb", + "0xac30d50ee8ce59a10d4b37a3a35e62cdb2273e5e52232e202ca7d7b8d09d28958ee667fae41a7bb6cdc6fe8f6e6c9c85", + "0xb199842d9141ad169f35cc7ff782b274cbaa645fdb727761e0a89edbf0d781a15f8218b4bf4eead326f2903dd88a9cc1", + "0x85e018c7ddcad34bb8285a737c578bf741ccd547e68c734bdb3808380e12c5d4ef60fc896b497a87d443ff9abd063b38", + "0x8c856e6ba4a815bdb891e1276f93545b7072f6cb1a9aa6aa5cf240976f29f4dee01878638500a6bf1daf677b96b54343", + "0xb8a47555fa8710534150e1a3f13eab33666017be6b41005397afa647ea49708565f2b86b77ad4964d140d9ced6b4d585", + "0x8cd1f1db1b2f4c85a3f46211599caf512d5439e2d8e184663d7d50166fd3008f0e9253272f898d81007988435f715881", + "0xb1f34b14612c973a3eceb716dc102b82ab18afef9de7630172c2780776679a7706a4874e1df3eaadf541fb009731807f", + "0xb25464af9cff883b55be2ff8daf610052c02df9a5e147a2cf4df6ce63edcdee6dc535c533590084cc177da85c5dc0baa", + "0x91c3c4b658b42d8d3448ae1415d4541d02379a40dc51e36a59bd6e7b9ba3ea51533f480c7c6e8405250ee9b96a466c29", + "0x86dc027b95deb74c36a58a1333a03e63cb5ae22d3b29d114cfd2271badb05268c9d0c819a977f5e0c6014b00c1512e3a", + "0xae0e6ff58eb5fa35da5107ebeacf222ab8f52a22bb1e13504247c1dfa65320f40d97b0e6b201cb6613476687cb2f0681", + "0x8f13415d960b9d7a1d93ef28afc2223e926639b63bdefce0f85e945dfc81670a55df288893a0d8b3abe13c5708f82f91", + "0x956f67ca49ad27c1e3a68c1faad5e7baf0160c459094bf6b7baf36b112de935fdfd79fa4a9ea87ea8de0ac07272969f4", + "0x835e45e4a67df9fb51b645d37840b3a15c171d571a10b03a406dd69d3c2f22df3aa9c5cbe1e73f8d767ce01c4914ea9a", + "0x919b938e56d4b32e2667469d0bdccb95d9dda3341aa907683ee70a14bbbe623035014511c261f4f59b318b610ac90aa3", + "0x96b48182121ccd9d689bf1dfdc228175564cd68dc904a99c808a7f0053a6f636c9d953e12198bdf2ea49ea92772f2e18", + "0xac5e5a941d567fa38fdbcfa8cf7f85bb304e3401c52d88752bcd516d1fa9bac4572534ea2205e38423c1df065990790f", + "0xac0bd594fb85a8d4fc26d6df0fa81f11919401f1ecf9168b891ec7f061a2d9368af99f7fd8d9b43b2ce361e7b8482159", + "0x83d92c69ca540d298fe80d8162a1c7af3fa9b49dfb69e85c1d136a3ec39fe419c9fa78e0bb6d96878771fbd37fe92e40", + "0xb35443ae8aa66c763c2db9273f908552fe458e96696b90e41dd509c17a5c04ee178e3490d9c6ba2dc0b8f793c433c134", + "0x923b2d25aa45b2e580ffd94cbb37dc8110f340f0f011217ee1bd81afb0714c0b1d5fb4db86006cdd2457563276f59c59", + "0x96c9125d38fca1a61ac21257b696f8ac3dae78def50285e44d90ea293d591d1c58f703540a7e4e99e070afe4646bbe15", + "0xb57946b2332077fbcdcb406b811779aefd54473b5559a163cd65cb8310679b7e2028aa55c12a1401fdcfcac0e6fae29a", + "0x845daedc5cf972883835d7e13c937b63753c2200324a3b8082a6c4abb4be06c5f7c629d4abe4bfaf1d80a1f073eb6ce6", + "0x91a55dfd0efefcd03dc6dacc64ec93b8d296cb83c0ee72400a36f27246e7f2a60e73b7b70ba65819e9cfb73edb7bd297", + "0x8874606b93266455fe8fdd25df9f8d2994e927460af06f2e97dd4d2d90db1e6b06d441b72c2e76504d753badca87fb37", + "0x8ee99e6d231274ff9252c0f4e84549da173041299ad1230929c3e3d32399731c4f20a502b4a307642cac9306ccd49d3c", + "0x8836497714a525118e20849d6933bb8535fb6f72b96337d49e3133d936999c90a398a740f42e772353b5f1c63581df6d", + "0xa6916945e10628f7497a6cdc5e2de113d25f7ade3e41e74d3de48ccd4fce9f2fa9ab69645275002e6f49399b798c40af", + "0x9597706983107eb23883e0812e1a2c58af7f3499d50c6e29b455946cb9812fde1aa323d9ed30d1c0ffd455abe32303cd", + "0xa24ee89f7f515cc33bdbdb822e7d5c1877d337f3b2162303cfc2dae028011c3a267c5cb4194afa63a4856a6e1c213448", + "0x8cd25315e4318801c2776824ae6e7d543cb85ed3bc2498ba5752df2e8142b37653cf9e60104d674be3aeb0a66912e97a", + "0xb5085ecbe793180b40dbeb879f4c976eaaccaca3a5246807dced5890e0ed24d35f3f86955e2460e14fb44ff5081c07ba", + "0x960188cc0b4f908633a6840963a6fa2205fc42c511c6c309685234911c5304ef4c304e3ae9c9c69daa2fb6a73560c256", + "0xa32d0a70bf15d569b4cda5aebe3e41e03c28bf99cdd34ffa6c5d58a097f322772acca904b3a47addb6c7492a7126ebac", + "0x977f72d06ad72d4aa4765e0f1f9f4a3231d9f030501f320fe7714cc5d329d08112789fa918c60dd7fdb5837d56bb7fc6", + "0x99fa038bb0470d45852bb871620d8d88520adb701712fcb1f278fed2882722b9e729e6cdce44c82caafad95e37d0e6f7", + "0xb855e8f4fc7634ada07e83b6c719a1e37acb06394bc8c7dcab7747a8c54e5df3943915f021364bd019fdea103864e55f", + "0x88bc2cd7458532e98c596ef59ea2cf640d7cc31b4c33cef9ed065c078d1d4eb49677a67de8e6229cc17ea48bace8ee5a", + "0xaaa78a3feaa836d944d987d813f9b9741afb076e6aca1ffa42682ab06d46d66e0c07b8f40b9dbd63e75e81efa1ef7b08", + "0xb7b080420cc4d808723b98b2a5b7b59c81e624ab568ecdfdeb8bf3aa151a581b6f56e983ef1b6f909661e25db40b0c69", + "0xabee85c462ac9a2c58e54f06c91b3e5cd8c5f9ab5b5deb602b53763c54826ed6deb0d6db315a8d7ad88733407e8d35e2", + "0x994d075c1527407547590df53e9d72dd31f037c763848d1662eebd4cefec93a24328c986802efa80e038cb760a5300f5", + "0xab8777640116dfb6678e8c7d5b36d01265dfb16321abbfc277da71556a34bb3be04bc4ae90124ed9c55386d2bfb3bda0", + "0x967e3a828bc59409144463bcf883a3a276b5f24bf3cbfdd7a42343348cba91e00b46ac285835a9b91eef171202974204", + "0x875a9f0c4ffe5bb1d8da5e3c8e41d0397aa6248422a628bd60bfae536a651417d4e8a7d2fb98e13f2dad3680f7bd86d3", + "0xacaa330c3e8f95d46b1880126572b238dbb6d04484d2cd4f257ab9642d8c9fc7b212188b9c7ac9e0fd135c520d46b1bf", + "0xaceb762edbb0f0c43dfcdb01ea7a1ac5918ca3882b1e7ebc4373521742f1ed5250d8966b498c00b2b0f4d13212e6dd0b", + "0x81d072b4ad258b3646f52f399bced97c613b22e7ad76373453d80b1650c0ca87edb291a041f8253b649b6e5429bb4cff", + "0x980a47d27416ac39c7c3a0ebe50c492f8c776ea1de44d5159ac7d889b6d554357f0a77f0e5d9d0ff41aae4369eba1fc2", + "0x8b4dfd5ef5573db1476d5e43aacfb5941e45d6297794508f29c454fe50ea622e6f068b28b3debe8635cf6036007de2e3", + "0xa60831559d6305839515b68f8c3bc7abbd8212cc4083502e19dd682d56ca37c9780fc3ce4ec2eae81ab23b221452dc57", + "0x951f6b2c1848ced9e8a2339c65918e00d3d22d3e59a0a660b1eca667d18f8430d737884e9805865ef3ed0fe1638a22d9", + "0xb02e38fe790b492aa5e89257c4986c9033a8b67010fa2add9787de857d53759170fdd67715ca658220b4e14b0ca48124", + "0xa51007e4346060746e6b0e4797fc08ef17f04a34fe24f307f6b6817edbb8ce2b176f40771d4ae8a60d6152cbebe62653", + "0xa510005b05c0b305075b27b243c9d64bcdce85146b6ed0e75a3178b5ff9608213f08c8c9246f2ca6035a0c3e31619860", + "0xaaff4ef27a7a23be3419d22197e13676d6e3810ceb06a9e920d38125745dc68a930f1741c9c2d9d5c875968e30f34ab5", + "0x864522a9af9857de9814e61383bebad1ba9a881696925a0ea6bfc6eff520d42c506bbe5685a9946ed710e889765be4a0", + "0xb63258c080d13f3b7d5b9f3ca9929f8982a6960bdb1b0f8676f4dca823971601672f15e653917bf5d3746bb220504913", + "0xb51ce0cb10869121ae310c7159ee1f3e3a9f8ad498827f72c3d56864808c1f21fa2881788f19ece884d3f705cd7bd0c5", + "0x95d9cecfc018c6ed510e441cf84c712d9909c778c16734706c93222257f64dcd2a9f1bd0b400ca271e22c9c487014274", + "0x8beff4d7d0140b86380ff4842a9bda94c2d2be638e20ac68a4912cb47dbe01a261857536375208040c0554929ced1ddc", + "0x891ff49258749e2b57c1e9b8e04b12c77d79c3308b1fb615a081f2aacdfb4b39e32d53e069ed136fdbd43c53b87418fa", + "0x9625cad224e163d387738825982d1e40eeff35fe816d10d7541d15fdc4d3eee48009090f3faef4024b249205b0b28f72", + "0x8f3947433d9bd01aa335895484b540a9025a19481a1c40b4f72dd676bfcf332713714fd4010bde936eaf9470fd239ed0", + "0xa00ec2d67789a7054b53f0e858a8a232706ccc29a9f3e389df7455f1a51a2e75801fd78469a13dbc25d28399ae4c6182", + "0xa3f65884506d4a62b8775a0ea0e3d78f5f46bc07910a93cd604022154eabdf1d73591e304d61edc869e91462951975e1", + "0xa14eef4fd5dfac311713f0faa9a60415e3d30b95a4590cbf95f2033dffb4d16c02e7ceff3dcd42148a4e3bc49cce2dd4", + "0x8afa11c0eef3c540e1e3460bc759bb2b6ea90743623f88e62950c94e370fe4fd01c22b6729beba4dcd4d581198d9358f", + "0xafb05548a69f0845ffcc5f5dc63e3cdb93cd270f5655173b9a950394b0583663f2b7164ba6df8d60c2e775c1d9f120af", + "0x97f179e01a947a906e1cbeafa083960bc9f1bade45742a3afee488dfb6011c1c6e2db09a355d77f5228a42ccaa7bdf8e", + "0x8447fca4d35f74b3efcbd96774f41874ca376bf85b79b6e66c92fa3f14bdd6e743a051f12a7fbfd87f319d1c6a5ce217", + "0xa57ca39c23617cd2cf32ff93b02161bd7baf52c4effb4679d9d5166406e103bc8f3c6b5209e17c37dbb02deb8bc72ddd", + "0x9667c7300ff80f0140be002b0e36caab07aaee7cce72679197c64d355e20d96196acaf54e06e1382167d081fe6f739c1", + "0x828126bb0559ce748809b622677267ca896fa2ee76360fd2c02990e6477e06a667241379ca7e65d61a5b64b96d7867de", + "0x8b8835dea6ba8cf61c91f01a4b3d2f8150b687a4ee09b45f2e5fc8f80f208ae5d142d8e3a18153f0722b90214e60c5a7", + "0xa98e8ff02049b4da386e3ee93db23bbb13dfeb72f1cfde72587c7e6d962780b7671c63e8ac3fbaeb1a6605e8d79e2f29", + "0x87a4892a0026d7e39ef3af632172b88337cb03669dea564bcdb70653b52d744730ebb5d642e20cb627acc9dbb547a26b", + "0x877352a22fc8052878a57effc159dac4d75fe08c84d3d5324c0bab6d564cdf868f33ceee515eee747e5856b62cfa0cc7", + "0x8b801ba8e2ff019ee62f64b8cb8a5f601fc35423eb0f9494b401050103e1307dc584e4e4b21249cd2c686e32475e96c3", + "0xa9e7338d6d4d9bfec91b2af28a8ed13b09415f57a3a00e5e777c93d768fdb3f8e4456ae48a2c6626b264226e911a0e28", + "0x99c05fedf40ac4726ed585d7c1544c6e79619a0d3fb6bda75a08c7f3c0008e8d5e19ed4da48de3216135f34a15eba17c", + "0xa61cce8a1a8b13a4a650fdbec0eeea8297c352a8238fb7cac95a0df18ed16ee02a3daa2de108fa122aca733bd8ad7855", + "0xb97f37da9005b440b4cb05870dd881bf8491fe735844f2d5c8281818583b38e02286e653d9f2e7fa5e74c3c3eb616540", + "0xa72164a8554da8e103f692ac5ebb4aece55d5194302b9f74b6f2a05335b6e39beede0bf7bf8c5bfd4d324a784c5fb08c", + "0xb87e8221c5341cd9cc8bb99c10fe730bc105550f25ed4b96c0d45e6142193a1b2e72f1b3857373a659b8c09be17b3d91", + "0xa41fb1f327ef91dcb7ac0787918376584890dd9a9675c297c45796e32d6e5985b12f9b80be47fc3a8596c245f419d395", + "0x90dafa3592bdbb3465c92e2a54c2531822ba0459d45d3e7a7092fa6b823f55af28357cb51896d4ec2d66029c82f08e26", + "0xa0a9adc872ebc396557f484f1dd21954d4f4a21c4aa5eec543f5fa386fe590839735c01f236574f7ff95407cd12de103", + "0xb8c5c940d58be7538acf8672852b5da3af34f82405ef2ce8e4c923f1362f97fc50921568d0fd2fe846edfb0823e62979", + "0x85aaf06a8b2d0dac89dafd00c28533f35dbd074978c2aaa5bef75db44a7b12aeb222e724f395513b9a535809a275e30b", + "0x81f3cbe82fbc7028c26a6c1808c604c63ba023a30c9f78a4c581340008dbda5ec07497ee849a2183fcd9124f7936af32", + "0xa11ac738de75fd60f15a34209d3825d5e23385796a4c7fc5931822f3f380af977dd0f7b59fbd58eed7777a071e21b680", + "0x85a279c493de03db6fa6c3e3c1b1b29adc9a8c4effc12400ae1128da8421954fa8b75ad19e5388fe4543b76fb0812813", + "0x83a217b395d59ab20db6c4adb1e9713fc9267f5f31a6c936042fe051ce8b541f579442f3dcf0fa16b9e6de9fd3518191", + "0x83a0b86e7d4ed8f9ccdc6dfc8ff1484509a6378fa6f09ed908e6ab9d1073f03011dc497e14304e4e3d181b57de06a5ab", + "0xa63ad69c9d25704ce1cc8e74f67818e5ed985f8f851afa8412248b2df5f833f83b95b27180e9e7273833ed0d07113d3b", + "0x99b1bc2021e63b561fe44ddd0af81fcc8627a91bfeecbbc989b642bc859abc0c8d636399701aad7bbaf6a385d5f27d61", + "0xb53434adb66f4a807a6ad917c6e856321753e559b1add70824e5c1e88191bf6993fccb9b8b911fc0f473fb11743acacd", + "0x97ed3b9e6fb99bf5f945d4a41f198161294866aa23f2327818cdd55cb5dc4c1a8eff29dd8b8d04902d6cd43a71835c82", + "0xb1e808260e368a18d9d10bdea5d60223ba1713b948c782285a27a99ae50cc5fc2c53d407de07155ecc16fb8a36d744a0", + "0xa3eb4665f18f71833fec43802730e56b3ee5a357ea30a888ad482725b169d6f1f6ade6e208ee081b2e2633079b82ba7d", + "0xab8beb2c8353fc9f571c18fdd02bdb977fc883313469e1277b0372fbbb33b80dcff354ca41de436d98d2ed710faa467e", + "0xaa9071cfa971e4a335a91ad634c98f2be51544cb21f040f2471d01bb97e1df2277ae1646e1ea8f55b7ba9f5c8c599b39", + "0x80b7dbfdcaf40f0678012acc634eba44ea51181475180d9deb2050dc4f2de395289edd0223018c81057ec79b04b04c49", + "0x89623d7f6cb17aa877af14de842c2d4ab7fd576d61ddd7518b5878620a01ded40b6010de0da3cdf31d837eecf30e9847", + "0xa773bb024ae74dd24761f266d4fb27d6fd366a8634febe8235376b1ae9065c2fe12c769f1d0407867dfbe9f5272c352f", + "0x8455a561c3aaa6ba64c881a5e13921c592b3a02e968f4fb24a2243c36202795d0366d9cc1a24e916f84d6e158b7aeac7", + "0x81d8bfc4b283cf702a40b87a2b96b275bdbf0def17e67d04842598610b67ea08c804d400c3e69fa09ea001eaf345b276", + "0xb8f8f82cb11fea1c99467013d7e167ff03deb0c65a677fab76ded58826d1ba29aa7cf9fcd7763615735ea3ad38e28719", + "0x89a6a04baf9cccc1db55179e1650b1a195dd91fb0aebc197a25143f0f393524d2589975e3fbfc2547126f0bced7fd6f2", + "0xb81b2162df045390f04df07cbd0962e6b6ca94275a63edded58001a2f28b2ae2af2c7a6cba4ecd753869684e77e7e799", + "0xa3757f722776e50de45c62d9c4a2ee0f5655a512344c4cbec542d8045332806568dd626a719ef21a4eb06792ca70f204", + "0x8c5590df96ec22179a4e8786de41beb44f987a1dcc508eb341eecbc0b39236fdfad47f108f852e87179ccf4e10091e59", + "0x87502f026ed4e10167419130b88c3737635c5b9074c364e1dd247cef5ef0fc064b4ae99b187e33301e438bbd2fe7d032", + "0xaf925a2165e980ced620ff12289129fe17670a90ae0f4db9d4b39bd887ccb1f5d2514ac9ecf910f6390a8fc66bd5be17", + "0x857fca899828cf5c65d26e3e8a6e658542782fc72762b3b9c73514919f83259e0f849a9d4838b40dc905fe43024d0d23", + "0x87ffebdbfb69a9e1007ebac4ffcb4090ff13705967b73937063719aa97908986effcb7262fdadc1ae0f95c3690e3245d", + "0xa9ff6c347ac6f4c6ab993b748802e96982eaf489dc69032269568412fc9a79e7c2850dfc991b28211b3522ee4454344b", + "0xa65b3159df4ec48bebb67cb3663cd744027ad98d970d620e05bf6c48f230fa45bf17527fe726fdf705419bb7a1bb913e", + "0x84b97b1e6408b6791831997b03cd91f027e7660fd492a93d95daafe61f02427371c0e237c75706412f442991dfdff989", + "0xab761c26527439b209af0ae6afccd9340bbed5fbe098734c3145b76c5d2cd7115d9227b2eb523882b7317fbb09180498", + "0xa0479a8da06d7a69c0b0fee60df4e691c19c551f5e7da286dab430bfbcabf31726508e20d26ea48c53365a7f00a3ad34", + "0xa732dfc9baa0f4f40b5756d2e8d8937742999623477458e0bc81431a7b633eefc6f53b3b7939fe0a020018549c954054", + "0x901502436a1169ba51dc479a5abe7c8d84e0943b16bc3c6a627b49b92cd46263c0005bc324c67509edd693f28e612af1", + "0xb627aee83474e7f84d1bab9b7f6b605e33b26297ac6bbf52d110d38ba10749032bd551641e73a383a303882367af429b", + "0x95108866745760baef4a46ef56f82da6de7e81c58b10126ebd2ba2cd13d339f91303bf2fb4dd104a6956aa3b13739503", + "0x899ed2ade37236cec90056f3569bc50f984f2247792defafcceb49ad0ca5f6f8a2f06573705300e07f0de0c759289ff5", + "0xa9f5eee196d608efe4bcef9bf71c646d27feb615e21252cf839a44a49fd89da8d26a758419e0085a05b1d59600e2dc42", + "0xb36c6f68fed6e6c85f1f4a162485f24817f2843ec5cbee45a1ebfa367d44892e464949c6669f7972dc7167af08d55d25", + "0xaaaede243a9a1b6162afbc8f571a52671a5a4519b4062e3f26777664e245ba873ed13b0492c5dbf0258c788c397a0e9e", + "0x972b4fb39c31cbe127bf9a32a5cc10d621ebdd9411df5e5da3d457f03b2ab2cd1f6372d8284a4a9400f0b06ecdbfd38e", + "0x8f6ca1e110e959a4b1d9a5ce5f212893cec21db40d64d5ac4d524f352d72198f923416a850bf845bc5a22a79c0ea2619", + "0xa0f3c93b22134f66f04b2553a53b738644d1665ceb196b8494b315a4c28236fb492017e4a0de4224827c78e42f9908b7", + "0x807fb5ee74f6c8735b0b5ca07e28506214fe4047dbeb00045d7c24f7849e98706aea79771241224939cb749cf1366c7d", + "0x915eb1ff034224c0b645442cdb7d669303fdc00ca464f91aaf0b6fde0b220a3a74ff0cb043c26c9f3a5667b3fdaa9420", + "0x8fda6cef56ed33fefffa9e6ac8e6f76b1af379f89761945c63dd448801f7bb8ca970504a7105fac2f74f652ccff32327", + "0x87380cffdcffb1d0820fa36b63cc081e72187f86d487315177d4d04da4533eb19a0e2ff6115ceab528887819c44a5164", + "0x8cd89e03411a18e7f16f968b89fb500c36d47d229f6487b99e62403a980058db5925ce249206743333538adfad168330", + "0x974451b1df33522ce7056de9f03e10c70bf302c44b0741a59df3d6877d53d61a7394dcee1dd46e013d7cb9d73419c092", + "0x98c35ddf645940260c490f384a49496a7352bb8e3f686feed815b1d38f59ded17b1ad6e84a209e773ed08f7b8ff1e4c2", + "0x963f386cf944bb9b2ddebb97171b64253ea0a2894ac40049bdd86cda392292315f3a3d490ca5d9628c890cfb669f0acb", + "0x8d507712152babd6d142ee682638da8495a6f3838136088df9424ef50d5ec28d815a198c9a4963610b22e49b4cdf95e9", + "0x83d4bc6b0be87c8a4f1e9c53f257719de0c73d85b490a41f7420e777311640937320557ff2f1d9bafd1daaa54f932356", + "0x82f5381c965b7a0718441131c4d13999f4cdce637698989a17ed97c8ea2e5bdb5d07719c5f7be8688edb081b23ede0f4", + "0xa6ebecab0b72a49dfd01d69fa37a7f74d34fb1d4fef0aa10e3d6fceb9eccd671225c230af89f6eb514250e41a5f91f52", + "0x846d185bdad6e11e604df7f753b7a08a28b643674221f0e750ebdb6b86ec584a29c869e131bca868972a507e61403f6a", + "0x85a98332292acb744bd1c0fd6fdcf1f889a78a2c9624d79413ffa194cc8dfa7821a4b60cde8081d4b5f71f51168dd67f", + "0x8f7d97c3b4597880d73200d074eb813d95432306e82dafc70b580b8e08cb8098b70f2d07b4b3ac6a4d77e92d57035031", + "0x8185439c8751e595825d7053518cbe121f191846a38d4dbcb558c3f9d7a3104f3153401adaaaf27843bbe2edb504bfe3", + "0xb3c00d8ece1518fca6b1215a139b0a0e26d9cba1b3a424f7ee59f30ce800a5db967279ed60958dd1f3ee69cf4dd1b204", + "0xa2e6cb6978e883f9719c3c0d44cfe8de0cc6f644b98f98858433bea8bbe7b612c8aca5952fccce4f195f9d54f9722dc2", + "0x99663087e3d5000abbec0fbda4e7342ec38846cc6a1505191fb3f1a337cb369455b7f8531a6eb8b0f7b2c4baf83cbe2b", + "0xab0836c6377a4dbc7ca6a4d6cf021d4cd60013877314dd05f351706b128d4af6337711ed3443cb6ca976f40d74070a9a", + "0x87abfd5126152fd3bac3c56230579b489436755ea89e0566aa349490b36a5d7b85028e9fb0710907042bcde6a6f5d7e3", + "0x974ba1033f75f60e0cf7c718a57ae1da3721cf9d0fb925714c46f027632bdd84cd9e6de4cf4d00bc55465b1c5ebb7384", + "0xa607b49d73689ac64f25cec71221d30d53e781e1100d19a2114a21da6507a60166166369d860bd314acb226596525670", + "0xa7c2b0b915d7beba94954f2aa7dd08ec075813661e2a3ecca5d28a0733e59583247fed9528eb28aba55b972cdbaf06eb", + "0xb8b3123e44128cc8efbe3270f2f94e50ca214a4294c71c3b851f8cbb70cb67fe9536cf07d04bf7fe380e5e3a29dd3c15", + "0xa59a07e343b62ad6445a0859a32b58c21a593f9ddbfe52049650f59628c93715aa1f4e1f45b109321756d0eeec8a5429", + "0x94f51f8a4ed18a6030d0aaa8899056744bd0e9dc9ac68f62b00355cddab11da5da16798db75f0bfbce0e5bdfe750c0b6", + "0x97460a97ca1e1fa5ce243b81425edc0ec19b7448e93f0b55bc9785eedeeafe194a3c8b33a61a5c72990edf375f122777", + "0x8fa859a089bc17d698a7ee381f37ce9beadf4e5b44fce5f6f29762bc04f96faff5d58c48c73631290325f05e9a1ecf49", + "0xabdf38f3b20fc95eff31de5aa9ef1031abfa48f1305ee57e4d507594570401503476d3bcc493838fc24d6967a3082c7f", + "0xb8914bfb82815abb86da35c64d39ab838581bc0bf08967192697d9663877825f2b9d6fbdcf9b410463482b3731361aef", + "0xa8187f9d22b193a5f578999954d6ec9aa9b32338ccadb8a3e1ce5bad5ea361d69016e1cdfac44e9d6c54e49dd88561b9", + "0xaac262cb7cba7fd62c14daa7b39677cabc1ef0947dd06dd89cac8570006a200f90d5f0353e84f5ff03179e3bebe14231", + "0xa630ef5ece9733b8c46c0a2df14a0f37647a85e69c63148e79ffdcc145707053f9f9d305c3f1cf3c7915cb46d33abd07", + "0xb102c237cb2e254588b6d53350dfda6901bd99493a3fbddb4121d45e0b475cf2663a40d7b9a75325eda83e4ba1e68cb3", + "0x86a930dd1ddcc16d1dfa00aa292cb6c2607d42c367e470aa920964b7c17ab6232a7108d1c2c11fc40fb7496547d0bbf8", + "0xa832fdc4500683e72a96cce61e62ac9ee812c37fe03527ad4cf893915ca1962cee80e72d4f82b20c8fc0b764376635a1", + "0x88ad985f448dabb04f8808efd90f273f11f5e6d0468b5489a1a6a3d77de342992a73eb842d419034968d733f101ff683", + "0x98a8538145f0d86f7fbf9a81c9140f6095c5bdd8960b1c6f3a1716428cd9cca1bf8322e6d0af24e6169abcf7df2b0ff6", + "0x9048c6eba5e062519011e177e955a200b2c00b3a0b8615bdecdebc217559d41058d3315f6d05617be531ef0f6aef0e51", + "0x833bf225ab6fc68cdcacf1ec1b50f9d05f5410e6cdcd8d56a3081dc2be8a8d07b81534d1ec93a25c2e270313dfb99e3b", + "0xa84bcd24c3da5e537e64a811b93c91bfc84d7729b9ead7f79078989a6eb76717d620c1fad17466a0519208651e92f5ff", + "0xb7cdd0a3fbd79aed93e1b5a44ca44a94e7af5ed911e4492f332e3a5ed146c7286bde01b52276a2fcc02780d2109874dd", + "0x8a19a09854e627cb95750d83c20c67442b66b35896a476358f993ba9ac114d32c59c1b3d0b8787ee3224cf3888b56c64", + "0xa9abd5afb8659ee52ada8fa5d57e7dd355f0a7350276f6160bec5fbf70d5f99234dd179eb221c913e22a49ec6d267846", + "0x8c13c4274c0d30d184e73eaf812200094bbbd57293780bdadbceb262e34dee5b453991e7f37c7333a654fc71c69d6445", + "0xa4320d73296ff8176ce0127ca1921c450e2a9c06eff936681ebaffb5a0b05b17fded24e548454de89aca2dcf6d7a9de4", + "0xb2b8b3e15c1f645f07783e5628aba614e60157889db41d8161d977606788842b67f83f361eae91815dc0abd84e09abd5", + "0xad26c3aa35ddfddc15719b8bb6c264aaec7065e88ac29ba820eb61f220fef451609a7bb037f3722d022e6c86e4f1dc88", + "0xb8615bf43e13ae5d7b8dd903ce37190800cd490f441c09b22aa29d7a29ed2c0417b7a08ead417868f1de2589deaadd80", + "0x8d3425e1482cd1e76750a76239d33c06b3554c3c3c87c15cb7ab58b1cee86a4c5c4178b44e23f36928365a1b484bde02", + "0x806893a62e38c941a7dd6f249c83af16596f69877cc737d8f73f6b8cd93cbc01177a7a276b2b8c6b0e5f2ad864db5994", + "0x86618f17fa4b0d65496b661bbb5ba3bc3a87129d30a4b7d4f515b904f4206ca5253a41f49fd52095861e5e065ec54f21", + "0x9551915da1304051e55717f4c31db761dcdcf3a1366c89a4af800a9e99aca93a357bf928307f098e62b44a02cb689a46", + "0x8f79c4ec0ec1146cb2a523b52fe33def90d7b5652a0cb9c2d1c8808a32293e00aec6969f5b1538e3a94cd1efa3937f86", + "0xa0c03e329a707300081780f1e310671315b4c6a4cedcb29697aedfabb07a9d5df83f27b20e9c44cf6b16e39d9ded5b98", + "0x86a7cfa7c8e7ce2c01dd0baec2139e97e8e090ad4e7b5f51518f83d564765003c65968f85481bbb97cb18f005ccc7d9f", + "0xa33811770c6dfda3f7f74e6ad0107a187fe622d61b444bbd84fd7ef6e03302e693b093df76f6ab39bb4e02afd84a575a", + "0x85480f5c10d4162a8e6702b5e04f801874d572a62a130be94b0c02b58c3c59bdcd48cd05f0a1c2839f88f06b6e3cd337", + "0x8e181011564b17f7d787fe0e7f3c87f6b62da9083c54c74fd6c357a1f464c123c1d3d8ade3cf72475000b464b14e2be3", + "0x8ee178937294b8c991337e0621ab37e9ffa4ca2bdb3284065c5e9c08aad6785d50cf156270ff9daf9a9127289710f55b", + "0x8bd1e8e2d37379d4b172f1aec96f2e41a6e1393158d7a3dbd9a95c8dd4f8e0b05336a42efc11a732e5f22b47fc5c271d", + "0x8f3da353cd487c13136a85677de8cedf306faae0edec733cf4f0046f82fa4639db4745b0095ff33a9766aba50de0cbcf", + "0x8d187c1e97638df0e4792b78e8c23967dac43d98ea268ca4aabea4e0fa06cb93183fd92d4c9df74118d7cc27bf54415e", + "0xa4c992f08c2f8bac0b74b3702fb0c75c9838d2ce90b28812019553d47613c14d8ce514d15443159d700b218c5a312c49", + "0xa6fd1874034a34c3ea962a316c018d9493d2b3719bb0ec4edbc7c56b240802b2228ab49bee6f04c8a3e9f6f24a48c1c2", + "0xb2efed8e799f8a15999020900dc2c58ece5a3641c90811b86a5198e593d7318b9d53b167818ccdfbe7df2414c9c34011", + "0x995ff7de6181ddf95e3ead746089c6148da3508e4e7a2323c81785718b754d356789b902e7e78e2edc6b0cbd4ff22c78", + "0x944073d24750a9068cbd020b834afc72d2dde87efac04482b3287b40678ad07588519a4176b10f2172a2c463d063a5cd", + "0x99db4b1bb76475a6fd75289986ef40367960279524378cc917525fb6ba02a145a218c1e9caeb99332332ab486a125ac0", + "0x89fce4ecd420f8e477af4353b16faabb39e063f3f3c98fde2858b1f2d1ef6eed46f0975a7c08f233b97899bf60ccd60a", + "0x8c09a4f07a02b80654798bc63aada39fd638d3e3c4236ccd8a5ca280350c31e4a89e5f4c9aafb34116e71da18c1226b8", + "0x85325cfa7ded346cc51a2894257eab56e7488dbff504f10f99f4cd2b630d913003761a50f175ed167e8073f1b6b63fb0", + "0xb678b4fbec09a8cc794dcbca185f133578f29e354e99c05f6d07ac323be20aecb11f781d12898168e86f2e0f09aca15e", + "0xa249cfcbca4d9ba0a13b5f6aac72bf9b899adf582f9746bb2ad043742b28915607467eb794fca3704278f9136f7642be", + "0x9438e036c836a990c5e17af3d78367a75b23c37f807228362b4d13e3ddcb9e431348a7b552d09d11a2e9680704a4514f", + "0x925ab70450af28c21a488bfb5d38ac994f784cf249d7fd9ad251bb7fd897a23e23d2528308c03415074d43330dc37ef4", + "0xa290563904d5a8c0058fc8330120365bdd2ba1fdbaef7a14bc65d4961bb4217acfaed11ab82669e359531f8bf589b8db", + "0xa7e07a7801b871fc9b981a71e195a3b4ba6b6313bc132b04796a125157e78fe5c11a3a46cf731a255ac2d78a4ae78cd0", + "0xb26cd2501ee72718b0eebab6fb24d955a71f363f36e0f6dff0ab1d2d7836dab88474c0cef43a2cc32701fca7e82f7df3", + "0xa1dc3b6c968f3de00f11275092290afab65b2200afbcfa8ddc70e751fa19dbbc300445d6d479a81bda3880729007e496", + "0xa9bc213e28b630889476a095947d323b9ac6461dea726f2dc9084473ae8e196d66fb792a21905ad4ec52a6d757863e7d", + "0xb25d178df8c2df8051e7c888e9fa677fde5922e602a95e966db9e4a3d6b23ce043d7dc48a5b375c6b7c78e966893e8c3", + "0xa1c8d88d72303692eaa7adf68ea41de4febec40cc14ae551bb4012afd786d7b6444a3196b5d9d5040655a3366d96b7cd", + "0xb22bd44f9235a47118a9bbe2ba5a2ba9ec62476061be2e8e57806c1a17a02f9a51403e849e2e589520b759abd0117683", + "0xb8add766050c0d69fe81d8d9ea73e1ed05f0135d093ff01debd7247e42dbb86ad950aceb3b50b9af6cdc14ab443b238f", + "0xaf2cf95f30ef478f018cf81d70d47d742120b09193d8bb77f0d41a5d2e1a80bfb467793d9e2471b4e0ad0cb2c3b42271", + "0x8af5ef2107ad284e246bb56e20fef2a255954f72de791cbdfd3be09f825298d8466064f3c98a50496c7277af32b5c0bc", + "0x85dc19558572844c2849e729395a0c125096476388bd1b14fa7f54a7c38008fc93e578da3aac6a52ff1504d6ca82db05", + "0xae8c9b43c49572e2e166d704caf5b4b621a3b47827bb2a3bcd71cdc599bba90396fd9a405261b13e831bb5d44c0827d7", + "0xa7ba7efede25f02e88f6f4cbf70643e76784a03d97e0fbd5d9437c2485283ad7ca3abb638a5f826cd9f6193e5dec0b6c", + "0x94a9d122f2f06ef709fd8016fd4b712d88052245a65a301f5f177ce22992f74ad05552b1f1af4e70d1eac62cef309752", + "0x82d999b3e7cf563833b8bc028ff63a6b26eb357dfdb3fd5f10e33a1f80a9b2cfa7814d871b32a7ebfbaa09e753e37c02", + "0xaec6edcde234df502a3268dd2c26f4a36a2e0db730afa83173f9c78fcb2b2f75510a02b80194327b792811caefda2725", + "0x94c0bfa66c9f91d462e9194144fdd12d96f9bbe745737e73bab8130607ee6ea9d740e2cfcbbd00a195746edb6369ee61", + "0xab7573dab8c9d46d339e3f491cb2826cabe8b49f85f1ede78d845fc3995537d1b4ab85140b7d0238d9c24daf0e5e2a7e", + "0x87e8b16832843251fe952dadfd01d41890ed4bb4b8fa0254550d92c8cced44368225eca83a6c3ad47a7f81ff8a80c984", + "0x9189d2d9a7c64791b19c0773ad4f0564ce6bea94aa275a917f78ad987f150fdb3e5e26e7fef9982ac184897ecc04683f", + "0xb3661bf19e2da41415396ae4dd051a9272e8a2580b06f1a1118f57b901fa237616a9f8075af1129af4eabfefedbe2f1c", + "0xaf43c86661fb15daf5d910a4e06837225e100fb5680bd3e4b10f79a2144c6ec48b1f8d6e6b98e067d36609a5d038889a", + "0x82ac0c7acaa83ddc86c5b4249aae12f28155989c7c6b91e5137a4ce05113c6cbc16f6c44948b0efd8665362d3162f16a", + "0x8f268d1195ab465beeeb112cd7ffd5d5548559a8bc01261106d3555533fc1971081b25558d884d552df0db1cddda89d8", + "0x8ef7caa5521f3e037586ce8ac872a4182ee20c7921c0065ed9986c047e3dda08294da1165f385d008b40d500f07d895f", + "0x8c2f98f6880550573fad46075d3eba26634b5b025ce25a0b4d6e0193352c8a1f0661064027a70fe8190b522405f9f4e3", + "0xb7653f353564feb164f0f89ec7949da475b8dad4a4d396d252fc2a884f6932d027b7eb2dc4d280702c74569319ed701a", + "0xa026904f4066333befd9b87a8fad791d014096af60cdd668ef919c24dbe295ff31f7a790e1e721ba40cf5105abca67f4", + "0x988f982004ada07a22dd345f2412a228d7a96b9cae2c487de42e392afe1e35c2655f829ce07a14629148ce7079a1f142", + "0x9616add009067ed135295fb74d5b223b006b312bf14663e547a0d306694ff3a8a7bb9cfc466986707192a26c0bce599f", + "0xad4c425de9855f6968a17ee9ae5b15e0a5b596411388cf976df62ecc6c847a6e2ddb2cea792a5f6e9113c2445dba3e5c", + "0xb698ac9d86afa3dc69ff8375061f88e3b0cff92ff6dfe747cebaf142e813c011851e7a2830c10993b715e7fd594604a9", + "0xa386fa189847bb3b798efca917461e38ead61a08b101948def0f82cd258b945ed4d45b53774b400af500670149e601b7", + "0x905c95abda2c68a6559d8a39b6db081c68cef1e1b4be63498004e1b2f408409be9350b5b5d86a30fd443e2b3e445640a", + "0x9116dade969e7ce8954afcdd43e5cab64dc15f6c1b8da9d2d69de3f02ba79e6c4f6c7f54d6bf586d30256ae405cd1e41", + "0xa3084d173eacd08c9b5084a196719b57e47a0179826fda73466758235d7ecdb87cbcf097bd6b510517d163a85a7c7edd", + "0x85bb00415ad3c9be99ff9ba83672cc59fdd24356b661ab93713a3c8eab34e125d8867f628a3c3891b8dc056e69cd0e83", + "0x8d58541f9f39ed2ee4478acce5d58d124031338ec11b0d55551f00a5a9a6351faa903a5d7c132dc5e4bb026e9cbd18e4", + "0xa622adf72dc250e54f672e14e128c700166168dbe0474cecb340da175346e89917c400677b1bc1c11fcc4cc26591d9db", + "0xb3f865014754b688ca8372e8448114fff87bf3ca99856ab9168894d0c4679782c1ced703f5b74e851b370630f5e6ee86", + "0xa7e490b2c40c2446fcd91861c020da9742c326a81180e38110558bb5d9f2341f1c1885e79b364e6419023d1cbdc47380", + "0xb3748d472b1062e54572badbb8e87ac36534407f74932e7fc5b8392d008e8e89758f1671d1e4d30ab0fa40551b13bb5e", + "0x89898a5c5ec4313aabc607b0049fd1ebad0e0c074920cf503c9275b564d91916c2c446d3096491c950b7af3ac5e4b0ed", + "0x8eb8c83fef2c9dd30ea44e286e9599ec5c20aba983f702e5438afe2e5b921884327ad8d1566c72395587efac79ca7d56", + "0xb92479599e806516ce21fb0bd422a1d1d925335ebe2b4a0a7e044dd275f30985a72b97292477053ac5f00e081430da80", + "0xa34ae450a324fe8a3c25a4d653a654f9580ed56bbea213b8096987bbad0f5701d809a17076435e18017fea4d69f414bc", + "0x81381afe6433d62faf62ea488f39675e0091835892ecc238e02acf1662669c6d3962a71a3db652f6fe3bc5f42a0e5dc5", + "0xa430d475bf8580c59111103316fe1aa79c523ea12f1d47a976bbfae76894717c20220e31cf259f08e84a693da6688d70", + "0xb842814c359754ece614deb7d184d679d05d16f18a14b288a401cef5dad2cf0d5ee90bad487b80923fc5573779d4e4e8", + "0x971d9a2627ff2a6d0dcf2af3d895dfbafca28b1c09610c466e4e2bff2746f8369de7f40d65b70aed135fe1d72564aa88", + "0x8f4ce1c59e22b1ce7a0664caaa7e53735b154cfba8d2c5cc4159f2385843de82ab58ed901be876c6f7fce69cb4130950", + "0x86cc9dc321b6264297987000d344fa297ef45bcc2a4df04e458fe2d907ad304c0ea2318e32c3179af639a9a56f3263cf", + "0x8229e0876dfe8f665c3fb19b250bd89d40f039bbf1b331468b403655be7be2e104c2fd07b9983580c742d5462ca39a43", + "0x99299d73066e8eb128f698e56a9f8506dfe4bd014931e86b6b487d6195d2198c6c5bf15cccb40ccf1f8ddb57e9da44a2", + "0xa3a3be37ac554c574b393b2f33d0a32a116c1a7cfeaf88c54299a4da2267149a5ecca71f94e6c0ef6e2f472b802f5189", + "0xa91700d1a00387502cdba98c90f75fbc4066fefe7cc221c8f0e660994c936badd7d2695893fde2260c8c11d5bdcdd951", + "0x8e03cae725b7f9562c5c5ab6361644b976a68bada3d7ca508abca8dfc80a469975689af1fba1abcf21bc2a190dab397d", + "0xb01461ad23b2a8fa8a6d241e1675855d23bc977dbf4714add8c4b4b7469ccf2375cec20e80cedfe49361d1a30414ac5b", + "0xa2673bf9bc621e3892c3d7dd4f1a9497f369add8cbaa3472409f4f86bd21ac67cfac357604828adfee6ada1835365029", + "0xa042dff4bf0dfc33c178ba1b335e798e6308915128de91b12e5dbbab7c4ac8d60a01f6aea028c3a6d87b9b01e4e74c01", + "0x86339e8a75293e4b3ae66b5630d375736b6e6b6b05c5cda5e73fbf7b2f2bd34c18a1d6cefede08625ce3046e77905cb8", + "0xaf2ebe1b7d073d03e3d98bc61af83bf26f7a8c130fd607aa92b75db22d14d016481b8aa231e2c9757695f55b7224a27f", + "0xa00ee882c9685e978041fd74a2c465f06e2a42ffd3db659053519925be5b454d6f401e3c12c746e49d910e4c5c9c5e8c", + "0x978a781c0e4e264e0dad57e438f1097d447d891a1e2aa0d5928f79a9d5c3faae6f258bc94fdc530b7b2fa6a9932bb193", + "0xaa4b7ce2e0c2c9e9655bf21e3e5651c8503bce27483017b0bf476be743ba06db10228b3a4c721219c0779747f11ca282", + "0xb003d1c459dacbcf1a715551311e45d7dbca83a185a65748ac74d1800bbeaba37765d9f5a1a221805c571910b34ebca8", + "0x95b6e531b38648049f0d19de09b881baa1f7ea3b2130816b006ad5703901a05da57467d1a3d9d2e7c73fb3f2e409363c", + "0xa6cf9c06593432d8eba23a4f131bb7f72b9bd51ab6b4b772a749fe03ed72b5ced835a349c6d9920dba2a39669cb7c684", + "0xaa3d59f6e2e96fbb66195bc58c8704e139fa76cd15e4d61035470bd6e305db9f98bcbf61ac1b95e95b69ba330454c1b3", + "0xb57f97959c208361de6d7e86dff2b873068adb0f158066e646f42ae90e650079798f165b5cd713141cd3a2a90a961d9a", + "0xa76ee8ed9052f6a7a8c69774bb2597be182942f08115baba03bf8faaeaee526feba86120039fe8ca7b9354c3b6e0a8e6", + "0x95689d78c867724823f564627d22d25010f278674c6d2d0cdb10329169a47580818995d1d727ce46c38a1e47943ebb89", + "0xab676d2256c6288a88e044b3d9ffd43eb9d5aaee00e8fc60ac921395fb835044c71a26ca948e557fed770f52d711e057", + "0x96351c72785c32e5d004b6f4a1259fb8153d631f0c93fed172f18e8ba438fbc5585c1618deeabd0d6d0b82173c2e6170", + "0x93dd8d3db576418e22536eba45ab7f56967c6c97c64260d6cddf38fb19c88f2ec5cd0e0156f50e70855eee8a2b879ffd", + "0xad6ff16f40f6de3d7a737f8e6cebd8416920c4ff89dbdcd75eabab414af9a6087f83ceb9aff7680aa86bff98bd09c8cc", + "0x84de53b11671abc9c38710e19540c5c403817562aeb22a88404cdaff792c1180f717dbdfe8f54940c062c4d032897429", + "0x872231b9efa1cdd447b312099a5c164c560440a9441d904e70f5abfc3b2a0d16be9a01aca5e0a2599a61e19407587e3d", + "0x88f44ac27094a2aa14e9dc40b099ee6d68f97385950f303969d889ee93d4635e34dff9239103bdf66a4b7cbba3e7eb7a", + "0xa59afebadf0260e832f6f44468443562f53fbaf7bcb5e46e1462d3f328ac437ce56edbca617659ac9883f9e13261fad7", + "0xb1990e42743a88de4deeacfd55fafeab3bc380cb95de43ed623d021a4f2353530bcab9594389c1844b1c5ea6634c4555", + "0x85051e841149a10e83f56764e042182208591396d0ce78c762c4a413e6836906df67f38c69793e158d64fef111407ba3", + "0x9778172bbd9b1f2ec6bbdd61829d7b39a7df494a818e31c654bf7f6a30139899c4822c1bf418dd4f923243067759ce63", + "0x9355005b4878c87804fc966e7d24f3e4b02bed35b4a77369d01f25a3dcbff7621b08306b1ac85b76fe7b4a3eb5f839b1", + "0x8f9dc6a54fac052e236f8f0e1f571ac4b5308a43acbe4cc8183bce26262ddaf7994e41cf3034a4cbeca2c505a151e3b1", + "0x8cc59c17307111723fe313046a09e0e32ea0cce62c13814ab7c6408c142d6a0311d801be4af53fc9240523f12045f9ef", + "0x8e6057975ed40a1932e47dd3ac778f72ee2a868d8540271301b1aa6858de1a5450f596466494a3e0488be4fbeb41c840", + "0x812145efbd6559ae13325d56a15940ca4253b17e72a9728986b563bb5acc13ec86453796506ac1a8f12bd6f9e4a288c3", + "0x911da0a6d6489eb3dab2ec4a16e36127e8a291ae68a6c2c9de33e97f3a9b1f00da57a94e270a0de79ecc5ecb45d19e83", + "0xb72ea85973f4b2a7e6e71962b0502024e979a73c18a9111130e158541fa47bbaaf53940c8f846913a517dc69982ba9e1", + "0xa7a56ad1dbdc55f177a7ad1d0af78447dc2673291e34e8ab74b26e2e2e7d8c5fe5dc89e7ef60f04a9508847b5b3a8188", + "0xb52503f6e5411db5d1e70f5fb72ccd6463fa0f197b3e51ca79c7b5a8ab2e894f0030476ada72534fa4eb4e06c3880f90", + "0xb51c7957a3d18c4e38f6358f2237b3904618d58b1de5dec53387d25a63772e675a5b714ad35a38185409931157d4b529", + "0xb86b4266e719d29c043d7ec091547aa6f65bbf2d8d831d1515957c5c06513b72aa82113e9645ad38a7bc3f5383504fa6", + "0xb95b547357e6601667b0f5f61f261800a44c2879cf94e879def6a105b1ad2bbf1795c3b98a90d588388e81789bd02681", + "0xa58fd4c5ae4673fa350da6777e13313d5d37ed1dafeeb8f4f171549765b84c895875d9d3ae6a9741f3d51006ef81d962", + "0x9398dc348d078a604aadc154e6eef2c0be1a93bb93ba7fe8976edc2840a3a318941338cc4d5f743310e539d9b46613d2", + "0x902c9f0095014c4a2f0dccaaab543debba6f4cc82c345a10aaf4e72511725dbed7a34cd393a5f4e48a3e5142b7be84ed", + "0xa7c0447849bb44d04a0393a680f6cd390093484a79a147dd238f5d878030d1c26646d88211108e59fe08b58ad20c6fbd", + "0x80db045535d6e67a422519f5c89699e37098449d249698a7cc173a26ccd06f60238ae6cc7242eb780a340705c906790c", + "0x8e52b451a299f30124505de2e74d5341e1b5597bdd13301cc39b05536c96e4380e7f1b5c7ef076f5b3005a868657f17c", + "0x824499e89701036037571761e977654d2760b8ce21f184f2879fda55d3cda1e7a95306b8abacf1caa79d3cc075b9d27f", + "0x9049b956b77f8453d2070607610b79db795588c0cec12943a0f5fe76f358dea81e4f57a4692112afda0e2c05c142b26f", + "0x81911647d818a4b5f4990bfd4bc13bf7be7b0059afcf1b6839333e8569cdb0172fd2945410d88879349f677abaed5eb3", + "0xad4048f19b8194ed45b6317d9492b71a89a66928353072659f5ce6c816d8f21e69b9d1817d793effe49ca1874daa1096", + "0x8d22f7b2ddb31458661abd34b65819a374a1f68c01fc6c9887edeba8b80c65bceadb8f57a3eb686374004b836261ef67", + "0x92637280c259bc6842884db3d6e32602a62252811ae9b019b3c1df664e8809ffe86db88cfdeb8af9f46435c9ee790267", + "0xa2f416379e52e3f5edc21641ea73dc76c99f7e29ea75b487e18bd233856f4c0183429f378d2bfc6cd736d29d6cadfa49", + "0x882cb6b76dbdc188615dcf1a8439eba05ffca637dd25197508156e03c930b17b9fed2938506fdd7b77567cb488f96222", + "0xb68b621bb198a763fb0634eddb93ed4b5156e59b96c88ca2246fd1aea3e6b77ed651e112ac41b30cd361fadc011d385e", + "0xa3cb22f6b675a29b2d1f827cacd30df14d463c93c3502ef965166f20d046af7f9ab7b2586a9c64f4eae4fad2d808a164", + "0x8302d9ce4403f48ca217079762ce42cee8bc30168686bb8d3a945fbd5acd53b39f028dce757b825eb63af2d5ae41169d", + "0xb2eef1fbd1a176f1f4cd10f2988c7329abe4eb16c7405099fb92baa724ab397bc98734ef7d4b24c0f53dd90f57520d04", + "0xa1bbef0bd684a3f0364a66bde9b29326bac7aa3dde4caed67f14fb84fed3de45c55e406702f1495a3e2864d4ee975030", + "0x976acdb0efb73e3a3b65633197692dedc2adaed674291ae3df76b827fc866d214e9cac9ca46baefc4405ff13f953d936", + "0xb9fbf71cc7b6690f601f0b1c74a19b7d14254183a2daaafec7dc3830cba5ae173d854bbfebeca985d1d908abe5ef0cda", + "0x90591d7b483598c94e38969c4dbb92710a1a894bcf147807f1bcbd8aa3ac210b9f2be65519aa829f8e1ccdc83ad9b8cf", + "0xa30568577c91866b9c40f0719d46b7b3b2e0b4a95e56196ac80898a2d89cc67880e1229933f2cd28ee3286f8d03414d7", + "0x97589a88c3850556b359ec5e891f0937f922a751ac7c95949d3bbc7058c172c387611c0f4cb06351ef02e5178b3dd9e4", + "0x98e7bbe27a1711f4545df742f17e3233fbcc63659d7419e1ca633f104cb02a32c84f2fac23ca2b84145c2672f68077ab", + "0xa7ddb91636e4506d8b7e92aa9f4720491bb71a72dadc47c7f4410e15f93e43d07d2b371951a0e6a18d1bd087aa96a5c4", + "0xa7c006692227a06db40bceac3d5b1daae60b5692dd9b54772bedb5fea0bcc91cbcdb530cac31900ffc70c5b3ffadc969", + "0x8d3ec6032778420dfa8be52066ba0e623467df33e4e1901dbadd586c5d750f4ccde499b5197e26b9ea43931214060f69", + "0x8d9a8410518ea64f89df319bfd1fc97a0971cdb9ad9b11d1f8fe834042ea7f8dce4db56eeaf179ff8dda93b6db93e5ce", + "0xa3c533e9b3aa04df20b9ff635cb1154ce303e045278fcf3f10f609064a5445552a1f93989c52ce852fd0bbd6e2b6c22e", + "0x81934f3a7f8c1ae60ec6e4f212986bcc316118c760a74155d06ce0a8c00a9b9669ec4e143ca214e1b995e41271774fd9", + "0xab8e2d01a71192093ef8fafa7485e795567cc9db95a93fb7cc4cf63a391ef89af5e2bfad4b827fffe02b89271300407f", + "0x83064a1eaa937a84e392226f1a60b7cfad4efaa802f66de5df7498962f7b2649924f63cd9962d47906380b97b9fe80e1", + "0xb4f5e64a15c6672e4b55417ee5dc292dcf93d7ea99965a888b1cc4f5474a11e5b6520eacbcf066840b343f4ceeb6bf33", + "0xa63d278b842456ef15c278b37a6ea0f27c7b3ffffefca77c7a66d2ea06c33c4631eb242bbb064d730e70a8262a7b848a", + "0x83a41a83dbcdf0d22dc049de082296204e848c453c5ab1ba75aa4067984e053acf6f8b6909a2e1f0009ed051a828a73b", + "0x819485b036b7958508f15f3c19436da069cbe635b0318ebe8c014cf1ef9ab2df038c81161b7027475bcfa6fff8dd9faf", + "0xaa40e38172806e1e045e167f3d1677ef12d5dcdc89b43639a170f68054bd196c4fae34c675c1644d198907a03f76ba57", + "0x969bae484883a9ed1fbed53b26b3d4ee4b0e39a6c93ece5b3a49daa01444a1c25727dabe62518546f36b047b311b177c", + "0x80a9e73a65da99664988b238096a090d313a0ee8e4235bc102fa79bb337b51bb08c4507814eb5baec22103ec512eaab0", + "0x86604379aec5bddda6cbe3ef99c0ac3a3c285b0b1a15b50451c7242cd42ae6b6c8acb717dcca7917838432df93a28502", + "0xa23407ee02a495bed06aa7e15f94cfb05c83e6d6fba64456a9bbabfa76b2b68c5c47de00ba169e710681f6a29bb41a22", + "0x98cff5ecc73b366c6a01b34ac9066cb34f7eeaf4f38a5429bad2d07e84a237047e2a065c7e8a0a6581017dadb4695deb", + "0x8de9f68a938f441f3b7ab84bb1f473c5f9e5c9e139e42b7ccee1d254bd57d0e99c2ccda0f3198f1fc5737f6023dd204e", + "0xb0ce48d815c2768fb472a315cad86aa033d0e9ca506f146656e2941829e0acb735590b4fbc713c2d18d3676db0a954ac", + "0x82f485cdefd5642a6af58ac6817991c49fac9c10ace60f90b27f1788cc026c2fe8afc83cf499b3444118f9f0103598a8", + "0x82c24550ed512a0d53fc56f64cc36b553823ae8766d75d772dacf038c460f16f108f87a39ceef7c66389790f799dbab3", + "0x859ffcf1fe9166388316149b9acc35694c0ea534d43f09dae9b86f4aa00a23b27144dda6a352e74b9516e8c8d6fc809c", + "0xb8f7f353eec45da77fb27742405e5ad08d95ec0f5b6842025be9def3d9892f85eb5dd0921b41e6eff373618dba215bca", + "0x8ccca4436f9017e426229290f5cd05eac3f16571a4713141a7461acfe8ae99cd5a95bf5b6df129148693c533966145da", + "0xa2c67ecc19c0178b2994846fea4c34c327a5d786ac4b09d1d13549d5be5996d8a89021d63d65cb814923388f47cc3a03", + "0xaa0ff87d676b418ec08f5cbf577ac7e744d1d0e9ebd14615b550eb86931eafd2a36d4732cc5d6fab1713fd7ab2f6f7c0", + "0x8aef4730bb65e44efd6bb9441c0ae897363a2f3054867590a2c2ecf4f0224e578c7a67f10b40f8453d9f492ac15a9b2d", + "0x86a187e13d8fba5addcfdd5b0410cedd352016c930f913addd769ee09faa6be5ca3e4b1bdb417a965c643a99bd92be42", + "0xa0a4e9632a7a094b14b29b78cd9c894218cdf6783e61671e0203865dc2a835350f465fbaf86168f28af7c478ca17bc89", + "0xa8c7b02d8deff2cd657d8447689a9c5e2cd74ef57c1314ac4d69084ac24a7471954d9ff43fe0907d875dcb65fd0d3ce5", + "0x97ded38760aa7be6b6960b5b50e83b618fe413cbf2bcc1da64c05140bcc32f5e0e709cd05bf8007949953fac5716bad9", + "0xb0d293835a24d64c2ae48ce26e550b71a8c94a0883103757fb6b07e30747f1a871707d23389ba2b2065fa6bafe220095", + "0x8f9e291bf849feaa575592e28e3c8d4b7283f733d41827262367ea1c40f298c7bcc16505255a906b62bf15d9f1ba85fb", + "0x998f4e2d12708b4fd85a61597ca2eddd750f73c9e0c9b3cf0825d8f8e01f1628fd19797dcaed3b16dc50331fc6b8b821", + "0xb30d1f8c115d0e63bf48f595dd10908416774c78b3bbb3194192995154d80ea042d2e94d858de5f8aa0261b093c401fd", + "0xb5d9c75bb41f964cbff3f00e96d9f1480c91df8913f139f0d385d27a19f57a820f838eb728e46823cbff00e21c660996", + "0xa6edec90b5d25350e2f5f0518777634f9e661ec9d30674cf5b156c4801746d62517751d90074830ac0f4b09911c262f1", + "0x82f98da1264b6b75b8fbeb6a4d96d6a05b25c24db0d57ba3a38efe3a82d0d4e331b9fc4237d6494ccfe4727206457519", + "0xb89511843453cf4ecd24669572d6371b1e529c8e284300c43e0d5bb6b3aaf35aeb634b3cb5c0a2868f0d5e959c1d0772", + "0xa82bf065676583e5c1d3b81987aaae5542f522ba39538263a944bb33ea5b514c649344a96c0205a3b197a3f930fcda6c", + "0xa37b47ea527b7e06c460776aa662d9a49ff4149d3993f1a974b0dd165f7171770d189b0e2ea54fd5fccb6a14b116e68a", + "0xa1017677f97dda818274d47556d09d0e4ccacb23a252f82a6cfe78c630ad46fb9806307445a59fb61262182de3a2b29c", + "0xb01e9fcac239ba270e6877b79273ddd768bf8a51d2ed8a051b1c11e18eff3de5920e2fcbfbd26f06d381eddd3b1f1e1b", + "0x82fcd53d803b1c8e4ed76adc339b7f3a5962d37042b9683aabac7513ac68775d4a566a9460183926a6a95dbe7d551a1f", + "0xa763e78995d55cd21cdb7ef75d9642d6e1c72453945e346ab6690c20a4e1eeec61bb848ef830ae4b56182535e3c71d8f", + "0xb769f4db602251d4b0a1186782799bdcef66de33c110999a5775c50b349666ffd83d4c89714c4e376f2efe021a5cfdb2", + "0xa59cbd1b785efcfa6e83fc3b1d8cf638820bc0c119726b5368f3fba9dce8e3414204fb1f1a88f6c1ff52e87961252f97", + "0x95c8c458fd01aa23ecf120481a9c6332ebec2e8bb70a308d0576926a858457021c277958cf79017ddd86a56cacc2d7db", + "0x82eb41390800287ae56e77f2e87709de5b871c8bdb67c10a80fc65f3acb9f7c29e8fa43047436e8933f27449ea61d94d", + "0xb3ec25e3545eb83aed2a1f3558d1a31c7edde4be145ecc13b33802654b77dc049b4f0065069dd9047b051e52ab11dcdd", + "0xb78a0c715738f56f0dc459ab99e252e3b579b208142836b3c416b704ca1de640ca082f29ebbcee648c8c127df06f6b1e", + "0xa4083149432eaaf9520188ebf4607d09cf664acd1f471d4fb654476e77a9eaae2251424ffda78d09b6cb880df35c1219", + "0x8c52857d68d6e9672df3db2df2dbf46b516a21a0e8a18eec09a6ae13c1ef8f369d03233320dd1c2c0bbe00abfc1ea18b", + "0x8c856089488803066bff3f8d8e09afb9baf20cecc33c8823c1c0836c3d45498c3de37e87c016b705207f60d2b00f8609", + "0x831a3df39be959047b2aead06b4dcd3012d7b29417f642b83c9e8ce8de24a3dbbd29c6fdf55e2db3f7ea04636c94e403", + "0xaed84d009f66544addabe404bf6d65af7779ce140dc561ff0c86a4078557b96b2053b7b8a43432ffb18cd814f143b9da", + "0x93282e4d72b0aa85212a77b336007d8ba071eea17492da19860f1ad16c1ea8867ccc27ef5c37c74b052465cc11ea4f52", + "0xa7b78b8c8d057194e8d68767f1488363f77c77bddd56c3da2bc70b6354c7aa76247c86d51f7371aa38a4aa7f7e3c0bb7", + "0xb1c77283d01dcd1bde649b5b044eac26befc98ff57cbee379fb5b8e420134a88f2fc7f0bf04d15e1fbd45d29e7590fe6", + "0xa4aa8de70330a73b2c6458f20a1067eed4b3474829b36970a8df125d53bbdda4f4a2c60063b7cccb0c80fc155527652f", + "0x948a6c79ba1b8ad7e0bed2fae2f0481c4e41b4d9bbdd9b58164e28e9065700e83f210c8d5351d0212e0b0b68b345b3a5", + "0x86a48c31dcbbf7b082c92d28e1f613a2378a910677d7db3a349dc089e4a1e24b12eee8e8206777a3a8c64748840b7387", + "0x976adb1af21e0fc34148917cf43d933d7bfd3fd12ed6c37039dcd5a4520e3c6cf5868539ba5bf082326430deb8a4458d", + "0xb93e1a4476f2c51864bb4037e7145f0635eb2827ab91732b98d49b6c07f6ac443111aa1f1da76d1888665cb897c3834e", + "0x8afd46fb23bf869999fa19784b18a432a1f252d09506b8dbb756af900518d3f5f244989b3d7c823d9029218c655d3dc6", + "0x83f1e59e3abeed18cdc632921672673f1cb6e330326e11c4e600e13e0d5bc11bdc970ae12952e15103a706fe720bf4d6", + "0x90ce4cc660714b0b673d48010641c09c00fc92a2c596208f65c46073d7f349dd8e6e077ba7dcef9403084971c3295b76", + "0x8b09b0f431a7c796561ecf1549b85048564de428dac0474522e9558b6065fede231886bc108539c104ce88ebd9b5d1b0", + "0x85d6e742e2fb16a7b0ba0df64bc2c0dbff9549be691f46a6669bca05e89c884af16822b85faefefb604ec48c8705a309", + "0xa87989ee231e468a712c66513746fcf03c14f103aadca0eac28e9732487deb56d7532e407953ab87a4bf8961588ef7b0", + "0xb00da10efe1c29ee03c9d37d5918e391ae30e48304e294696b81b434f65cf8c8b95b9d1758c64c25e534d045ba28696f", + "0x91c0e1fb49afe46c7056400baa06dbb5f6e479db78ee37e2d76c1f4e88994357e257b83b78624c4ef6091a6c0eb8254d", + "0x883fb797c498297ccbf9411a3e727c3614af4eccde41619b773dc7f3259950835ee79453debf178e11dec4d3ada687a0", + "0xa14703347e44eb5059070b2759297fcfcfc60e6893c0373eea069388eba3950aa06f1c57cd2c30984a2d6f9e9c92c79e", + "0xafebc7585b304ceba9a769634adff35940e89cd32682c78002822aab25eec3edc29342b7f5a42a56a1fec67821172ad5", + "0xaea3ff3822d09dba1425084ca95fd359718d856f6c133c5fabe2b2eed8303b6e0ba0d8698b48b93136a673baac174fd9", + "0xaf2456a09aa777d9e67aa6c7c49a1845ea5cdda2e39f4c935c34a5f8280d69d4eec570446998cbbe31ede69a91e90b06", + "0x82cada19fed16b891ef3442bafd49e1f07c00c2f57b2492dd4ee36af2bd6fd877d6cb41188a4d6ce9ec8d48e8133d697", + "0x82a21034c832287f616619a37c122cee265cc34ae75e881fcaea4ea7f689f3c2bc8150bbf7dbcfd123522bfb7f7b1d68", + "0x86877217105f5d0ec3eeff0289fc2a70d505c9fdf7862e8159553ef60908fb1a27bdaf899381356a4ef4649072a9796c", + "0x82b196e49c6e861089a427c0b4671d464e9d15555ffb90954cd0d630d7ae02eb3d98ceb529d00719c2526cd96481355a", + "0xa29b41d0d43d26ce76d4358e0db2b77df11f56e389f3b084d8af70a636218bd3ac86b36a9fe46ec9058c26a490f887f7", + "0xa4311c4c20c4d7dd943765099c50f2fd423e203ccfe98ff00087d205467a7873762510cac5fdce7a308913ed07991ed7", + "0xb1f040fc5cc51550cb2c25cf1fd418ecdd961635a11f365515f0cb4ffb31da71f48128c233e9cc7c0cf3978d757ec84e", + "0xa9ebae46f86d3bd543c5f207ed0d1aed94b8375dc991161d7a271f01592912072e083e2daf30c146430894e37325a1b9", + "0x826418c8e17ad902b5fe88736323a47e0ca7a44bce4cbe27846ec8fe81de1e8942455dda6d30e192cdcc73e11df31256", + "0x85199db563427c5edcbac21f3d39fec2357be91fb571982ddcdc4646b446ad5ced84410de008cb47b3477ee0d532daf8", + "0xb7eed9cd400b2ca12bf1d9ae008214b8561fb09c8ad9ff959e626ffde00fee5ff2f5b6612e231f2a1a9b1646fcc575e3", + "0x8b40bf12501dcbac78f5a314941326bfcddf7907c83d8d887d0bb149207f85d80cd4dfbd7935439ea7b14ea39a3fded7", + "0x83e3041af302485399ba6cd5120e17af61043977083887e8d26b15feec4a6b11171ac5c06e6ad0971d4b58a81ff12af3", + "0x8f5b9a0eecc589dbf8c35a65d5e996a659277ef6ea509739c0cb7b3e2da9895e8c8012de662e5b23c5fa85d4a8f48904", + "0x835d71ed5e919d89d8e6455f234f3ff215462c4e3720c371ac8c75e83b19dfe3ae15a81547e4dc1138e5f5997f413cc9", + "0x8b7d2e4614716b1db18e9370176ea483e6abe8acdcc3dcdf5fb1f4d22ca55d652feebdccc171c6de38398d9f7bfdec7a", + "0x93eace72036fe57d019676a02acf3d224cf376f166658c1bf705db4f24295881d477d6fdd7916efcfceff8c7a063deda", + "0xb1ac460b3d516879a84bc886c54f020a9d799e7c49af3e4d7de5bf0d2793c852254c5d8fe5616147e6659512e5ccb012", + "0xacd0947a35cb167a48bcd9667620464b54ac0e78f9316b4aa92dcaab5422d7a732087e52e1c827faa847c6b2fe6e7766", + "0x94ac33d21c3d12ff762d32557860e911cd94d666609ddcc42161b9c16f28d24a526e8b10bb03137257a92cec25ae637d", + "0x832e02058b6b994eadd8702921486241f9a19e68ed1406dad545e000a491ae510f525ccf9d10a4bba91c68f2c53a0f58", + "0x9471035d14f78ff8f463b9901dd476b587bb07225c351161915c2e9c6114c3c78a501379ab6fb4eb03194c457cbd22bf", + "0xab64593e034c6241d357fcbc32d8ea5593445a5e7c24cac81ad12bd2ef01843d477a36dc1ba21dbe63b440750d72096a", + "0x9850f3b30045e927ad3ec4123a32ed2eb4c911f572b6abb79121873f91016f0d80268de8b12e2093a4904f6e6cab7642", + "0x987212c36b4722fe2e54fa30c52b1e54474439f9f35ca6ad33c5130cd305b8b54b532dd80ffd2c274105f20ce6d79f6e", + "0x8b4d0c6abcb239b5ed47bef63bc17efe558a27462c8208fa652b056e9eae9665787cd1aee34fbb55beb045c8bfdb882b", + "0xa9f3483c6fee2fe41312d89dd4355d5b2193ac413258993805c5cbbf0a59221f879386d3e7a28e73014f10e65dd503d9", + "0xa2225da3119b9b7c83d514b9f3aeb9a6d9e32d9cbf9309cbb971fd53c4b2c001d10d880a8ad8a7c281b21d85ceca0b7c", + "0xa050be52e54e676c151f7a54453bbb707232f849beab4f3bf504b4d620f59ed214409d7c2bd3000f3ff13184ccda1c35", + "0xadbccf681e15b3edb6455a68d292b0a1d0f5a4cb135613f5e6db9943f02181341d5755875db6ee474e19ace1c0634a28", + "0x8b6eff675632a6fad0111ec72aacc61c7387380eb87933fd1d098856387d418bd38e77d897e65d6fe35951d0627c550b", + "0xaabe2328ddf90989b15e409b91ef055cb02757d34987849ae6d60bef2c902bf8251ed21ab30acf39e500d1d511e90845", + "0x92ba4eb1f796bc3d8b03515f65c045b66e2734c2da3fc507fdd9d6b5d1e19ab3893726816a32141db7a31099ca817d96", + "0x8a98b3cf353138a1810beb60e946183803ef1d39ac4ea92f5a1e03060d35a4774a6e52b14ead54f6794d5f4022b8685c", + "0x909f8a5c13ec4a59b649ed3bee9f5d13b21d7f3e2636fd2bb3413c0646573fdf9243d63083356f12f5147545339fcd55", + "0x9359d914d1267633141328ed0790d81c695fea3ddd2d406c0df3d81d0c64931cf316fe4d92f4353c99ff63e2aefc4e34", + "0xb88302031681b54415fe8fbfa161c032ea345c6af63d2fb8ad97615103fd4d4281c5a9cae5b0794c4657b97571a81d3b", + "0x992c80192a519038082446b1fb947323005b275e25f2c14c33cc7269e0ec038581cc43705894f94bad62ae33a8b7f965", + "0xa78253e3e3eece124bef84a0a8807ce76573509f6861d0b6f70d0aa35a30a123a9da5e01e84969708c40b0669eb70aa6", + "0x8d5724de45270ca91c94792e8584e676547d7ac1ac816a6bb9982ee854eb5df071d20545cdfd3771cd40f90e5ba04c8e", + "0x825a6f586726c68d45f00ad0f5a4436523317939a47713f78fd4fe81cd74236fdac1b04ecd97c2d0267d6f4981d7beb1" + ], + "g2_monomial": [ + "0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + "0xb5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2", + "0xb5337ba0ce5d37224290916e268e2060e5c14f3f9fc9e1ec3af5a958e7a0303122500ce18f1a4640bf66525bd10e763501fe986d86649d8d45143c08c3209db3411802c226e9fe9a55716ac4a0c14f9dcef9e70b2bb309553880dc5025eab3cc", + "0xb3c1dcdc1f62046c786f0b82242ef283e7ed8f5626f72542aa2c7a40f14d9094dd1ebdbd7457ffdcdac45fd7da7e16c51200b06d791e5e43e257e45efdf0bd5b06cd2333beca2a3a84354eb48662d83aef5ecf4e67658c851c10b13d8d87c874", + "0x954d91c7688983382609fca9e211e461f488a5971fd4e40d7e2892037268eacdfd495cfa0a7ed6eb0eb11ac3ae6f651716757e7526abe1e06c64649d80996fd3105c20c4c94bc2b22d97045356fe9d791f21ea6428ac48db6f9e68e30d875280", + "0x88a6b6bb26c51cf9812260795523973bb90ce80f6820b6c9048ab366f0fb96e48437a7f7cb62aedf64b11eb4dfefebb0147608793133d32003cb1f2dc47b13b5ff45f1bb1b2408ea45770a08dbfaec60961acb8119c47b139a13b8641e2c9487", + "0x85cd7be9728bd925d12f47fb04b32d9fad7cab88788b559f053e69ca18e463113ecc8bbb6dbfb024835f901b3a957d3108d6770fb26d4c8be0a9a619f6e3a4bf15cbfd48e61593490885f6cee30e4300c5f9cf5e1c08e60a2d5b023ee94fcad0", + "0x80477dba360f04399821a48ca388c0fa81102dd15687fea792ee8c1114e00d1bc4839ad37ac58900a118d863723acfbe08126ea883be87f50e4eabe3b5e72f5d9e041db8d9b186409fd4df4a7dde38c0e0a3b1ae29b098e5697e7f110b6b27e4", + "0xb7a6aec08715a9f8672a2b8c367e407be37e59514ac19dd4f0942a68007bba3923df22da48702c63c0d6b3efd3c2d04e0fe042d8b5a54d562f9f33afc4865dcbcc16e99029e25925580e87920c399e710d438ac1ce3a6dc9b0d76c064a01f6f7", + "0xac1b001edcea02c8258aeffbf9203114c1c874ad88dae1184fadd7d94cd09053649efd0ca413400e6e9b5fa4eac33261000af88b6bd0d2abf877a4f0355d2fb4d6007adb181695201c5432e50b850b51b3969f893bddf82126c5a71b042b7686", + "0x90043fda4de53fb364fab2c04be5296c215599105ecff0c12e4917c549257125775c29f2507124d15f56e30447f367db0596c33237242c02d83dfd058735f1e3c1ff99069af55773b6d51d32a68bf75763f59ec4ee7267932ae426522b8aaab6", + "0xa8660ce853e9dc08271bf882e29cd53397d63b739584dda5263da4c7cc1878d0cf6f3e403557885f557e184700575fee016ee8542dec22c97befe1d10f414d22e84560741cdb3e74c30dda9b42eeaaf53e27822de2ee06e24e912bf764a9a533", + "0x8fe3921a96d0d065e8aa8fce9aa42c8e1461ca0470688c137be89396dd05103606dab6cdd2a4591efd6addf72026c12e065da7be276dee27a7e30afa2bd81c18f1516e7f068f324d0bad9570b95f6bd02c727cd2343e26db0887c3e4e26dceda", + "0x8ae1ad97dcb9c192c9a3933541b40447d1dc4eebf380151440bbaae1e120cc5cdf1bcea55180b128d8e180e3af623815191d063cc0d7a47d55fb7687b9d87040bf7bc1a7546b07c61db5ccf1841372d7c2fe4a5431ffff829f3c2eb590b0b710", + "0x8c2fa96870a88150f7876c931e2d3cc2adeaaaf5c73ef5fa1cf9dfa0991ae4819f9321af7e916e5057d87338e630a2f21242c29d76963cf26035b548d2a63d8ad7bd6efefa01c1df502cbdfdfe0334fb21ceb9f686887440f713bf17a89b8081", + "0xb9aa98e2f02bb616e22ee5dd74c7d1049321ac9214d093a738159850a1dbcc7138cb8d26ce09d8296368fd5b291d74fa17ac7cc1b80840fdd4ee35e111501e3fa8485b508baecda7c1ab7bd703872b7d64a2a40b3210b6a70e8a6ffe0e5127e3", + "0x9292db67f8771cdc86854a3f614a73805bf3012b48f1541e704ea4015d2b6b9c9aaed36419769c87c49f9e3165f03edb159c23b3a49c4390951f78e1d9b0ad997129b17cdb57ea1a6638794c0cca7d239f229e589c5ae4f9fe6979f7f8cba1d7", + "0x91cd9e86550f230d128664f7312591fee6a84c34f5fc7aed557bcf986a409a6de722c4330453a305f06911d2728626e611acfdf81284f77f60a3a1595053a9479964fd713117e27c0222cc679674b03bc8001501aaf9b506196c56de29429b46", + "0xa9516b73f605cc31b89c68b7675dc451e6364595243d235339437f556cf22d745d4250c1376182273be2d99e02c10eee047410a43eff634d051aeb784e76cb3605d8e079b9eb6ad1957dfdf77e1cd32ce4a573c9dfcc207ca65af6eb187f6c3d", + "0xa9667271f7d191935cc8ad59ef3ec50229945faea85bfdfb0d582090f524436b348aaa0183b16a6231c00332fdac2826125b8c857a2ed9ec66821cfe02b3a2279be2412441bc2e369b255eb98614e4be8490799c4df22f18d47d24ec70bba5f7", + "0xa4371144d2aa44d70d3cb9789096d3aa411149a6f800cb46f506461ee8363c8724667974252f28aea61b6030c05930ac039c1ee64bb4bd56532a685cae182bf2ab935eee34718cffcb46cae214c77aaca11dbb1320faf23c47247db1da04d8dc", + "0x89a7eb441892260b7e81168c386899cd84ffc4a2c5cad2eae0d1ab9e8b5524662e6f660fe3f8bfe4c92f60b060811bc605b14c5631d16709266886d7885a5eb5930097127ec6fb2ebbaf2df65909cf48f253b3d5e22ae48d3e9a2fd2b01f447e", + "0x9648c42ca97665b5eccb49580d8532df05eb5a68db07f391a2340769b55119eaf4c52fe4f650c09250fa78a76c3a1e271799b8333cc2628e3d4b4a6a3e03da1f771ecf6516dd63236574a7864ff07e319a6f11f153406280d63af9e2b5713283", + "0x9663bf6dd446ea7a90658ee458578d4196dc0b175ef7fcfa75f44d41670850774c2e46c5a6be132a2c072a3c0180a24f0305d1acac49d2d79878e5cda80c57feda3d01a6af12e78b5874e2a4b3717f11c97503b41a4474e2e95b179113726199", + "0xb212aeb4814e0915b432711b317923ed2b09e076aaf558c3ae8ef83f9e15a83f9ea3f47805b2750ab9e8106cb4dc6ad003522c84b03dc02829978a097899c773f6fb31f7fe6b8f2d836d96580f216fec20158f1590c3e0d7850622e15194db05", + "0x925f005059bf07e9ceccbe66c711b048e236ade775720d0fe479aebe6e23e8af281225ad18e62458dc1b03b42ad4ca290d4aa176260604a7aad0d9791337006fbdebe23746f8060d42876f45e4c83c3643931392fde1cd13ff8bddf8111ef974", + "0x9553edb22b4330c568e156a59ef03b26f5c326424f830fe3e8c0b602f08c124730ffc40bc745bec1a22417adb22a1a960243a10565c2be3066bfdb841d1cd14c624cd06e0008f4beb83f972ce6182a303bee3fcbcabc6cfe48ec5ae4b7941bfc", + "0x935f5a404f0a78bdcce709899eda0631169b366a669e9b58eacbbd86d7b5016d044b8dfc59ce7ed8de743ae16c2343b50e2f925e88ba6319e33c3fc76b314043abad7813677b4615c8a97eb83cc79de4fedf6ccbcfa4d4cbf759a5a84e4d9742", + "0xa5b014ab936eb4be113204490e8b61cd38d71da0dec7215125bcd131bf3ab22d0a32ce645bca93e7b3637cf0c2db3d6601a0ddd330dc46f9fae82abe864ffc12d656c88eb50c20782e5bb6f75d18760666f43943abb644b881639083e122f557", + "0x935b7298ae52862fa22bf03bfc1795b34c70b181679ae27de08a9f5b4b884f824ef1b276b7600efa0d2f1d79e4a470d51692fd565c5cf8343dd80e5d3336968fc21c09ba9348590f6206d4424eb229e767547daefa98bc3aa9f421158dee3f2a", + "0x9830f92446e708a8f6b091cc3c38b653505414f8b6507504010a96ffda3bcf763d5331eb749301e2a1437f00e2415efb01b799ad4c03f4b02de077569626255ac1165f96ea408915d4cf7955047620da573e5c439671d1fa5c833fb11de7afe6", + "0x840dcc44f673fff3e387af2bb41e89640f2a70bcd2b92544876daa92143f67c7512faf5f90a04b7191de01f3e2b1bde00622a20dc62ca23bbbfaa6ad220613deff43908382642d4d6a86999f662efd64b1df448b68c847cfa87630a3ffd2ec76", + "0x92950c895ed54f7f876b2fda17ecc9c41b7accfbdd42c210cc5b475e0737a7279f558148531b5c916e310604a1de25a80940c94fe5389ae5d6a5e9c371be67bceea1877f5401725a6595bcf77ece60905151b6dfcb68b75ed2e708c73632f4fd", + "0x8010246bf8e94c25fd029b346b5fbadb404ef6f44a58fd9dd75acf62433d8cc6db66974f139a76e0c26dddc1f329a88214dbb63276516cf325c7869e855d07e0852d622c332ac55609ba1ec9258c45746a2aeb1af0800141ee011da80af175d4", + "0xb0f1bad257ebd187bdc3f37b23f33c6a5d6a8e1f2de586080d6ada19087b0e2bf23b79c1b6da1ee82271323f5bdf3e1b018586b54a5b92ab6a1a16bb3315190a3584a05e6c37d5ca1e05d702b9869e27f513472bcdd00f4d0502a107773097da", + "0x9636d24f1ede773ce919f309448dd7ce023f424afd6b4b69cb98c2a988d849a283646dc3e469879daa1b1edae91ae41f009887518e7eb5578f88469321117303cd3ac2d7aee4d9cb5f82ab9ae3458e796dfe7c24284b05815acfcaa270ff22e2", + "0xb373feb5d7012fd60578d7d00834c5c81df2a23d42794fed91aa9535a4771fde0341c4da882261785e0caca40bf83405143085e7f17e55b64f6c5c809680c20b050409bf3702c574769127c854d27388b144b05624a0e24a1cbcc4d08467005b", + "0xb15680648949ce69f82526e9b67d9b55ce5c537dc6ab7f3089091a9a19a6b90df7656794f6edc87fb387d21573ffc847062623685931c2790a508cbc8c6b231dd2c34f4d37d4706237b1407673605a604bcf6a50cc0b1a2db20485e22b02c17e", + "0x8817e46672d40c8f748081567b038a3165f87994788ec77ee8daea8587f5540df3422f9e120e94339be67f186f50952504cb44f61e30a5241f1827e501b2de53c4c64473bcc79ab887dd277f282fbfe47997a930dd140ac08b03efac88d81075", + "0xa6e4ef6c1d1098f95aae119905f87eb49b909d17f9c41bcfe51127aa25fee20782ea884a7fdf7d5e9c245b5a5b32230b07e0dbf7c6743bf52ee20e2acc0b269422bd6cf3c07115df4aa85b11b2c16630a07c974492d9cdd0ec325a3fabd95044", + "0x8634aa7c3d00e7f17150009698ce440d8e1b0f13042b624a722ace68ead870c3d2212fbee549a2c190e384d7d6ac37ce14ab962c299ea1218ef1b1489c98906c91323b94c587f1d205a6edd5e9d05b42d591c26494a6f6a029a2aadb5f8b6f67", + "0x821a58092900bdb73decf48e13e7a5012a3f88b06288a97b855ef51306406e7d867d613d9ec738ebacfa6db344b677d21509d93f3b55c2ebf3a2f2a6356f875150554c6fff52e62e3e46f7859be971bf7dd9d5b3e1d799749c8a97c2e04325df", + "0x8dba356577a3a388f782e90edb1a7f3619759f4de314ad5d95c7cc6e197211446819c4955f99c5fc67f79450d2934e3c09adefc91b724887e005c5190362245eec48ce117d0a94d6fa6db12eda4ba8dde608fbbd0051f54dcf3bb057adfb2493", + "0xa32a690dc95c23ed9fb46443d9b7d4c2e27053a7fcc216d2b0020a8cf279729c46114d2cda5772fd60a97016a07d6c5a0a7eb085a18307d34194596f5b541cdf01b2ceb31d62d6b55515acfd2b9eec92b27d082fbc4dc59fc63b551eccdb8468", + "0xa040f7f4be67eaf0a1d658a3175d65df21a7dbde99bfa893469b9b43b9d150fc2e333148b1cb88cfd0447d88fa1a501d126987e9fdccb2852ecf1ba907c2ca3d6f97b055e354a9789854a64ecc8c2e928382cf09dda9abde42bbdf92280cdd96", + "0x864baff97fa60164f91f334e0c9be00a152a416556b462f96d7c43b59fe1ebaff42f0471d0bf264976f8aa6431176eb905bd875024cf4f76c13a70bede51dc3e47e10b9d5652d30d2663b3af3f08d5d11b9709a0321aba371d2ef13174dcfcaf", + "0x95a46f32c994133ecc22db49bad2c36a281d6b574c83cfee6680b8c8100466ca034b815cfaedfbf54f4e75188e661df901abd089524e1e0eb0bf48d48caa9dd97482d2e8c1253e7e8ac250a32fd066d5b5cb08a8641bdd64ecfa48289dca83a3", + "0xa2cce2be4d12144138cb91066e0cd0542c80b478bf467867ebef9ddaf3bd64e918294043500bf5a9f45ee089a8d6ace917108d9ce9e4f41e7e860cbce19ac52e791db3b6dde1c4b0367377b581f999f340e1d6814d724edc94cb07f9c4730774", + "0xb145f203eee1ac0a1a1731113ffa7a8b0b694ef2312dabc4d431660f5e0645ef5838e3e624cfe1228cfa248d48b5760501f93e6ab13d3159fc241427116c4b90359599a4cb0a86d0bb9190aa7fabff482c812db966fd2ce0a1b48cb8ac8b3bca", + "0xadabe5d215c608696e03861cbd5f7401869c756b3a5aadc55f41745ad9478145d44393fec8bb6dfc4ad9236dc62b9ada0f7ca57fe2bae1b71565dbf9536d33a68b8e2090b233422313cc96afc7f1f7e0907dc7787806671541d6de8ce47c4cd0", + "0xae7845fa6b06db53201c1080e01e629781817f421f28956589c6df3091ec33754f8a4bd4647a6bb1c141ac22731e3c1014865d13f3ed538dcb0f7b7576435133d9d03be655f8fbb4c9f7d83e06d1210aedd45128c2b0c9bab45a9ddde1c862a5", + "0x9159eaa826a24adfa7adf6e8d2832120ebb6eccbeb3d0459ffdc338548813a2d239d22b26451fda98cc0c204d8e1ac69150b5498e0be3045300e789bcb4e210d5cd431da4bdd915a21f407ea296c20c96608ded0b70d07188e96e6c1a7b9b86b", + "0xa9fc6281e2d54b46458ef564ffaed6944bff71e389d0acc11fa35d3fcd8e10c1066e0dde5b9b6516f691bb478e81c6b20865281104dcb640e29dc116daae2e884f1fe6730d639dbe0e19a532be4fb337bf52ae8408446deb393d224eee7cfa50", + "0x84291a42f991bfb36358eedead3699d9176a38f6f63757742fdbb7f631f2c70178b1aedef4912fed7b6cf27e88ddc7eb0e2a6aa4b999f3eb4b662b93f386c8d78e9ac9929e21f4c5e63b12991fcde93aa64a735b75b535e730ff8dd2abb16e04", + "0xa1b7fcacae181495d91765dfddf26581e8e39421579c9cbd0dd27a40ea4c54af3444a36bf85a11dda2114246eaddbdd619397424bb1eb41b5a15004b902a590ede5742cd850cf312555be24d2df8becf48f5afba5a8cd087cb7be0a521728386", + "0x92feaaf540dbd84719a4889a87cdd125b7e995a6782911931fef26da9afcfbe6f86aaf5328fe1f77631491ce6239c5470f44c7791506c6ef1626803a5794e76d2be0af92f7052c29ac6264b7b9b51f267ad820afc6f881460521428496c6a5f1", + "0xa525c925bfae1b89320a5054acc1fa11820f73d0cf28d273092b305467b2831fab53b6daf75fb926f332782d50e2522a19edcd85be5eb72f1497193c952d8cd0bcc5d43b39363b206eae4cb1e61668bde28a3fb2fc1e0d3d113f6dfadb799717", + "0x98752bb6f5a44213f40eda6aa4ff124057c1b13b6529ab42fe575b9afa66e59b9c0ed563fb20dff62130c436c3e905ee17dd8433ba02c445b1d67182ab6504a90bbe12c26a754bbf734665c622f76c62fe2e11dd43ce04fd2b91a8463679058b", + "0xa9aa9a84729f7c44219ff9e00e651e50ddea3735ef2a73fdf8ed8cd271961d8ed7af5cd724b713a89a097a3fe65a3c0202f69458a8b4c157c62a85668b12fc0d3957774bc9b35f86c184dd03bfefd5c325da717d74192cc9751c2073fe9d170e", + "0xb221c1fd335a4362eff504cd95145f122bf93ea02ae162a3fb39c75583fc13a932d26050e164da97cff3e91f9a7f6ff80302c19dd1916f24acf6b93b62f36e9665a8785413b0c7d930c7f1668549910f849bca319b00e59dd01e5dec8d2edacc", + "0xa71e2b1e0b16d754b848f05eda90f67bedab37709550171551050c94efba0bfc282f72aeaaa1f0330041461f5e6aa4d11537237e955e1609a469d38ed17f5c2a35a1752f546db89bfeff9eab78ec944266f1cb94c1db3334ab48df716ce408ef", + "0xb990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c463e3f1eead58b20b09efcefa566fbfdfe1c6e48d32367936142d0a734143e5e63cdf86be7457723535b787a9cfcfa32fe1d61ad5a2617220", + "0x8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db", + "0xa92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c", + "0x92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10" + ] +} \ No newline at end of file diff --git a/presets/minimal/altair.yaml b/presets/minimal/altair.yaml index 88d78bea36..cbb5d7eb13 100644 --- a/presets/minimal/altair.yaml +++ b/presets/minimal/altair.yaml @@ -1,6 +1,6 @@ # Minimal preset - Altair -# Updated penalty values +# Rewards and penalties # --------------------------------------------------------------- # 3 * 2**24 (= 50,331,648) INACTIVITY_PENALTY_QUOTIENT_ALTAIR: 50331648 @@ -9,16 +9,16 @@ MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR: 64 # 2 PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR: 2 - # Sync committee # --------------------------------------------------------------- -# [customized] +# [customized] 2**5 (= 32) participants SYNC_COMMITTEE_SIZE: 32 -# [customized] +# [customized] 2**3 (= 8) epochs EPOCHS_PER_SYNC_COMMITTEE_PERIOD: 8 - # Sync protocol # --------------------------------------------------------------- -# 1 +# 2**0 (= 1) participants MIN_SYNC_COMMITTEE_PARTICIPANTS: 1 +# [customized] SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD (= 8 * 8) epochs +UPDATE_TIMEOUT: 64 diff --git a/presets/minimal/bellatrix.yaml b/presets/minimal/bellatrix.yaml new file mode 100644 index 0000000000..79b5002e84 --- /dev/null +++ b/presets/minimal/bellatrix.yaml @@ -0,0 +1,21 @@ +# Minimal preset - Bellatrix + +# Rewards and penalties +# --------------------------------------------------------------- +# 2**24 (= 16,777,216) +INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: 16777216 +# 2**5 (= 32) +MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX: 32 +# 3 +PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX: 3 + +# Execution +# --------------------------------------------------------------- +# 2**30 (= 1,073,741,824) bytes +MAX_BYTES_PER_TRANSACTION: 1073741824 +# 2**20 (= 1,048,576) transactions +MAX_TRANSACTIONS_PER_PAYLOAD: 1048576 +# 2**8 (= 256) bytes +BYTES_PER_LOGS_BLOOM: 256 +# 2**5 (= 32) bytes +MAX_EXTRA_DATA_BYTES: 32 diff --git a/presets/minimal/capella.yaml b/presets/minimal/capella.yaml new file mode 100644 index 0000000000..979f4118c2 --- /dev/null +++ b/presets/minimal/capella.yaml @@ -0,0 +1,16 @@ +# Minimal preset - Capella + +# Max operations per block +# --------------------------------------------------------------- +# 2**4 (= 16) credential changes +MAX_BLS_TO_EXECUTION_CHANGES: 16 + +# Execution +# --------------------------------------------------------------- +# [customized] 2**2 (= 4) withdrawals +MAX_WITHDRAWALS_PER_PAYLOAD: 4 + +# Withdrawals processing +# --------------------------------------------------------------- +# [customized] 2**4 (= 16) validators +MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: 16 diff --git a/presets/minimal/custody_game.yaml b/presets/minimal/custody_game.yaml deleted file mode 100644 index c06fccad4f..0000000000 --- a/presets/minimal/custody_game.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# Minimal preset - Custody Game - -# Time parameters -# --------------------------------------------------------------- -# 2**1 (= 2) epochs, 12.8 minutes -RANDAO_PENALTY_EPOCHS: 2 -# [customized] quicker for testing -EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 64 -# [customized] quicker for testing -EPOCHS_PER_CUSTODY_PERIOD: 32 -# [customized] quicker for testing -CUSTODY_PERIOD_TO_RANDAO_PADDING: 8 -# [customize for faster testing] -MAX_CHUNK_CHALLENGE_DELAY: 64 - - -# Max operations -# --------------------------------------------------------------- -# 2**8 (= 256) -MAX_CUSTODY_KEY_REVEALS: 256 -# 2**0 (= 1) -MAX_EARLY_DERIVED_SECRET_REVEALS: 1 -# [customized] -MAX_CUSTODY_CHUNK_CHALLENGES: 2 -# [customized] -MAX_CUSTODY_CHUNK_CHALLENGE_RESP: 8 -# 2**0 (= 1) -MAX_CUSTODY_SLASHINGS: 1 - - -# Reward and penalty quotients -# --------------------------------------------------------------- -EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE: 2 -# 2**8 (= 256) -MINOR_REWARD_QUOTIENT: 256 diff --git a/presets/minimal/deneb.yaml b/presets/minimal/deneb.yaml new file mode 100644 index 0000000000..460d845b67 --- /dev/null +++ b/presets/minimal/deneb.yaml @@ -0,0 +1,16 @@ +# Minimal preset - Deneb + +# Execution +# --------------------------------------------------------------- +# [customized] 2**5 (= 32) blob commitments +MAX_BLOB_COMMITMENTS_PER_BLOCK: 32 + +# Networking +# --------------------------------------------------------------- +# [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 + +# Blob +# --------------------------------------------------------------- +# 2**12 (= 4,096) field elements +FIELD_ELEMENTS_PER_BLOB: 4096 diff --git a/presets/minimal/eip6800.yaml b/presets/minimal/eip6800.yaml new file mode 100644 index 0000000000..25e6e19c66 --- /dev/null +++ b/presets/minimal/eip6800.yaml @@ -0,0 +1,12 @@ +# Minimal preset - EIP6800 + +# Execution +# --------------------------------------------------------------- +# `uint64(2**16)` (= 65,536) +MAX_STEMS: 65536 +# `uint64(33)` +MAX_COMMITMENTS_PER_STEM: 33 +# `uint64(2**8)` (= 256) +VERKLE_WIDTH: 256 +# `uint64(2**3)` (= 8) +IPA_PROOF_DEPTH: 8 diff --git a/presets/minimal/eip7441.yaml b/presets/minimal/eip7441.yaml new file mode 100644 index 0000000000..5588beb9ae --- /dev/null +++ b/presets/minimal/eip7441.yaml @@ -0,0 +1,16 @@ +# Minimal preset - EIP7441 + +# Misc +# --------------------------------------------------------------- +# [customized] +CURDLEPROOFS_N_BLINDERS: 4 +# [customized] +CANDIDATE_TRACKERS_COUNT: 32 +# [customized] +PROPOSER_TRACKERS_COUNT: 16 +# [customized] +VALIDATORS_PER_SHUFFLE: 4 +# `uint64(2**15)` TODO: will be replaced by a fix format once there's a serialized format +MAX_SHUFFLE_PROOF_SIZE: 32768 +# `uint64(2**10)` TODO: will be replaced by a fix format once there's a serialized format +MAX_OPENING_PROOF_SIZE: 1024 diff --git a/presets/minimal/eip7732.yaml b/presets/minimal/eip7732.yaml new file mode 100644 index 0000000000..d0282fe803 --- /dev/null +++ b/presets/minimal/eip7732.yaml @@ -0,0 +1,10 @@ +# Minimal preset - EIP7732 + +# Execution +# --------------------------------------------------------------- +# 2**1(= 2) +PTC_SIZE: 2 +# 2**2 (= 4) +MAX_PAYLOAD_ATTESTATIONS: 4 +# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK) (= 8 + 1 + 5 = 14) +KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732: 14 diff --git a/presets/minimal/eip7805.yaml b/presets/minimal/eip7805.yaml new file mode 100644 index 0000000000..2cc83a0940 --- /dev/null +++ b/presets/minimal/eip7805.yaml @@ -0,0 +1,6 @@ +# Minimal preset - EIP7805 + +# Inclusion list committee +# --------------------------------------------------------------- +# 2**4 (= 16) +INCLUSION_LIST_COMMITTEE_SIZE: 16 diff --git a/presets/minimal/electra.yaml b/presets/minimal/electra.yaml new file mode 100644 index 0000000000..22e26e4025 --- /dev/null +++ b/presets/minimal/electra.yaml @@ -0,0 +1,50 @@ +# Minimal preset - Electra + +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# Rewards and penalties +# --------------------------------------------------------------- +# 2**12 (= 4,096) +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +# 2**12 (= 4,096) +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# State list lengths +# --------------------------------------------------------------- +# 2**27 (= 134,217,728) pending deposits +PENDING_DEPOSITS_LIMIT: 134217728 +# [customized] 2**6 (= 64) pending partial withdrawals +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 +# [customized] 2**6 (= 64) pending consolidations +PENDING_CONSOLIDATIONS_LIMIT: 64 + +# Max operations per block +# --------------------------------------------------------------- +# 2**0 (= 1) attester slashings +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# 2**3 (= 8) attestations +MAX_ATTESTATIONS_ELECTRA: 8 + +# Execution +# --------------------------------------------------------------- +# 2**13 (= 8,192) deposit requests +MAX_DEPOSIT_REQUESTS_PER_PAYLOAD: 8192 +# 2**4 (= 16) withdrawal requests +MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD: 16 +# 2**1 (= 2) consolidation requests +MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD: 2 + +# Withdrawals processing +# --------------------------------------------------------------- +# 2**1 (= 2) pending withdrawals +MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: 2 + +# Pending deposits processing +# --------------------------------------------------------------- +# 2**4 (= 16) pending deposits +MAX_PENDING_DEPOSITS_PER_EPOCH: 16 diff --git a/presets/minimal/fulu.yaml b/presets/minimal/fulu.yaml new file mode 100644 index 0000000000..48b4d7a072 --- /dev/null +++ b/presets/minimal/fulu.yaml @@ -0,0 +1,13 @@ +# Minimal preset - Fulu + +# Networking +# --------------------------------------------------------------- +# floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments') (= 4) +KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4 + +# Blob +# --------------------------------------------------------------- +# 2**6 (= 64) +FIELD_ELEMENTS_PER_CELL: 64 +# 2**1 * FIELD_ELEMENTS_PER_BLOB (= 8,192) +FIELD_ELEMENTS_PER_EXT_BLOB: 8192 diff --git a/presets/minimal/merge.yaml b/presets/minimal/merge.yaml deleted file mode 100644 index 88aa86c09b..0000000000 --- a/presets/minimal/merge.yaml +++ /dev/null @@ -1,3 +0,0 @@ -# Minimal preset - The Merge - -# No presets here. diff --git a/presets/minimal/phase0.yaml b/presets/minimal/phase0.yaml index c9c81325f1..a4c8a5c425 100644 --- a/presets/minimal/phase0.yaml +++ b/presets/minimal/phase0.yaml @@ -2,13 +2,13 @@ # Misc # --------------------------------------------------------------- -# [customized] Just 4 committees for slot for testing purposes +# [customized] 2**2 (= 4) committees MAX_COMMITTEES_PER_SLOT: 4 -# [customized] unsecure, but fast +# [customized] 2**2 (= 4) committees TARGET_COMMITTEE_SIZE: 4 -# 2**11 (= 2,048) +# 2**11 (= 2,048) validators MAX_VALIDATORS_PER_COMMITTEE: 2048 -# [customized] Faster, but unsecure. +# [customized] SHUFFLE_ROUND_COUNT: 10 # 4 HYSTERESIS_QUOTIENT: 4 @@ -17,13 +17,6 @@ HYSTERESIS_DOWNWARD_MULTIPLIER: 1 # 5 (plus 1.25) HYSTERESIS_UPWARD_MULTIPLIER: 5 - -# Fork Choice -# --------------------------------------------------------------- -# 2**1 (= 1) -SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 2 - - # Gwei values # --------------------------------------------------------------- # 2**0 * 10**9 (= 1,000,000,000) Gwei @@ -33,38 +26,35 @@ MAX_EFFECTIVE_BALANCE: 32000000000 # 2**0 * 10**9 (= 1,000,000,000) Gwei EFFECTIVE_BALANCE_INCREMENT: 1000000000 - # Time parameters # --------------------------------------------------------------- -# 2**0 (= 1) slots 6 seconds +# 2**0 (= 1) slots, 6 seconds MIN_ATTESTATION_INCLUSION_DELAY: 1 -# [customized] fast epochs +# 2**5 (= 32) slots, 48 seconds SLOTS_PER_EPOCH: 8 -# 2**0 (= 1) epochs +# 2**0 (= 1) epochs, 48 seconds MIN_SEED_LOOKAHEAD: 1 -# 2**2 (= 4) epochs +# 2**2 (= 4) epochs, 3.2 minutes MAX_SEED_LOOKAHEAD: 4 -# [customized] higher frequency new deposits from eth1 for testing +# [customized] 2**2 (= 4) epochs, 3.2 minutes EPOCHS_PER_ETH1_VOTING_PERIOD: 4 -# [customized] smaller state +# [customized] 2**6 (= 64) slots, 51.2 minutes SLOTS_PER_HISTORICAL_ROOT: 64 -# 2**2 (= 4) epochs +# 2**2 (= 4) epochs, 3.2 minutes MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 - # State list lengths # --------------------------------------------------------------- -# [customized] smaller state +# [customized] 2**6 (= 64) epochs, 51.2 minutes EPOCHS_PER_HISTORICAL_VECTOR: 64 -# [customized] smaller state +# [customized] 2**6 (= 64) epochs, 51.2 minutes EPOCHS_PER_SLASHINGS_VECTOR: 64 -# 2**24 (= 16,777,216) historical roots +# 2**24 (= 16,777,216) historical roots, ~204 years HISTORICAL_ROOTS_LIMIT: 16777216 # 2**40 (= 1,099,511,627,776) validator spots VALIDATOR_REGISTRY_LIMIT: 1099511627776 - -# Reward and penalty quotients +# Rewards and penalties # --------------------------------------------------------------- # 2**6 (= 64) BASE_REWARD_FACTOR: 64 @@ -76,19 +66,18 @@ PROPOSER_REWARD_QUOTIENT: 8 INACTIVITY_PENALTY_QUOTIENT: 33554432 # [customized] 2**6 (= 64) MIN_SLASHING_PENALTY_QUOTIENT: 64 -# [customized] 2 (lower safety margin than Phase 0 genesis but different than mainnet config for testing) +# [customized] 2 (lower safety margin than Phase0 genesis but different than mainnet config for testing) PROPORTIONAL_SLASHING_MULTIPLIER: 2 - # Max operations per block # --------------------------------------------------------------- -# 2**4 (= 16) +# 2**4 (= 16) proposer slashings MAX_PROPOSER_SLASHINGS: 16 -# 2**1 (= 2) +# 2**1 (= 2) attester slashings MAX_ATTESTER_SLASHINGS: 2 -# 2**7 (= 128) +# 2**7 (= 128) attestations MAX_ATTESTATIONS: 128 -# 2**4 (= 16) +# 2**4 (= 16) deposits MAX_DEPOSITS: 16 -# 2**4 (= 16) +# 2**4 (= 16) voluntary exits MAX_VOLUNTARY_EXITS: 16 diff --git a/presets/minimal/sharding.yaml b/presets/minimal/sharding.yaml deleted file mode 100644 index 6b8d223b41..0000000000 --- a/presets/minimal/sharding.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Minimal preset - Sharding - -# Misc -# --------------------------------------------------------------- -# Misc -# [customized] reduced for testing -MAX_SHARDS: 8 -# [customized] reduced for testing -INITIAL_ACTIVE_SHARDS: 2 -# 2**3 (= 8) -SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT: 8 -# [customized] reduced for testing -MAX_SHARD_PROPOSER_SLASHINGS: 4 -# -MAX_SHARD_HEADERS_PER_SHARD: 4 -# 2**8 (= 256) -SHARD_STATE_MEMORY_SLOTS: 256 -# 2**40 (= 1,099,511,627,776) -BLOB_BUILDER_REGISTRY_LIMIT: 1099511627776 - -# Shard blob samples -# --------------------------------------------------------------- -# 2**11 (= 2,048) -MAX_SAMPLES_PER_BLOCK: 2048 -# 2**10 (= 1,1024) -TARGET_SAMPLES_PER_BLOCK: 1024 - -# Gwei values -# --------------------------------------------------------------- -# 2**33 (= 8,589,934,592) Gwei -MAX_SAMPLE_PRICE: 8589934592 -# 2**3 (= 8) Gwei -MIN_SAMPLE_PRICE: 8 diff --git a/presets/minimal/trusted_setups/curdleproofs_crs.json b/presets/minimal/trusted_setups/curdleproofs_crs.json new file mode 100644 index 0000000000..b9b6ad5b9c --- /dev/null +++ b/presets/minimal/trusted_setups/curdleproofs_crs.json @@ -0,0 +1,20 @@ +{ + "vec_G": [ + "0xa44aae199242e24a4d00b8e5c96e318793eeeb2e154423ca6dcac20043387323dea3216a69bc13e9a3507ff842da544d", + "0x8dff68d38281daa552c587d073e498d1ed311986967192cba052827b07f194a936809ea3de921511db45d15234754993", + "0xad0ff210542fc069d065b53b2cd4228a0f097facebe089a83b989fd3344c53e440ded5da26bc6115299d9d464e1a9f28", + "0xb638e703f852d2ac49595141f7688c52a95dcc0b00f82a8548b14d823ebffe8657557ed7dab6bee44b17d39d742f69aa" + ], + "vec_H": [ + "0x9377c7771e07a1a9b9368796ce1a1b93d560d7726afde02627b424ee1dcddb3761ed49f2e1ae5279dca050935bd4a6dd", + "0x8d1be282936392c0c61c94745cfb29da0f3334272c9b37fe43c8b869640eb1ea88c8aaf5ff797bd90daf3d6ebeb4efb3", + "0xb3b55847d3bcf98b587c4441b0703939b5984bac91b00aabb5f3a45b38445405b61127bc6ee9f6b4b9e88c7a29c3aaa3", + "0xafb61afb9f92c37ec21220c02edf14876d2a08eab8ad3c2bc1f3bfe5036abfd23a4a7216616fa1953e14340bf0acab37" + ], + "H": "0xa94133ee96e00465fe5423e0ea52404e0f624ee8cc9d69b4cf94e7d73635dfa2087cd2d2596ac4a75504aac5ef6a02d4", + "G_t": "0xa4b93e670e7ee926ffb4ea4e07b87346e5d33c76520912f8a7937cdc3290a4c054586e175c39826b7fafbe777d14e4f4", + "G_u": "0xa57540f7c906d9e70ef90580967562f8c06398ac2d77612045dce0ea6fbc2fedcfdbeb3f6ad3bb717e1295d9539ede63", + "G_sum": "0xa0d97028d7194094fe1c4f00189e360ae362eca4aa9dc8f92eabb8dcf0d93140a81953d4505cd7dc592504710d696ef9", + "H_sum": "0xaf415fddfb82e7cbb91fae0c443425c51dbb68e05f0324bd2d79e40b923ecb4f806e96e9993eabadd1c39ac4e12e74bf" +} + diff --git a/presets/minimal/trusted_setups/trusted_setup_4096.json b/presets/minimal/trusted_setups/trusted_setup_4096.json new file mode 100644 index 0000000000..6793490e2e --- /dev/null +++ b/presets/minimal/trusted_setups/trusted_setup_4096.json @@ -0,0 +1,8265 @@ +{ + "g1_monomial": [ + "0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", + "0xad3eb50121139aa34db1d545093ac9374ab7bca2c0f3bf28e27c8dcd8fc7cb42d25926fc0c97b336e9f0fb35e5a04c81", + "0x8029c8ce0d2dce761a7f29c2df2290850c85bdfaec2955626d7acc8864aeb01fe16c9e156863dc63b6c22553910e27c1", + "0xb1386c995d3101d10639e49b9e5d39b9a280dcf0f135c2e6c6928bb3ab8309a9da7178f33925768c324f11c3762cfdd5", + "0x9596d929610e6d2ed3502b1bb0f1ea010f6b6605c95d4859f5e53e09fa68dc71dfd5874905447b5ec6cd156a76d6b6e8", + "0x851e3c3d4b5b7cdbba25d72abf9812cf3d7c5a9dbdec42b6635e2add706cbeea18f985afe5247459f6c908620322f434", + "0xb10f4cf8ec6e02491bbe6d9084d88c16306fdaf399fef3cd1453f58a4f7633f80dc60b100f9236c3103eaf727468374f", + "0xade11ec630127e04d17e70db0237d55f2ff2a2094881a483797e8cddb98b622245e1f608e5dcd1172b9870e733b4a32f", + "0xaf58c8a2f58f904ce20db81005331bf2d251e227e7d1bef575d691bdca842e6233eb2e26c2e116a61a78594772b38d25", + "0xb3c1313c31ec82da5a7a09e9cf6656ca598c243345fe8d4828e520ade91787ffb8b9867db789b34ad67cef47b26ff86d", + "0xa8ed8a235355948e0b04be080b7b3e145293accefb4704d1da9050796b2f6870516c1ebf77ae6a65359edcfd016c0f36", + "0x80e792d5ba24b8058f6d7291a2ec5cb68aab1e16e96d793128e86815631baf42c56b6205c19e25ce9727bd1fd6f9defb", + "0x816288c5d726b094e3fdf95cb8882f442c4d9d1101b92c7938a7dfd49bc50636d73ea1b05f75eb731c908c8fd8dee717", + "0xae009128d128ba2e1519bfa7a0c01ed494a7d461c3aba60f8a301701fed61fe4e31d6c79ce189542ae51df91e73ce1b3", + "0x96a866d60a9007d05825c332476a83e869e15b11d7257172a67690ea9bd3efea44bf9c8d42191454eb04fcf110b16396", + "0x8b250a2a06419adb9b611e89f7f8f2990aa301949b533ad3bf17c4a61ab5f5be0b1d5e2b571864d13f1bb75805c7795d", + "0x8450f49facf2e620fa45ee90e1801178842d927a2a25fc6ed7ba99a4eec7ae40eebfee41028eaa84f107f4a777694976", + "0x91049080cf659c0985a22d1366e59191bb89663f922e8168b9b7d85c8a73d74a6d9dceefd855d3d858b493670c750581", + "0xa1e167aeb2008087f3195926f1985c0a459d6ec57237255b1473a96de4e2c1cf766127c862c7dc853a6909e67cb06cf7", + "0xb667c0d4e26e20698b07567358625d5f003839c92de8088e12dbd74a6f6a3156b4ea8d252c9ad62af5f6c4fec1cf6cc7", + "0x8e4b5e304c0b1b161ae3e4b68b5e3ac66c42acd7c1ee2458044f6527c508a93995e50894d72d57c1350f91afe72775ff", + "0x8c642640aa7915421cdc21fd639f88a42052b1cfa358ff7702e60793a92b7b5926dae15a0c8f8f59cd3013f01c159ba3", + "0xa356f35e713cfc283056bf539de54a21731e61efb4c47319f20de4a4b723d76a33b65f4a67d298b9ec5c2a1579418657", + "0x93ce204146ce95f484dc79c27919a16c9e3fc14a9111c6c63d44491158d5838117d20851cc3227a5e8ba6ccf79e77f39", + "0xb585664cbb9a84b52f89114e1cf0cf1171bea78a136dc1404ac88a11210b2debc3b7a55e702da93ff629095c134a295e", + "0xb6dfd444ec7fdceb14c6328f26ca12c3f9fc4327d8d8c68948e92e7e61262b82d833a65a9e3af6353ffa832b6da25705", + "0xb4d4b8eb9ecfffe3f0d48fb4149c7b31aec1da7041ec03bd0750c52a2a7cbc3a7cfbf09d5bfdc56e3860826a62d0bb91", + "0xa4e248e3d61db52da9683fef188579c470d65e2df9064726847b1599fc774049ffdc6ef2ae578d5ed7874f1298ecdf69", + "0xa68a0fffc2e37d3183feb01b42234c0f4e510f9dc29d09c571e6da00fecad9da224cd0f31550070148667e226c4ca413", + "0x86adda2ffecb77236c18005051f31f9657a0d50fef2a1175dfda32e74d5d53df825c10f289eb0ad39df0c64fc9bc7729", + "0x998266d5c9c3764ed97d66fa9ed176af043999652bae19f0657c8328629d30af453230e3681c5a38e2f01e389ed8d825", + "0xa05261554d3c620af0c914cf27ab98f5d3593c33ab313c198e0c40d6c72022eb5943778cd4f73e9fe8383392a7004976", + "0xad243fb3631bf90fedb9d679fd71fc0cf06bda028591ded2bd4c634ea7b3c2bd22eca2ab318fcdaa6c2cda1e63e1c57b", + "0x89b9859a04f903c95e97fb2951f01cc6418a2505eee0b5bc7266b4d33e01b69b9fe7dc56fa9ebb5856095be0925a422d", + "0xa68d118343a5bbfbbab95ff9bfe53aeb7fdbaf16db983e6f4456366df2aa01fbdb6ee9901cb102fc7d2bd099be2f1f3e", + "0xb49301f25d5a9dd2ec60ddb0b4b477291958487efea9e54dc0e4ef388f03b8bbadd13259d191f7a0b7513876767d8282", + "0x8b93df7fb4513f67749905fd43db78f7026589b704ebb9ea3255d0ad6415437799f40f02e07efccda1e6fd5e8cd0a721", + "0xad88769ace96455da37c3c9019a9f523c694643be3f6b37b1e9dcc5053d1fe8e463abebdb1b3ef2f2fb801528a01c47c", + "0x80f0eb5dcbfaaf421bf59a8b9bd5245c4823c94510093e23e0b0534647fb5525a25ea3aeea0a927a1ee20c057f2c9234", + "0xb10ad82ea6a5aeabe345d00eb17910d6942b6862f7f3773c7d321194e67c9cced0b3310425662606634dcd7f8b976c04", + "0x82f6fd91f87822f6cc977808eeac77889f4a32fb0d618e784b2331263d0ffa820b3f70b069d32e0319c9e033ab75d3b4", + "0x9436d3dc6b5e25b1f695f8c6c1c553dab312ccace4dac3afddc141d3506467cd50cb04a49ea96ea7f5a8a7b0fc65ef37", + "0x8e0a9491651d52be8ebf4315fbbb410272f9a74b965d33b79ff1b9e1be3be59e43d9566773560e43280549c348e48f01", + "0x8809137e5d3a22400d6e645a9bd84e21c492371736c7e62c51cef50fee3aa7f2405724367a83fd051ff702d971167f67", + "0xb536a24f31a346de7f9863fc351fa602158404d2f94747eebe43abf1f21bf8f95a64146c02a4bec27b503f546789a388", + "0xb5cdf5a04fc12a0e0ef7545830061dff7fd8abea46e48fbe6235109e6c36ee6bffcb9529e2f3d0d701cf58bbfb6a4197", + "0xab15377525753467d042b7931f66f862cbbb77464212c9aa72d4e5c04375ef55f619b3a446091c1ba1a3b5d9f05e538f", + "0x905a75b943ad017ff78ea6ddd1d28a45c7273ee1c2e5e3353685813793ead3370c09cabd903fcab9d8b1c6961372d486", + "0x8147df4324faddc02fb0896367a7647b719b6499a361aecfdd3a34296fa6768ad31c34f9e873fd1e683386c44651883e", + "0xac91d08570dd91f89d2e01dca67cdc83b640e20f073ea9f0734759c92182bb66c5d645f15ebd91ed705b66486ed2088d", + "0xac6295ef2513bbea7ef4cdcf37d280300c34e63c4b9704663d55891a61bf5c91b04cc1d202a3a0a7c4520c30edc277c7", + "0xb604be776a012095c0d4ebc77797dd8dec62a54c0559fb2185d7bac6b50d4e5fd471ac2d7f4523206d5d8178eabd9a87", + "0x80ead68def272ce3f57951145e71ed6dc26da98e5825ef439af577c0c5de766d4e39207f205d5d21db903d89f37bbb02", + "0x9950b4a830388c897158c7fe3921e2fe24beedc7c84e2024e8b92b9775f8f99593b54a86b8870ec5087734295ba06032", + "0xb89ba714adabf94e658a7d14ac8fc197376a416841c2a80e1a6dde4f438d5f747d1fb90b39e8ea435c59d6ecda13dea1", + "0xb0c78e7cc60bd05be46d48fbb0421a678c7f14b8d93730deb66fbe1647613b2c62b5075126d917047820c57fc3509cb9", + "0xa860c4acc5444e9ae987e8c93cb9a5f17d954d63c060cc616f724e26bc73d2c54cd36e0492d1fde173847278e55942ba", + "0x8fb8269c9d5c15428e8d45da1251e4c4a4b600d47da0caea29fef246854d8fb6acae86a8e6440d0c429d8dd9c2dfee0c", + "0x96c5d8eb6fd5c525b348ee4335d200139e437e4be83690af0f35b7f336a7cda8c6d2958647988b84da9f2dd7bbb7710b", + "0xa7f62141c4346cc14e9823dc38ac7d587b0427022afc1498d12ee2c43f6ac3a82167057e670dd524b74137f8c3ceb56d", + "0x956aac50d06b46a3e94397f163f593f5010d366aa2d816c2205c7d0f47f90cf0f36c169e964f9bcf698d49182d47d91f", + "0xb812899bcdc0e70d79ca729cb01104bf60e1357b9085a10f64f3ba9865d57e9abd0a505a502d4de07afb46f4d266be2f", + "0xabce02c7e1372e25d40944dc9ece2904a8f59c8854c5f2875fe63ace8ce37d97881f4f9ab4f7bad070ec8e0daee58d3f", + "0x8fb13c515b2d6abb4e14ed753fad5cc36c3631dfe21a23d0f603aad719423dd5423157eefcbd9a9c6074e155b79eb38d", + "0xa9ef67304dc297ab5af778cf8afa849eeac27db4b6978963e97b95ef7a8d3264d0d07775f728c298a2b6daed2ecf5053", + "0xa9b975520adb066e2ff2a4cde53284c23bc84261a22dc43b1634d99eff8e7892e46bb6e6da7319c9e72788aa9ea7a1ea", + "0xa6eaea4ab4206294474d9b956d9d3188d558a5633de2bd05df0d3bac03dbcbe4ed85406349c1d2e660b77c6da1f5bf8c", + "0xaf4a19f77290dddee762e1e0d4bc9945aacea3f75756ae46cd3e58a8f74d1b5db73e4834687946b0f39191e32f2fed0c", + "0xaafa6523f58f1a4cabc924c86d842816d606afeea21fa4b2b8b9573425810fdcc41c98888318e868f9c05e2be12178a3", + "0x8ef38fba0a3fa4ebe985239c8b759c22aaef0c57e6f39050a651c869487803b0d1e389c3d958fb5a7f37740f050ac69e", + "0xb07dfc9f85913c608ca7596a2e361f05e4853fad00e796fd492d247de6414892ce160f627669b1ba933b6ad726415d4e", + "0x94da679ad1d78b2bff5283c938f17b2a7d6e9cbcdf59d340e6dfb652951c7a9e852ac0590f99cfee9631b9410f6f00ea", + "0x98a907c9c021a5b034d3720197c160a82c4b7146cb73d48efeed99b9d0c6b831812cf80ac7e19e85a676a8cd3ead72de", + "0xadb746595466a12929019d0048cea33236b05c1229d2eba73b259a18a786f2bc3f05fc0598d8ce253cecb80bdf679aaf", + "0xa2fbac016996d68f9027a157b0a3f6a336144a798d6113adfcda3a5d05b62c31f108f112aa915906aef22b7f83b9228b", + "0x81841dea1904406d1b6fa49b4b3f7f6cb40b7646cf44d36c9fa07e3dee29f8e47324b40d8356ddf653109673c3374e9b", + "0xa3edbb8aac5e60c775775cbdb19067341b2e2530de48738e84c2c07151241ee31f0d8333bf20c2bc9dcb7b2e638a6b5e", + "0xb8aa6890e22964828787ce86460d3a32f12a655bb5c28de500f2fcf6b61e3334640ec6ba96029a4912af0d18df4b4139", + "0x8ca43169f04243ad0fdb0152de17c60d9e31ee0ab520970fccd98590e05508821a183b4b367967e60d53c2c826ec5dbd", + "0xb179fffd9df8c00486c5a8b9327d599f5a11745ef564f06e126849b06fe2f99273c81f65bc941efb0debaadfecbfec1c", + "0xacf068f1c2b1926279cc82750ce21b0d6b0bfd0406f0d8bbfa959bd83935932957c7f6b8de318315bf0b75f6ee41a0f2", + "0xb97831da260919c856e9f71a41687f5979bc16f8a53b1037285b4a2f9ce93af5cfe70bf0ad484744827fb55c847b58eb", + "0xaff50b0bd907383b0c241727af364fe084d021221bfb1b09fb6c1a7752eeba45d662493d590f1f182764b90b25f17906", + "0xaeeef044c14e3ad41e1235c9e816e1eb49087fd3abe877b89b3bade74459186126e160bb569bcd77779e701b19b5f71a", + "0x8483deb2b7001ca7c438fcdca8ca6aba96c9cbc4becfd9b16a6062705eae270011bcaedcae69bb54630d8c78129e57c7", + "0xaeee8d24be4ac0d9784c029e239fb5e64316ce29b88f47394cfaaa8bb966a72061bff72f99d02dc51c9705854686e77f", + "0x90ae09525a16bb2422169e15d6831c87968a14ebc0d1d27e11a759839c73c655b9d33ee5b12f275d6f440688146fbd2f", + "0xa3a41fc7fefef101422465e506bea7f3ff23c26fe35f5732b86f5f2471fb93b37ebc339f84c6be1e8d22abc812c2e212", + "0x86f4b5293e8aea4af1f1fb05dcf99714cb3aff1cfc849b1bb73524061c921c9da9ad92579a852e1889da29d952f02fe5", + "0x8932ef39d4050a1e9dc0fd8afeaf159472d71c5c27f458c69d2730836606ea56e19c8c4febf2535f930d3260e9bc7637", + "0x86307b9f3696bb21c20e4558e30310389e7367803c353d437e9b696039a0ff054d9a4953b75237ab1d1dd6f71118c189", + "0x96e57730e683ef5b550c91de18b19ac73879f3e26234297db68d28747ed0953beb0f3913cfb720c602720bf9330685d8", + "0xb04a19ee70123782e47b238abde55baf60ac0c66292a998af0d14afc8bbeb1134e557b94cd17a020084631c09a0d3c02", + "0x829abc8718be8139569fcb2c398962f38f4201114d30e2b2fb23566f8a27a5c380f5605cec543415202a12ed859e33f6", + "0xa0744fa488c8fa92a722c5fc4ef5a47dfe824eccd87d26c8bab9c174cbb151d44b1b29082c48652f03d3177e5ec86001", + "0x81d4035ae9fd28bdcd78b135cb54955d3b685a527319df6ee7e904b8e6d796f5f5a5f5035ee1de750c4cb6050e452b9e", + "0xb205e8c2ec24d7104fa0106c09ad34b5a912c1adef553fb718838dd627355993c2ec01055c11d00b2c75b68e9516d44b", + "0xb12d09da7968fa7394e449624fc7174d1d76c069ccb03e140d4d87a2d3f6d1f7b9cfc930f0c80becc673406ebe63f08e", + "0xb23752c158695da85048fdf38b395681cc0e8998630af8a9ed41efbda08c9964c2dc8ae6e53377264be4467d702c0de4", + "0xb0d84582fd73628d96b8c1ec96197697c41a963542451a2ade0890af0d33c7161d0f18e1a1ce2c168ca2dc1e9119d55e", + "0x8b877e618b469aa187632e410b125d2999d5738fd66d482000706b51fd904a0c7e7daa8c9b729fa33817bbc4154cba2a", + "0xb1cfc8a7551b601723b937d497d01dec3ee7614c2bf13d430b1058d5ebc1406045009ff02c2ac15bf8cf16f860193d1e", + "0xb6d9da84f97b21e13175bbb0b5cc8e79e88b470c87a3e115726c1bd98e0288526c58f3faaa8aa170ace0cd6a60852525", + "0xad2e773c2d527671ca5fab7085dde4da31cd35f45d4315dd95d8893ff5fb900494dca08eccfc1a2fc7bf7c7fd2fcab97", + "0x8d5a79b34aeb761d4a0c73f09f02e9548e6d382c33ee6887a759ab05762b490b8a549ef2933c7e3a46415c154c0221c0", + "0xb6f2cbe81bd0a7298403be392f8456bed30aed7ef30216959357698f789affd2942ae5fbaf3f48ecebeb7c273b20cb57", + "0xb5b6c45d99cea7ce6a1dc134aff4a8f630f299b42bd59592a7592345f8cd35bcbee944e61b0723de732fcad6e4425b63", + "0x8077d64dfcb2418974e956ea6dbf8a4c05b25d2a025333ad7e2a379f1976dc036771403383a51bfa3476c9c619ef8bef", + "0xad2e0a9d479c77a5fb73b3613a177fdaad50dcb50fed50e756ba18164c153af30b07fb2565e80ff7469f1b0338b7b5de", + "0x81017d1d80a6b6df4e99d0d7f85a8180b5523e8fa2ea2672fddff604933f8a113cab27fce098dcb454d7d1f7ed266e04", + "0x852355479d68e76c7febf6dfe2ef8e80d575c0d3bd52c983803592021cfa898c571c0b884412c21e66f0dbfe03167b53", + "0x98e1bf8ad48421467c93b9f72b47dded7c41b4fcd36ea55ca43ab24b0d0b876f5a731f422579b7167c7138fad2121266", + "0x803369314abd5422019ed4b0ef652b4dbe97ef5a87b0ea373eec9628b64a12120b2c3d4eb53db405131ff786d14c7ac6", + "0xadf2613fc34f73e1160975c140e925ed84d254e03cc3bc7fc1d19957b499c9ba9d9e4c1639981b594a7095c0a52c6757", + "0xa2f6a68efdff6e4173c00692abcfdfcdaf6f8b62369afad3dafaae4f2f38c4860780b4624d185e20e4f4498b75b5fe94", + "0x8b1658aa0e119fb8401d486ed08d60240d26a8623ef9788e3b45ad09ae31259395b021bd16be395139cbb7149714e764", + "0xa7dd8bf21121285e00672ee8bb84e0cb39b2496fb53a26e35dfbca7f2b04e9a9ff9db15f53fe63fcbeafeb2deeaf2ca4", + "0xb6d8d709e44bc18f3b41d69608edce60c02bcba48d3b7e2fd420842657f0665a7343246dea149a25e8f3416284abae66", + "0xaaf744ca5e9bcb63e3e2939b7a1e96e4a93c88c76bec0cf4294dd7db95cdd3f6a7d92196e352d08680e2328bc4592899", + "0x84434b015a7c398d35f1ec71fce455d62ba4ed4f62da042ec31bb2b4db47073314354cd50bc322297a1cfe35138bf490", + "0x8d70b3a3cd9d5dfefdacfa418c0b775a112a47ce538d33a560a519660009c3f141fd6221c18539129e9c0acdaceeeb80", + "0xb8c6903412a800ec78a4c15f31c24385a267b0c0ece32fd31bbbb557fd70c3b2d60d8fc0f90fbd70f43baa1928ea30ba", + "0x8e391dd445ea06cabb433f057853f8159511b2f9bef41aed9ccd14e0a6fcd912bbaebd38fd5fb736cfde0fa34b7a4874", + "0xa40cd988f70613df32babbd1bbc2f1b29ff1ab0147b01161555a81d56c9621657999bcdb1df38485f687afc51d5d0f23", + "0xb6a008b4426b3d7b28ae04eee4698fc8ef6a35d89008ef5394da39ce582ce1a45dcfae9a33b90f6fa4237f3667803873", + "0x8987280debfb175c3b44a2f152ea82548e4f680966f1fcbee9bf7d714e31bf8080c33f52705ef3aeee70544b22516aba", + "0xa78a51a2c11eea7680a5a0ae417a2981f8c69c396e06da621eadd7510a3664ade49d065617bec67b3de779548a4f4509", + "0xa4d9163f0a1bc048385e94d5e0bcafeee1b18f28eb23505623b9e8ef16f3df76408254dfbe790e45f2884198060d388d", + "0x83dcae2568a0c518793c0f6e38b42f9ceb50673d100b556a17ec8bd9faeec84afe50b8d72422c6b2356959667bb8e2de", + "0x874731941be4474b4576226e5906b5dee89fc9b56a9870dcc7289c1a7d494d345ba6aba31f7546a16f9963283c05f744", + "0x82c1cfab1f501189ac20147fc4631075dbf1abf9125b7d42fcb4f31cf73f3d6461b1bd08fdf6e45cc54bc08a7d5d51d1", + "0xb978228286f5d4a10ce027b6bea3021affcaa805340ca4b5192c69e8c56db59f48e4a14a284ec015f53baf97389f62b2", + "0xaf125f4fdccd1c1b64fdffecb5ec7cf8c7392bbe476e1b89a5b5329c5ba4a526e58c11e72ab9de8a38d60af648d75adc", + "0x8411a41ec14295acab0d36389013535a80dfff6e024bffeb32fb3070762f61256419e8c51b2ad6de9dbe4f1e8e286912", + "0x8ea67a91112a41f9c65515cd496f4b0cdefa1400fc06568eef000c9eae6dc250fb7622eb3f2deca10b37287cd96fa463", + "0x8da99b6c55c31dee6a49aabb54da249d348a31d4416201a10c45a3b04b11e99d4ae9813632f0ee36c523b5cca62f6f49", + "0x8b44656341e039e2bd83a19c3bb9a88f6209482e274f8cd4f8557b728e5948dd80b5745f621b96f4562928689314e8c2", + "0xa02d424a615ba0dce8ed91f477e79852215a3a39d025059826fa278e7eebef19824b2a2844f5b3865a0f471b609a23f5", + "0xa1f115cebc3fff3bcf233da27cef19eae791660f155d088003460f75567a550bef0722885010ddc384acdeac635939dc", + "0xb61a55ce9d143c17876776e064b58a10baf0ba13553c785c1e47f57b5f94c0cda8bc89d43d73386e57816c15b61a8ec8", + "0xb4073f47041e20a8e548c7fb00e07ba3b9056c34eb4ab63bb0e7b48f8e338e8b56a17611a1b5f4c03b352450b86f1d69", + "0xa7b1a07b213205b682fc5b6acb7e76fdf97b280c26621d8f3b76b7c1deb3511957da33a4e358c8e8f3d98b2a8855d67e", + "0xb797e67c2670fbd9844e8a68c585f404b035dc14bd4ec75c3f95f932c777f9db5d5f5df7629164af488fc1213035cc5f", + "0x99618200797b945f595794d6468e5c618649554ad9ba896330f1cc844090eb956ae9fc23132912f9047085c5f0c3bf7b", + "0x81194aa1319abf534cb3927af9adfb178a99d0e3e8c99ab1105f1d3b4fed40ec2971caf1d6647acb0c8d681eca53097b", + "0x80673f18e4978dbc226a6cd4b128a1259d9a7f833879c6e2fbe24d69fef2c3c23a51a4f3e8d88fa4533434bbb0723661", + "0x8125bf6c7dbb2fb63aaa3f53283559f172c788223674adbeb6d5bd17cfe888e6b87a79aec774917f20ce911c1f85f8e7", + "0x884bcdb1878b14fc38adc9fb8b4dd0b3afde404fbeb664f26ddfebc81736018551f23e75ce4cfe4865f610bcd454fbd7", + "0xaec65c8d4be8316e98aa54888af01bc6703a0c5d04b69756ff39a0a947b66817ec59d76afe9f61a25749b5e890f03e02", + "0xaa457aaa1b014a4c5a8992847a187a23321bb43452c98745987d038e3b04046102ae859b7a8e980eea978a39d76a88ef", + "0xa9832ee63b08e19123f719bfe2fe742125f32463efa966c7709a98ebfc65277670e9ea1fa2d2d78b96bdc7523b0c4c3e", + "0xa87b6b1b7858f96d55064274f29fbde56067064962cf3c3e2ba3110b22ea633bc037a74d23543ce3307a46208855d74f", + "0x897cbe4ab68a753020fec732dfcc052c7ed9905342b5a6fe0aa25c631f9ad9b659e0ee75d46f0df6507b6720675ee28c", + "0x97c3b5f0d54c1fc45e79445c3ff30458959e406a069f5bbf7979d684195b4fa0406b87c1c008f4075bc9e602ed863152", + "0x921e65d582ea9322ddfad1c855331c3cac81f53c700b96db5305a643c084eb6793094e07944bfd41dc02c3b3cf671530", + "0x8f23ef1aca02a260a3b65d25b110f28d3bafca44727448c8f2d03c5e77eda620c1721b06681bd816ee6027664d76352a", + "0x946a89b132ec0795aea9ff9dde7b77e7feafffe6e4a2f093042a7e6c71cd6ab87ce0ca914a1b5fabad4e1f96a795f163", + "0xa01e2de9db33df6511172123ad6f7c64074237471df646b32dd9aff8c15278e2723108e4facaedca97e9f49503f8c792", + "0x99dcdcde45b2ea3f15279936feede5f7d3b63ca4972f335b0559c2fa6f9faabd8127aa892a36deb114357ca906553ed8", + "0xa3f8af37bfcf66b04d1896a4bd5d343f4733d4c3305369ac7e75a08f20f2004c10c642d2c7577f4e5c4d1f2cd851ac3b", + "0xb7294d15a3d674a56099f97a1adc9e82c15e90832eaf1722df110fc2abc8634c51515e5ad8522015498a3753b1fa8c49", + "0xb4f27f5062ba7a04ea0048b3025b5e3d5b5d319a9e80310c808a5fb4e8e77b38c10a0f3172cb805cadbcc8bc66d36ec7", + "0xaefe5decee0ae2dc372cc6cf4217daf97c4c908d145f100f0daf1ccdfdf641c78432c2e473e7e4b77dcdf2d4c2bb05f0", + "0xacc84af7648a535ffd218c0cc95c8f7b092418c548815f1bafc286b1fe14f6ccb51b2044db3bff864d0bb70e88604084", + "0x84d8e3dac0df6a22beb03742e1d4af684f139f07e2ea0f7fb27fc2d7d4f1e89b5e89f71af32ff115ed5e6092133535f0", + "0x8ada001e1a03a823c4c056f636e77adc0f9dc08689d28de0d99e0feecab5db13abf37b41ec268dbdb42c75419a046c68", + "0x87dac6c798d1744dff81d8bc3e0e04f3c9bf260e811685ddb9a9a8d6eda73927439b344f9a818d2103fad633de5a4a17", + "0xad9929a7d8a7d5d5954e48281a87e5c84f67e19110d73296b9989a09c76767a57a8115629239ffb4d99dfdf9c52ef6d9", + "0x81ac7cbeef8ec35a5c3b61cc887080c29e6cd3e08af37e45830d17400dbacfb374dd07bf370b979828c3875b2027d5c6", + "0x97f92c9182953b7e10f7a1bbb6b5b5c40b8275eb5a6eec1e29874c4712814749aa8c409651380216e1ff01d7b8511041", + "0xa09794d0bbe7db013045d3fd857c1544fe6231d21afa3495fa300371f6301a3a0f4b8ea175b281503dd06078ff371ae4", + "0x839bb58d320aa08116dd387a57a2b9bd9efc89c4cdfd82d0e47a00cabe644631d09be5436bd485df3b61b75ddf81a3ef", + "0xb1cdaa344f783757e8b9c1f84421da3c5be4c69f019a8fd4c1aa5bf1a63e8970c99e35c22cf3b48a0e6738bc6ba7ce8d", + "0x92af68e3216c78998208fb24b5ba0e645d0d3f5e28222b805668d7e9cdd6c033d3b22fd6df4c2d745d7f910d133cd226", + "0x87640a4ea4e605e2204e5232b29a6c1c31152d83547eef14122cb76a0da52b8653801af48455a3ed713b9dcfee7b1ef1", + "0x8147e5bf0c8f4731155ca0517ef3fae5a32b4d5d2d98ed0007b23893d8dbb7f8a1199c50c1750c2fa7c9cebe594b1bb0", + "0xa76b4473c63c3ab6103c729afd2482822e4150f3155af39983b0ff0766c71cb622455ce6304e23853661eaa322219d18", + "0xb3e2f05ca551bc3adec0067e4034aaffd72e0b64ac18ae25452c996927976c6727966e26d213b032521889be2170800d", + "0xa8414cd14cb3be658e9e0004ce511ef7063439b1cbc3166a11de030613fde4b59caad4e91d426927863c55382afbf476", + "0xb2f0f8ab99f4d0ea785ac84fdbc00b20217b1df59b30b51d9d209d489d53b69dd5d82cdacc16fd1dd15c3a4001595f50", + "0x8b2025d5fd658c9bbed619f3e3f6ac8efe7aeff8aa9401bd66a7ceb0062c44b353608ca073f95be99204f0a913bb77eb", + "0x94a46bc5a87291b42024b2137e623c70115b9c6b196604106bfbfa20f3f56ac7779763f56b580190d3cb2f1c648cada1", + "0xaca9355545118d0769cacf69c4b23d6d68d229cd8f68f1bc0c847c05569c5af6bbbd8c4dceb637b4a6b3b5c83841bf5e", + "0xb0731992cab87c7116406b283a84707a34838bfa3284b0f6082dfabeaf41c5ac2b0ddc1b420547a1b0955aee92de2dc0", + "0xb671f77588c0f69f6830a5b28e7d07ed161b81fa9791bb3a24aae6638e3aa5e186df74978a82549c370c18ebee04d4f0", + "0xb5621ed841780f3e6681d880a76cf519cdd20d35197b112eeaa686764d57b5dfa78ffe1a294b6bc76b6e3949cd2a2369", + "0xafeba2524659d00caecf089645611553187a6ed7102050f6dd20f5a19bed08ac7065912d88371ee06242897d58d652a4", + "0xb78bfb83d44ced14a20135804aba3f00128c3ce1f302e95567ce4097b0d973414153fb305b9f156882a5a0554bf25973", + "0x98510aede95d26b1adf214053eae051ffaf24894e2fa37961a91d0ff5392dd09388196648d95b73e90bd88f2587cc4bf", + "0xb35c682d49c295946b9f120fbc47b95abd9ee86d294abb003a92139fb825b509209562575015856a270eb3eea86397a7", + "0xb9641bf685571dd9c478dd2033a1f1b11cd3a662b26502c78595863b8e536a189674a9a85f7a253453ebfd1b99fbd841", + "0xb2ad37036a59b1c9b8457972665720a6868422ed8157b6810a9c0783006103be34ab732d7aeb8629653edd18fd0f1717", + "0xaf0920cff05179a3896ea6ea322c39adf91ada5bc40fe3f6fb1b1b4e121e907c904bbaa8ca00468b3749f3da144d71f3", + "0x8e269672818ef1e2f9e0c8aa65c84442fcd9151d74bb8e870cee8c0e3fe24526e1a5388b430cef47b67f79b4e4056bcc", + "0xaa29a16fe00ea3d143b1032b1dd26b8ce638f37f95c085c7e777e8e2784bd724bd5c38b1583c61a6ec7c451dd78fd3fb", + "0x87452b7435911cc5f513b0c81b15aa04972ecbe3d7bbd0a5d676c96a8a311301c0e07fac925c53a350b46fbd3d4d0fc1", + "0x869a81c351096f47748e41566ae7b77a454b1cdfaa41d34a5742f80df38fbf5cbb08924b6fdff58e3b18f05c62bbbbb1", + "0x8b7bc1b0486300981147a40a449ada9a41afc06d735cce8bf0fab3ee94ba2e2ea57b1397e3cd31bc295352beb8334ef7", + "0x93e93fc41adb2df279d95654921b4c2edf0d293dab58d0afefb221f777349ef88d0985b3447e3b935954a81f1580a92c", + "0x970fa7cdca8324faf3e62348bb50d78f580b4f43f2e1c11bd8382d48d0074a3c55c6407203a0c9cb1c5f2163ba421ef4", + "0x924983929e608d27e4a36d4ed919297869e3c64de51aca794d32d6e90aea546bf898d98ceca28a0b2187734821b78504", + "0x8d395332529c703d943d68415d443332b5c1342ca9d9a59bfa8bd4ab63e93358c4b0dde6ce1f2e8ea9dc8f52ad7ebd95", + "0x80200dda853e588256599e7f905add5d5ee7c74272780317694fbae39318ae9be05d5bcd7b20cf460069743f3d4ef240", + "0xa287d51d6359c9ef7c7ac1b20e479ce7d0146dba5606397bd04b7a622cec642508d5b45d51b31de71f9763595b6ac88e", + "0xa320396c075175d6599225cf2e1de8c7cab549f6316c07feb0f6eaa21f06b2dd29ab14fbdf2af4543b4890ec0fd08a4d", + "0xb1e9fe230418d20368691058adcbbe30011bab3000422f0371015ff8bd09c60fb5fa85d18550d35b1c900977ca48f58b", + "0x9718fc26a51783b971744933f20490e9b5cd9162f86b84788c4c5217f5409e37b5a39d628b18e5b35a757acf67596321", + "0xa0cf81fdb161f4f1b419c5e4caa36d4bdca2325f0cd25b119a30178016f171bd6fb88403e4e3aec026c4089f180d540e", + "0x8ab1e36bd04625ee794ef04c4dcb8e004d61aceb2b62438377f49ad95dcf025ba25eb799280004941e555bf7172af6fe", + "0x9257b9e3d14d37fc7efae49b0c68d36eaac546035f4a2654d566b3ce1b2c4564cbb03dc8ec66efceb768559a8a507a18", + "0x945d1123b839637ab5154a1972c3c83a0ff34a3b1a3465de6ef0416b1950f649869a3ef88d7f1036648ee385265ce2df", + "0x81449639d708860fc0229c94f754f7262e8a3c7f67960ff12dfd15df95f57a9ffcee2013e81978b7703dd42bd5d0816f", + "0xa865481deaae5a690fd53892791e5fa729db283b75a525a11cdfee1ce17e8e7f0b449d25f20b3c1b43da128dbdf98a8b", + "0x98766812a65fcd25b853546e3bba618a3edc9fd61510e4f8ab60c038a7fa50d197abeec8776109df0f2119be9445ad00", + "0xb1b8dd5379d903dc41d74e999b1ab693607a0d2905692f4fb96adf08f738e5d31f9d00df28ccb8b5856145ca552c3e3c", + "0x99d20be7b511bec78a8ed03c207aa4aa9097ba39d85e18f1b8d52f65431ab7e9a773c7b9ac3e8d8b25458bc91bd00703", + "0xb1b7c3563fe8cb33c7d3e0b89d00bdd13e86452ff507c2e69db7b3af06f247f139155396e9b0278753310dc63940a10b", + "0xb3dc9c08451b1de7c9969b1e47574bffff50490f4a16c51e12390195d9e9c72f794790caf7b0a835d64e01fec995d3ac", + "0xaaaa4761a00022ede0809d7063d3532b7bfae90ff16f45e17a340ad4ebaa2fbac40728ccc5fbe36a67ab0e707566c5dc", + "0x8319a1903314eab01f5442d2aee6ae9c3f6edfda0d9a88b416d0f874d7d1d05d08bb482102f8ca70a4fa34836d0840c1", + "0x932949a6e9edfec344932a74d4f81eec3667ece1e8b8ca840ce07ffd4b5d6d8f01657c764d64ac1b9190f876b136490e", + "0x904db1568128487e312fe629dd8bb920cecafd3bb9cad8b63e269ae0129f2f5c80cd82f0d81e7feca9835c3945a72d28", + "0xa17280693d30dcd43c85de8f6b02d5f30cb9097274ad680cede1ef105c903615b4c40f3c6aaca478642de324972514e0", + "0x8d5f76e093aee71d0cdeb017fdfcb13bd068039746de90690ce150a0bfdbe7ddc4d539df0f82c2d2890a40b191900594", + "0x96fa1f2196a3883cdd73c66d28403cbbb58f6a939a3697ee0d308d8a076393cbb4be86255af986869230ee410c01bcfa", + "0xa8b74438dc5cabd70a91bf25601af915c4418d074327a9b01e0190c27d3922c89bb9b41e0b366e82e313edda8f21983d", + "0xac9fdc1a9b2e3ff379eb2370979372e13c4177bf4574f1490fadf05a7073e6d61e703e2d8eed9ce984aba317d411e219", + "0xa45a6c9b958169f2f8df70143e6ac3e2f6f969a4eed6fd9f1c620711bc2454739bb69f0094079464790c5429c0d8aedd", + "0x8901cbdd1009864386577842c1e3d37835fddf834064d9613b4559ea9aef3084204e1f863c4306f874141f4374f449ff", + "0xb6c582161691e3635536686825be9c4d7399d668a7675738417e0363e064dfd28acdbd8dbc9e34c1dab8a1990f1f0eba", + "0x89e89ddaf3cacc78428f3168549c161283ca8337345750667c98212717b21e7d994eae4e45bbddacc832a18df1d79276", + "0x84be275627eed8e1a73c7af8a20cee1ef5cc568cfeea7ec323d7f91b44e9653e9aeed47c1896a8240b99dde545f0e1fa", + "0xa779a54ab4f40228f6e2539595fb8d509b70aab7c19e1928c1be69ec1dc19285c3898cf15e5f8b8bc725e13af177fe17", + "0x92e2a49d2b9b36349d442283b17d46f8f9bf5932c34223015ce62d2f285e7363b2c12232be4a838b5b6cf08e694c094c", + "0x8b4e28c6f3f36caa2cfb82ba88066c830f8017bd35608b077143dff236f3181230166f5a5c02fa0e5272297331726aed", + "0x85fd77d46162ffac4b8adb25baff0eb0512a53a3d01638b3a376ea34702279ce21c8e7d8884308c03e00c9bcc1a9fd29", + "0xaad5e46916ff1be29009b595d1d8fa160cc7aa01c7fbf3a68f445c87615790dcab1fcdbdceda533d182b6541f09f2f73", + "0x948df7654726250dae393325addd3c0a20431c81f00470962190335ea4b6d9f7463d6f308cda46b92084c1f24390b1da", + "0x8f577474dea132676504376c5542b730b6604fe3d965eaa194659fd11c52233bd0b11ab62e198c0f442327ff1c00e501", + "0xae2f1001546db3e0c19700adad997cd9f765fe7a51a502cbcd9a2a07a3a5db79c8f603e05cf96d80b688cb6c9b6cd3ae", + "0x953b68e5d9561088dd20406ea7fb6894cba33868a38ace38fc30b5813140cb15dd6dd2171befae5b4df2e4a9658889d8", + "0x86c52901655ff11419b084a04da8fc3596eae59d81d3461601c0baff59ba59e3d1dd0b7ce719e741a3e97c013e898579", + "0xb9a72dd5eff73f9912a28b55de073568efb3eb0241a10b77a2bfd4f30c2aa4fbfe0c89eb345c9f07fb725660873cb515", + "0x8e7353f5f2932e4ffd95811caf46c9bd1a53643c27eb41a4ebd211f230955cd71a8b27e17cfe8aa708d8514c0de67a66", + "0xa096b8e66312a92fb10839ebe60189a8d1bd34dff55f7dfae85e4d2f53a1a4a88211c19fc84494f066358ddce82be131", + "0x931c5cd82719d76596832b007969b5f75d65cffabb41b9dac7910300db677c1309abe77eeb9837a68c760bb72013b73a", + "0x8ba10f5118d778085122065b55dd1918fddb650cce7854d15a8f0da747da44d7b12d44fc29ad7dc38f174be803db74c6", + "0x8c971deec679372a328587d91fd24ab91043e936ca709c333453d7afd43ee256d08c71cb89f0ab0e89ae119831df6d86", + "0xa2ac28a58034fbd8fd518f409221bad0efec52670880f202e09c0530e2aabc2171ed95e99891790596ffad163d86c110", + "0xb3354e3dfa8068aba4f3741152b9204baa4e342c1cc77e6dd1419cbaf8da1d118be605846b8609e997d6a62a11f3423a", + "0xa12ab65a213c9d95c24865fddc2dffe0cf9fc527dd6bcdacc1bd7271e79929a4ab3427a231f4f49d0530474e6cbc88f9", + "0x90afd65b7e6973f8aafbe74da0f42441840d3c93bd69bc1bec8fa56824e7ca97ad1b427c8a85da7d588469bd4ccc50c3", + "0xa09175940c59489bac3d3da3a4091270d9118948cbbdd57f2bcc63fbf45b8010651c801d3e58dccf42733ce1d6b446a3", + "0xa843bbf286e3cecc1fe370ff1bcf5f1001bc2e95b34246625ff50d48ee62343e82fba2d25b8a4bd5f7b5ffe90920efa2", + "0xa3c4d1003219157fdbee2707ce07afa6c2a64ae8e450182c307ed7f070024071f30b12c4b0032960ff913c74e73a9976", + "0xb24af3f68d66f825d06fc3ff94fcccebe28b1a0d4ba29c48d3a3c953b9bf7ae6707f193fef25e2dcbd2b74e483c774f0", + "0xb0f657f7723184ef7d7e4381143f1ac8020d8c6c6f2dcbebb0eaf9870d61a81f2d452596503311e46d1b38f625d4756b", + "0xb90091004fc8f6205c51bec68547ac82dba0f5525631e7632cf6efe54eecd9020729fbee6105d1b8012402d3b79c54aa", + "0x8e3fa187713c60eb0a416d6900a894cdf81e6b6b69dae0bb64f6287f3c3f030cfa85c665f7aace1eab4937f380b8f728", + "0x879bf0784ccf6725c9cd1ea8c49fde31c91c605de1ea664a33c2ce24c277ee45d20b66309f98d989acb2ff3b77e13101", + "0xaf3f3a3ddc4e11abd627d5aef8adffa91c25df5f0c68b4d2b5d51e7d9af3395ba4f6f7ae2325a6672847e1ecc6cad628", + "0x973e667289e796d3a40f072e6fea575a9b371a9997cf8961677f8dd934619ddc47c1a3efe91bae9ef95acb11a8fe6d09", + "0xafa81c5606de82f46b93f4bb6db3fc0670f4e0d1091388b138a66b3827322d95a56168c951c30831d59eeadc227500bd", + "0xb83eff77db5b4c18574662942eb36f6261c59f655f8a9c3d3731412d0f257c8e80aacc995c4b2303058a1ba32522a434", + "0x912e5ac9234b9445be8260393ff08e4859a7a385e800b74d1534eeb971f58f74cfb518dfdb89f8705d89fbf721439129", + "0xab27c8ece4a51d23e22c2e22efa43487c941139b37ea1182e96efb54ca4809d8245eae0ebe8ba94f0ed4457896fe11b1", + "0xa6630585d104a745bc79dba266d9292bbdad346449c8ee8140a5e6e8a6194411df9cdbf3d3ef83468a536d4f052e9335", + "0x8b8c128244da48e7fec641a882d0005a2d05c7138d86a293e6a0a97c76bf632b44767d0ce44663c975e7f9f9679e25e3", + "0x87dbcaca67351a4e7d2297d7cdba4796d12f58857e7ee4abd0645563577ff33544a44cd84e50b3a3b420d6998de9b57c", + "0xb859ba43df259d7f8e7fac70bfd7aae546d57a5dc90e107b174a95bf7fd3cf00f740c4434848e69b2a7e6061f66c1ef1", + "0x99d6e20978fefc40c6d310187eb2ad3a39296f189ee122ed64d74f81033c3069d44f7a9d3988a1df635b609603a17272", + "0x99a5ddf3420cc0c92b21f71a805245608d4995ead447d8f73a670d26d33e26920d5f07bfe1f6230bd5f15978055b4253", + "0xb936ac0944d3c5e4b494f48f158000abb37b80b5c763f77fe856398c664b0f1ddbcc0a9a2a672db9278f08b4bafbe2ec", + "0xb4af85fbf4040e35a686dd016adec037c99b47cc2e4dfccaf7870ee9e8c97bff30f3035992def2a9d4af323c0b3af8ae", + "0xa5ee32b8bd5f8fa9000da4da0bf00565659a43285393d37080b555d0166bde64d87317b2eab2d48a0e7b287caa989be2", + "0x894d4ad58ecb1c9ebc4f5a97407082e56cb7358d7a881ba7da72321c5027498454f2c7fa2bd5f67a4b11d38c7f14344a", + "0x965be9eeaa0d450dacc1b1cc2fbf0d5d4b0dd188f2c89aaa9260e7307a2a1eb22db6092fccb662269e9a1abfc547cabb", + "0x805893c424aec206260c1c2d2509d2cb9e67ee528bd5179a8417a667aa216a3f318ed118b50d28da18e36c01f0805e3f", + "0x972d7040d4963b35260ef0cc37cd01746f1a2a87cedc0dc7b0ee7e838c9e4573784ea743f563b5267eb3905d4fa961ba", + "0x8c7156991d4c2e561888feaecf501f721b4174e7d14109e9deeac5a9d748301c07e11fb2b04b09799f0d34ff42cb77d1", + "0x894722ac35af3d507e81d737d21e16c5ba04686f8f004aa75934aae5e17acd3e065b96e229eb011c2f34096f4c62048b", + "0x81237937c247c88e8e31e2c72412189fe59c1daf65c5513489d86cf29ee922c0bb08e5f7890f09f4ada7e5262083d266", + "0x8cf62cda2fe0d9a6b42aa2a1c483f4ad26378c7cc2c2d1510a76df7560b07dba8528b33aaacb15f7f20b9d4c7c9f61f6", + "0xaaf0921fb3e1920eee5d0acb59dcc268b42f4b435d60d25d30357edd7dd758d035919691bd15311d85489dfa2e5ee696", + "0x92cec07be2247ef42002ebcaf65ec855611b8e893a5675796f2225f55412201b0bf9f4761924d0c8377b9f131e09e39f", + "0x8e514a62ac1e91773d99588415426c97ad63e917c10d762fe06ace5277a5c3bf3730e4b9e5d116f8493b9ab8687b70e3", + "0x83932df2d923a5052468a3ea87f7b55c6a80ede3594046ee4fe233046570921822bc16555b92ba6aeabaef9b1dc0805a", + "0xa2b5bfb249de3472113fd3f35bfabf3c21d5609da62a27ea6aab5f309c9068d94bc58ba03efb4ec11be06306d59e60e8", + "0x8106cf3ebe6f0507be8c6e8d137987315fe3689ecb75bb27980f36ba5efac504baccea0e7603549b6d126beccc278804", + "0xa73ee70b6fe8c082443972102c453fc0e386852476cf22224fc0bfe554735c12f96037fbf10922795f4502c4f052b5f4", + "0x932b27e175440169958504f3ed6400e7d6dcd5e716c19dcd0f15c56c04503ed133d5a993e111c016f141e32d68b29886", + "0x96f7ce4595318e0b4a6b368f788ff82226aac676aed4ace343867f751de414453a9aaaabef6e6224ce5aedc3d5cf77c4", + "0xa950c1e3bc9a14484997013d44d876374b939af437ae7c821c131fb886063ee9fe7214a25a0c7084f0b07b99412eff75", + "0xa9dba3886ed6855303106a1bdd26010f294218684e1c178afcfea3f37a2f04fd01724a31d82de3449046617e3507a115", + "0x87a2f776b32a6b550cf3ceeaf78db02819be74968d228b1d14e0d74a1cdf994bb500b7abef6619455e98d728701fac5c", + "0x8cd887b07e335edc0b27e6a660cebb64d210741395be431d79d570139687b056557159407459799a8197b6079644f666", + "0xb81a61fce00588909c13a90c1caa150f15788786af443ff60ce654b57147601f7e70b95659e01f470334a220b547611b", + "0x8aebc51141544c5f3d3b99422250424b9800031a8fdfbf22c430907a3a446fecaa2392105d66d64b1c8e847240da4a6a", + "0x90db7dc12baa02f3f86d3edadf9434e2b9318d4f6f0eca08276b765dbb38d8eb0d08be2fe70adf2bf16ceda5db08d3ca", + "0xaa1839894152d548cc6ad963de20fb6fcc843bc9af2a2bf967c63626b8ad19e900894d6106265f38f3afccca317c22f0", + "0x848e27b741496988a582515c0c8847b2bfc6a001259396cdeea1e1b1d2828ca3a626693a1bf4adf3a3d7f8b1fa3d75fe", + "0xa0aa11754d4ee136ac3ca609b17bcae77758763b2016544ca7921dddedd8aafcc7ad5f2b337c8bf53084eb8e43ea41fb", + "0xb8713b7aa1c112178195fdcc9b7024f46e6bc04c4e76c41abe620aa265287809200d98eaed6c9703fa97e81d6964f0ec", + "0x8605b5b33309e9ea6823542b85383c496794b8481c577497aaf99ba90496e794dce405be615bf92c7b6361460e6b82e3", + "0x826fa34faa7f83e063a7bf172addfc07badabada59cfc6604fdf481d29085251c0a67a1355b2cbd374e2975934b84cb6", + "0xb45d131082dc16fa53af010d43eefb79200dc23d2f3ee26af95ac6a5cebc49c84a9ed293e534ed16ff3ef9a4a25456ec", + "0x91bd6ce3c5396a7a0de489e49f0cdf6dce1cd2d0be7a410326423c3185bd1125ce1e610768be7f15f4e44b62f8834fc3", + "0x903ffbe3d33fbf106c01c727dc3a385201a67ded70d4df623934882f69a3a96c909b027a124f3d70cb072b0046a149e8", + "0xb405359db9d9ef4821a181b440ef2918c240595141d861d19a85867a5afa74d2972d22c988775eab441e734700bae4a3", + "0x8abb756d027233c83751910a832b0ef4d28d100077f1c5d656720c94906f91d85dd0ea94b1cc0ed95b692efee14c786e", + "0xa78ee77ab476a41a3454160ba7ca4085d8b1f7057c63e76db8b07cf20afdeddd2250cd00771a6329133bb4ad48ccc20a", + "0xa41810271d8c37197aa9b3dfcefe3498e42f5978d3f3d59defff4676d6402d8575b40683834f184f143b6cfbfc859b3a", + "0x90c24a0750242660bcc6d487358a3cc015730538a0a8beb00ad5ac2ef33cb8ca8a62121e50bec8f3d2f43900f8e3134a", + "0x8b96c39695d864ef5796941754978a1fd612b369f6b77fe5ae6587beac936ee28190af8f0a3822b63060af35e49a5c8b", + "0xacde2548883d0e63c0fc257bb9dadd919aba60a985b69ebcfa1bca78acca42fc1322ec30bcc8e7c188818f858d04ad33", + "0x895c86ae9ff8d95f2707d4838a3bc8ddb05b2611f0476f014b9c150d0e8332bc73285037a747426f09ac8179ba4e19fc", + "0x821761fe406e18bd86fa9ca9db99d382cd3b5c70c456f471fa3706d57763d147706304c75d54f51ce8f3115aa26e59d9", + "0xa803a80e3e8f47dc3c59ea23eafdec017458eac648b360cd42cbd075e0dde6f6f450b48c7646fb1e178c04f82ae51a12", + "0x91f40e1b6f588bd592829ce937996452c40be0fd6c43793c607866701ac6a8c7227e0891d45c6e7b1599382b0a3fbdbb", + "0x9408246d996a634a58689337f2526dfb3ba9ffef1d3ff91c32aa8cbbed900861ef25d6477308b67d76491edfcc70d65e", + "0xa492325a427f3df1c9c690c5b553daa8ac41f62f5ae55f425539222bacf959e2f67afabbba1732e120d3e7a6dcdf7049", + "0x8fd0c3e15477cae228613a171b6e9ec29ddc63ef74854d99b638adeffe39f89f34346a42851e8445e855a9f2bbef0f57", + "0xb735ed01fafa051004dbaad5e8c9e2faca8f6049ef9b590f256ea4d75b04594af12764ad4e6031735eae36f83179db93", + "0xa7d35f43fca06c86b3425dcb68a87186834ba9740664fd657915771beca4cdc0fa2fc9b4c2e9d9bdad8ec33543ddfa59", + "0xa1156e71e2db1b17df5da28747c88e091bd687bfee59d89096437ab4dc9a543fe5c5272d5023d72adbaab397a6fc94d1", + "0xab06a58bd81b33a411bade8d8c5232d38fadc2e38507159edea6e2e104b8ebd65ca02b05335118f691d44197b847a4dd", + "0x848b67a10f1e6ff8f5c228f226ef2ffeb67fb8f50925fc94cbb588d61896d9dc79726959e649898fd3354fe3ff7b7ee3", + "0xaa933397361f32b388edcf832f0db172a38e756b34d5f7a4a050fa7325058006c22cede26ee27917e8f1b0f301792bd7", + "0x89e49e7f02cfaae4a4b9c4180c9f6559d76e3a45774955859d4147970b1470dac37bdc9aedca1c32a20b045049161590", + "0xadc1825d5ab94fc719f25d8c9773f4d518134ed88eb13ac33cb910b2be3523ef9ef88d9e4aea2418b806e20108317bf6", + "0x96c4b444c8a023da644f3a343ebeeed19a8392d2ce175992461451c318a54273b76c3574d8f2dceda2947ddd34d1a674", + "0x8aa7e97e87c8c5b29bbd51a6d30396a6be1fb82b716ef83800f2c36d5b85467ade7e0f59d2db82c310fa92a9265f0b03", + "0x9146c32d99f02c3a6f764dcd9b4807f1585f528ac69dc4f84e4380f6fda4f9d5057c375671d51e7aca2b2b4140e83da0", + "0xa10760a533d9bc57536bcaf65f080302086aa50225437efd64e176841544711828c23a15c49c0dd1f357d3f10722ab72", + "0xacb0811777e17f7ae7aaba5f6fce81b759c067a4908730916195a2505c7450d0e6e2194c2ef0f241090597d58e70de47", + "0xb24f161e9bcdbad56665e2490b5e4c7768390d4668cd69a04ed74739062dbe832636dd33cda89e9b0afa8c77e93fc641", + "0x96b4d01106b831868a88ef016500ef2fa42d0ce87a37ca8ca4194a92a22c113edfe04eb2ca037329f3c1acc635148f55", + "0xaebbb95fb4f7adcc8e7a217aeb73f9e037cbb873d08c1cd9d68c6c6834511adf1af8b44567fee84327599bdcb734dedb", + "0xa9bd8b17300532fb94d028659bcafbe7bbdf32f8945baf5db4cfaa1bac09e57c94cad0ba046b4514044b8fe81ea8596d", + "0xa5557cbda599857c512533e7cadcf27bf8444daa0602aa7499cafc1cf1cf21f9d16429915db7485f0e9a1b5046cf01c5", + "0x8810307c40bc661c478a9747ebf2a30e5a5ead942d1ac0418db36ba5db0709c476f7d19685cabe6959e33ec1f3bff914", + "0x8829b741f41f2c32e10b252d9338deb486dba2f23996a44cf1dd888ad967a589d51329be34d764139f372a1043f6c2e5", + "0xa6b4728d18857c5fa082fa67bfb3b1d801e76b251b1e211a19c87cea5fe7ce757f943c85071f7a03a718388cd5690e95", + "0x86da7f397e2533cd487f962ae58e87bea2cd50af70ef2df9ea0f29f70b5843cde664d30ec207ab84fc817f3851277e02", + "0x8085776ef4ac6d42ab85b9d9135ecc6380720efd274f966544eeedf4684028197de76ecab919fa5414302597e1962bca", + "0xb05a065c733033d223ba13d16baa7a97bd8c8b8b1f0e59a9bdd36ee17e9922d48eb39bd180c168b122088a77f0bf321a", + "0xa89343fe44a93023dcc7ef71bd3bcb6786f68e1885ad260edc56a52445d34757f476395ba7ad35437f89bc573c7618dc", + "0xa114a9cd6105b524f3969c69faa2e09afe21753a93361a296f9e0e3b4e3e63726ddf2e6bfd3ddc046043e50bd44e539e", + "0x8a5611fec539cf681c05636bb580f29acc06f628bb012649ffa41ea6c1521194a5643d5dd843f09b6eb2c3bdb4d41acd", + "0xade247c4011ec73ec90b72f35afa59a999e64ba5a7e664a4b30874fea53ba6a14a76a41b58a5f891a20d019e5f091bdb", + "0x905b5d96df388160ade1ffe210d0c6d1979081bc3de3b8d93ac0d677cc2fc2dc1ef6dcd49d3947055514292a3fa2932e", + "0xa9520796ca9fccd11b7524d866507f731f0f88976f0de04286e68d7cf6dbd192d0d269f0cd60fd3d34011a9fe9e144c2", + "0x989a1edf4d7dae811eb57a865c8e64297837ffeeaae6ee6ac3af0f1044f023f1ca552bf00f1642491f0f0f20e820632e", + "0x879c8e63713f4935ed6e020559e140ea3073ced79d3096c152c430141272117b4fd9a9fc3eef012e81262df02ea14bd7", + "0x95074738ac1540c0312274333acd1ecad9c5509fee883c4d9295fa8d8200f6e637c363de395f9fa612f05c0dc58fae88", + "0xa770e4fc595269eb806b113ab3187ea75c8f96b57bf9fcfaf535f3eedc1d4d7e6285a20990575de0ff09f62d06ed0692", + "0x81283e5dfb6423439ff513eca1cc316941d196df8da2d1069d2d0b63f5289e630af2fd4119bc0144c002d33313372dab", + "0xabd1b108e743887b78f698f2aba9d5492f87a22868d1351d705d93a1084fd45be67170c68a6e18b07f400d9a01cda8c2", + "0x8509c3f67b92908cea8144f4e2a71631a66a61ac3547601c788907e52e380e5fe8ae4110aed95d13c67d3bcdd5b55a61", + "0x8fa5a790ec5cce6d4114128c295390120869aac5490a82feebd3c37a167120df2e7fdfaf2a4050a7dfebf48fb093212f", + "0x944753e1ea7d8bc727d46a7702077dc01dc0c6574e8263a16579b57ee155ca5901f71bb347a01a9a922b329d3ff75135", + "0xb46bc1fd4590b7a6275e20036d247c5909fc549c78e95b64ae7ed96e3b05bb044840f19f7650ebfe7008ba09fa83c3c9", + "0xb1e47e4d88e59a06c465348c6cc4181d40f45b91e5e883966d370c26622c328415c6144aa2f61ddb88ec752482c550ca", + "0x8bd4f8e293e3f1815c7e67167618fb3b0ea76424bc0985908957cfcede36109378e41b4d89555b8c2541b4c447e00461", + "0xa70589a867b2bfb63d0106083d58475d506637148549ed35c83f14e5c8de996e1b1f3447ecc80cf5cd134ef4db9d2fb6", + "0x8048b80ba6131d07370162724127b0f7cb17fa7f71855e55e5a75bd0a9e4fd71b0d0ea2d16ec98858e458528df8d06b5", + "0x97326cb94bae7530f4ec3235770c5a7ba042759e789d91c31fedbd979e3c0e6a2c69e2af3c1979c6fe0094274dbd53ce", + "0xa18e9c1d3eabd62af4e31a4b8e08494f4167fd4598c95d0123f39c46c53f9e93f76615900246e81a286c782ac37c569f", + "0x80309c59d4522b15aba617cd3c6238663e8b1c7ad84456346082c8f281140fc0edf9caa19de411c7e7fb809ca4fa3f4d", + "0x8e450c0990e2f65923f252311623038899eeff7b5c2da85b3a224e0ef7132588b291b782d53c477ecb70f34501466178", + "0x87843f96f41484e254e754c681a65681b9ae5c96c292140368743df9e60f7e2ada58ca2bb95fa39abe064b2ebf21eeba", + "0x858e8d5bf2a1cf26d8af5036b28b831d450a446026f58a1734b696c18f1f41482796b91cab0e5b443dd2f0b9cffa52b4", + "0x99627dd6bad8c05c5904cd23aa667d664da846496dbbb8452705c4ec01e1480e9c7295504a5a8529e4a0c842306b038d", + "0xb64b33256c18b2c886a837a0c0730fdfe73befb0e2796207c4dc592c5a33cd51f8c2ef47c584dd5773abf9ce9c1b0082", + "0x944f6da2a1546f0bfc4d98c3e73c79e935e33d208b6be26b0b5f8df6d0e3b74a5bda649853b99281bd3a3ec799a7dd04", + "0xa266d165435784d4e884640155e35b2a911b3f89e1e715986de419b166a36a341ba724877d80583fa3da566f6a828971", + "0xadff2698409d0756e78c534032ee926560c13d578cb178d5073172d049ebbce32a92692f7e2033ec781b9b0d894ddce0", + "0xa91933f110756c699c28bf9e24fd405bf432002a28c4349e0ca995528e56a5a2d101b8d78afa90a178ff1a9bf2ba515c", + "0x8e77839c0eb4da2d01e4053912cd823eddffbdc6b9c42199fba707ca6ab49fc324288b57be959fbfb11d59085d49324a", + "0xaa124517c76692036c737e987f27c2660514e12a953e63ff4bcb269dd18fc44dae95e282de8444bed09639ef6577af88", + "0xb285deae99688f1bd80f338772472fa2b35e68887c7eb52c4ef30fc733812444c5cd110050275ad999d5a9b57f782911", + "0x8877b0fa85b44ef31f50bdb70b879fa6df5eb1940e2b304fd0c8f08abb65f3118fa3d97ff93919038c1e452fb1160334", + "0x8a89f3b50dcbca655024542ca7d93df17deff5c7d01c7da2bdb69e76b3e0b4490d85c800fb3debb4b0b4d20c9527f7ad", + "0xb7e5dbe36e985354ac2f4ab7730fea01b850af00767a6c4d8ee72e884d0fe539bb81f2e34638fcf5d07b7c8d605f4c06", + "0xa85a1d78f6d4f9d5d83ec0f2a426708342d4e4a5d15625554e8452f6a843d9aa4db0c7e68caebdaf767c5b3a6a6b2124", + "0xa518078a9dac63c5bf511b21ed8e50d1ccede27ebfe9d240937be813f5ee56aef93dc3bf7c08606be1e6172f13f352ce", + "0x91144eedebda4d1ad801654ef4ecd46683489b177ba1de7259f7dd8242c8c1700e15938e06c5d29aa69f4660564209a0", + "0xa16c4657bc29d1d3271f507847b5a4f6401cee4ad35583ad6b7a68e6c2b9b462d77b5dd359fd88ea91ce93bb99130173", + "0x85b855778f4b506880a2833b8468871c700440a87112fa6a83fd3ddb7e294b3a232d045dc37dfc7100b36f910d93c2ae", + "0x8d86bb149d31bfbf1fabcae1b8183d19087fd601c3826a72a95d2f9cedb8bb0203d1136a754aa2dd61f84b7f515acfa9", + "0xacfe7264eee24e14e9f95251cbcfdd7e7f7112955a1972058444df3c2d2a1070627baefada3574ebd39600f7f2ea7595", + "0x906bd14ecca20ac4ae44bff77cc94eb5a4ecc61eba130de9838e066e8766ed3b58705f32c650e1e222b3100691b3806b", + "0x8f2cbc7b8593c4be941dd01b80dc406fe9dfdf813ef87df911763f644f6309d659ea9e3830ff9155e21b195fc3c01c57", + "0xa68eb15ed78fae0060c6d20852db78f31bebb59d4ddc3c5bdd9a38dbe4efa99141b311473033ff8f8ea23af219bc8125", + "0xa95cb76c9d23fc478c7e8a73161f2ff409c1e28a2624c7d5e026e3cee9e488f22225a0c5907264545a73e83260e3a4ec", + "0xb76f90e55fa37c9e2732fd6eba890dd9f1958c1a3e990bd0ce26055e22fe422d6f0bcc57a8a9890585717f0479180905", + "0xb80cc95f365fabd9602ec370ca67aa4fb1219a46e44adf039d63c432e786835bb6b80756b38f80d0864ecb80e4acb453", + "0xb753c86c82d98a5b04e89de8d005f513f5ea5ea5cf281a561d881ed9ad9d9a4be5febb6438e0dba3d377a7509d839df0", + "0xa664733f3b902fac4d1a65ea0d479bb2b54a4f0e2140ed258570da2e5907746e2ac173ace9120d8de4a5e29657ae6e05", + "0x9479722da1a53446e2559bb0e70c4e5bf3f86c0ce478eede6f686db23be97fcd496f00a9e174ceb89ab27f80621f9b80", + "0xb707fd21b75a8d244d8d578f3302d1b32bb2d09f2bd5247dff638d8b8b678c87d4feab83fe275c5553720a059d403836", + "0x93214c16831c6e1d6e5a1266f09f435bbed5030c3c4c96794b38d4a70871782002e558d960778e4465b1ff296ffedad8", + "0x8648f84e18eb63dad624e5fa0e7a28af2ee6d47c28f191be0918c412bf24b5460c04bf2b7a127c472914a0741843f78b", + "0xb67f61e75d6b773a6b58b847d87084b94f3cdac3daa7bef75c2238903a84250355a986b158ff96ba276ca13a6035fdd6", + "0xae9b094b7b5359ee4239d0858d3755a51aba19fce8ad82b0936cca48017523319c3309409ea6e9883a41bece2077e4d8", + "0x8d1d8e1fba8cebd7a0e1effea785a35e16b1a10842f43e2b161d75add11eccf8f942d2ae91c20eef6c1a0c813731ea9a", + "0xb82bd387458e3603782d5e2dec32ae03890a3fc156d7138d953f98eff4200de27c224f626e3648e80cd3dfc684c4790f", + "0xa6dd02a89ad1c84e25e91176c26355e21a01b126c1df4d22546159dab9d502dbc69bc0d793a017c1456516e4aa5fa53f", + "0xa9ab74a5c5459b8500beb0ad13e9cfe2656e966dc9b4f3f98bec7588023b4ddebf74e4fc722d30423f639f4ee1b2587f", + "0xb03e5f33ab7ecec12cbc547038d3fa4f7ea0437e571891c39660c38d148212d191be29e04eb2dc001b674219b7a15a9c", + "0x925df4fc6e898ca55090ad1a8f756cc5014167a042affda5b24896eeb6aac408545134920586a8e1a2b997de9758b78a", + "0x98c8580fb56ed329fad9665bdf5b1676934ddfb701a339cc52c2c051e006f8202e1b2b0f5de01127c2cacf3b84deb384", + "0xafc3765d374c60fac209abd976fe2c6f03ce5cc5c392f664bb8fac01be6d5a6e6251ac5fb54cfcd73e3b2db6af587cbb", + "0x8e7e98fb5a0b5b50d1a64a411f216c6738baaca97e06d1eba1c561e5c52809b9dab1da9f378b5f7d56a01af077e4f8cf", + "0xb724bf90309651afb2c5babaa62dc6eac2b8a565701520fe0508cee937f4f7b6f483fc164b15d4be4e29414ce5d3c7d4", + "0x9665160e7bf73c94f956ecb8ba8c46fe43ae55c354ce36da40ccc7594beae21d48d9c34d1af15228c42d062a84353a0c", + "0x8600ab3aa86b408ee6e477c55572573ed8cfb23689bbdadf9fccb00161b921ec66427d9988763a7009b823fa79f8a187", + "0xb0d8d19fd1022e7bc628d456b9bd1a2584dce504eb0bf0802bdb1abd7a069abbeeccdb97ce688f3f84a229342dbc1c33", + "0x8f447d5e5a65bb4b717d6939cbd06485b1d9870fe43d12f2da93ca3bb636133a96e49f46d2658b6c59f0436d4eede857", + "0xb94e327d408d8553a54e263f6daa5f150f9067364ded7406dcb5c32db3c2dffd81d466ee65378db78d1c90bc20b08ab3", + "0xb58c02781b74ef6f57f9d0714a96161d6bfa04aa758473fb4d67cc02094cd0c0f29d0527c37679a62b98771420cf638b", + "0x8cfa0a687ea51561713e928271c43324b938aa11bb90f7ffaa0e4a779b3e98899f2af59364ce67b73a46a88748c76efa", + "0x95d6d39c814c5362df69116558d81ce6f1c65fb400fc62de037f670d85f23f392c1451d43341c59bc342bc31842c8582", + "0xaf888b384c52d9e04e4db6c4e507c2037eb5857e9bcc33acf84fc3a02d93cbde8cce32141fce9f5fec715b5f24d56356", + "0xa7822bbc3c236fd58bd978f0fc15fe0b60933a0c953db6436a233441219418090ae0c07c490a6548e319029771cdaba7", + "0x8c53729f750922e5eb461774be8851a3f40fe42eed170881cc8024d590bf0a161d861f5c967144d15cdcdc3dc6b5cf88", + "0xa052a25a4aeab0d5bb79bc92a6ae14b5ad07d1baca73f4f6684ccecfc7ea69bc21eadeb9510452fdba116c0502dd698f", + "0x923946b83d37f60555dbac99f141f5a232728c6eb819a37e568c8c6e4d9e97a4229fb75d1de7e9d81f3356f69e6d36f1", + "0x8cab82cf7e415b64a63bd272fe514d8b1fa03ba29852ec8ef04e9c73d02a2b0d12092a8937756fdec02d27c8080fb125", + "0xb1123314852495e8d2789260e7b3c6f3e38cb068a47bdf54ed05f963258d8bcabaa36ccbea095ba008e07a2678ec85a7", + "0xa685b779514961e2652155af805996ceb15fb45c7af89c5896f161cac18e07b78c9776047c95b196362c9ad5430bcb22", + "0xb734dd88f6cc6329c1cb0316c08ade03369a11dc33191086c6a177cf24540c7ceee8199b7afa86c344d78d513f828e81", + "0xb0bf492fb136ecdb602c37636ed4deef44560ab752c0af5080a79c9f76a1f954eba60a0bf6ba8bd7b8cac21848c29741", + "0xa5c74682323e85ac20f912ab9c1d6e1b9246c4c829dca40c8a7d58ec07ea0ad3524be30623f351269552f49b65a1245c", + "0x837403b9cf830fb33ecc11a7c8433e07745973c36acdeb3fc9ea8f7d8d690d462e1250b7410f79f2f4180fe8f3962a4f", + "0xb03d64b944d49c83608f2c5b9c14070c025f7568c4c33d4eeb1da31d07f0bc5897e498b35b50d557ee129f0c3c68e254", + "0x827272aab8bf757e2483156e00fbebe1093a58070dd3af9855bbf946c7abfb9c8a850a6a8acda8c620902f391f968b8f", + "0x84c4eb863a865282d321302d06b362f8bd11c2bb0090f90ebffedd3eb3e7af704cff00d39a6d48cbea4262942e95200b", + "0xb044eb91653dc55dce75c8d636308a5a0dae1298de4382d318e934140a21ca90e8a210e06fdf93aadbbeab1c2ef3904a", + "0xa8c08955a4378522e09a351ecb21b54025a90f2936b974068e80862803e7da2b5380c4b83b4b4aad0409df8d6c8cc0cb", + "0xa763a5fb32bd6cb7d7c6199041f429782deacac22b6a8467077fab68824dd69343ebca63a11004c637b9cb3129dbf493", + "0x8c44c8afa9a623f05c2e2aba12e381abdb6753bb494da81f238452f24c758c0a0d517982f3999d2537b7279d381625ed", + "0x8613f47fda577cd3bda7c99b80cf4b2dd40699edfd3df78acb5e456dd41fd0773bc8da6c5e8cbf726a519b9fb7646ccc", + "0xb21a30d49d7e1c52068482b837a4475568d0923d38e813cea429c1000b5f79b8905b08f6db237e2eccf7ef3e29848162", + "0xb9bdf4915f3fbb8d84cdfd0deedf2c9dc5b14f52bf299ef5dca2f816988e66322df078da2c54b934b69728fd3bef40b5", + "0x993b45f389f55eba8e5ba1042d9a87242c383a066cbf19bc871b090abe04de9ff6c1438cb091875d21b8c10fac51db58", + "0xa85a95d14633d52d499727f3939979a498c154fd7ebb444b08f637b32c1caf5cca5e933a2f5d94f26851ae162707b77d", + "0xb9874c7c4be1c88a9646e0c2f467cd76bc21765b5ab85d551305f5ec0b4419e39d90703d4ac1bb01feb3b160517e97b7", + "0xad6771177fc78812904c90594712956357de1533a07fec3082ba707f19c5866596d624efc3e11773b3100547d8f6c202", + "0xa79f31921134f7197f79c43a4b5d5b86736a8d3ad5af1bdf4ad8789c2bfe1c905199c5e9f21e9f446247224f82b334f8", + "0xa7f1b6c45321222a350a86543162c6e4e3d2a7c2dce41aeb94c42c02418f0892dbd70c31700245d78c4d125163b2cd5e", + "0x92abafe3ec9dbe55c193fb69042500067eb8f776e9bf0f1cb5ab8eb12e3d34986d1204136856fb115c12784c3b8dea6e", + "0x89bc761238a4d989006ca5af5303c910c584fe7e6f22aa9f65f0718a1bc171e452c43695e9f5a591725e870770c0eceb", + "0xaa0e44c2b006a27d35e8087779411ba2f9f1966a0f5646ff6871bcf63a8b1a4a7638751b94c9b9798ccd491c940bc53f", + "0x8736fe82862b8106e7fdab7b5a964d87ec291a74b8eb1cb5a6c046a648c1b686064ef3d52297043b8940bfe870c712f8", + "0x956a3def1942f05144d8e9c3a82fd2d3610064b53b9eefde3d5594a8f705bf8f6849eb2c22181796beffeba43cc74ee4", + "0xaf27416d00cf97d5a1f4a1b6b51c010884cceca294f1151c3b684a3f83c3c8a3c30771df1166d833cbddf6c873c400c3", + "0xaac3b8dca2336fc4ffc63c362df461289e4bbd3418c621bde6c581d3ecedf66e2b3e523d4db39e3d8ba014577bf85efd", + "0x94c3a8167f62074e5b28c2bffe4b6ce645439a9a0c5da3ca1b3ee956590a465d6f84a8a4dbbe9070ffbd6bbc734e4d62", + "0x95e23ba6986d25ed4451215da05bd72c5491528271726d79a94c8cb16aef1c85b190d6c5b8a3a1191c7cafbab1dccf0c", + "0x953e3dadb5ad68f7de31ac09692948655d174fe16d88b96930ef35b331da7f1dbc4c17863cd07b4ec3135b5205891a27", + "0x915d018f18b5d63cb3301c2bb5c6e85e75a88ba80663c964d06575b6bacbbe59139d030b218ce0998271d5b28c00b26d", + "0x8c871ba3dd138a908b2f7effeea0e71df096b23e0dd47cab10b9762b250abfd1221da94a8ee884e05bdf02271fb85a04", + "0x96bad5c6ebc3080ecbe337409ae398bbeada651221c42a43ea3b7c08c21841ddbcfde544c9b8d4772de6f2ce92c0b963", + "0xb5dbcd0b1c44c62108841558ec0a48df4b327a741e208c38b1c052321eda6e6ad01af71d49dfcdd445ab6fa6f0c34e6d", + "0x97dba59219b69e8aef2659d1f10bbea98d74aefff1f6451de3f41be39acbac0122b8ff58b02e90554469e88911ec3547", + "0xb7e5682ec306478be4858296f5d03364a61f3260636a4242f984d351a02e8723378496beb30c4ca22def9c9ca193ea70", + "0x9656a7a3df4d11df3d8bc35930dff70a5e78a488ca57bba20bb06814fc390fc6c7cb3f39b22134992aad196cced577de", + "0x8b269695aa63eb56d0324ba984279dc4c88e565321f1d61d553622bd4f1910d5eff68393d3a830eb924472bd478c2aa3", + "0x9177bcd04b28c87bc0440268b4c8995c6790cad6039594971b2c177f0e197055231e776927d3fa30d98fb897a2ba401f", + "0xae0e943973482001c4f214b9da82e1c27e38aa254d0555e016095c537c835d3702bc2de5c67b234ab151e02b3b7a43a6", + "0x82fc719a7d38bf4787fe1888019ad89fbf29beb951d2fece8686d2beb9119d0c8c6d13bc598748c72c70d73d488140ca", + "0xb716dc66f87eb16b95df8066877353962d91bf98cf7346a7f27056c2a4956fb65e55cb512af278783887ab269e91cd76", + "0x81d58cd8bc6657362d724b966321cd29a1b5cdc4601a49fa06e07e1ad13b05e9f387ca4f053ed42396c508cd065c5219", + "0xb32ad0280df6651c27bb6ddbdc61d5eb8246722140a2e29c02b8b52127de57a970e1ded5c2a67f9491ae9667349f4c46", + "0xb68a2eb64cc43f423be8985b1a068e3814b0d6217837fb8fbfd9c786db9cca91885c86899c50a1242040b53bf304ced9", + "0x85887515d4e371eabb81194cbc070e0c422179e01dbda050b359bd5870449c7950e6b3947b7a4a0eb68199341cc89fc3", + "0xac5fff3c27dfbab78eb8aad37ac31cc747a82401ebf3644a4f4f5aa98d37b8bf3b3f4bd8a3428b32a127c25c9e19d239", + "0x86fceaa6fbf8913553a9e1e907fcb1f1986d5e401a7eafd353beefd1899d571454fea96ff5b2a21254d9fb693ec94951", + "0xb6778bb296d3f0de2531b67d36fdbfa21475be0ca48b9dfcc38f396c41b557823735ed0b583e525a2bae1fe06e04058c", + "0x898088babeb5b9866537d6489f7514524c118704abd66b54210dc40a1c1ddb0a1edf7fe0b6e0db53b836f1828ecf939e", + "0xb27854364b97274765f0fb8d1f80d3660d469785d1b68da05e2bd1e4b8cbbe04304804d4c8aabb44cf030eba6c496510", + "0x8c55bbf3603dc11cb78b6395ccbc01e08afcef13611f7c52956b7a65ccf9c70551bff3ae274367200be9fc2d5cb26506", + "0x947726f73cd6281cd448d94f21d3b91b96de7ad3ff039f9153befbb5f172db9f53cacb4f88c80a3db26e6a0f7a846eb0", + "0xa7b733a05e97528812d71cecb4f638a90d51acf6b8fcbc054787d6deb7e2595b7b8d1cbe1aa09d78375b5e684a2019bc", + "0x8d5ca6d161341461544c533314fe0a6655cde032c2d96f0e4ea7e41098b8b39fa075d38e2d8c74e2d0308f250d6cf353", + "0xb960e9f081393e2260b41f988935285586a26657a3d00b0692ea85420373b9f279b2f1bb2da2caae72dd2e314045f1bd", + "0x852a49c7388c10821b387c6d51617add97ba72485f52be95d347bac44c638c92e9c6a44ba0d32afc4d59178a497d944a", + "0x8412162a65147e1334ad5af512982b2b48eef565682b3f3e0bbe93fbc5e1103db9375a0c486bdb1b2c57e4cb3a8e7851", + "0x8f52c3eb5d4f1e1e82cfd2b291d4910195427603b796f6c311deb35ef14a01a57a9e6cad39619ad108f3e86f384f9e1c", + "0x88d221088f2bf0103c53e44d0d96cd7881ec2b0a965db9121a47481771a8b796edd5ac23c4f9c208a171dab301f7d3bb", + "0xb49c3235e8b3617ed08a1891b9e2bcb33dbdacceb94ca96330555b7e00904fe6a749ced9312b8634f88bcb4e76f91cb1", + "0xa85834215e32f284d6dfb0cbfd97f6cffc7b9d354e8f8126d54598bb42d7f858a2b914cf84fa664069632db2ff89a332", + "0xaa3d48eb483c6120c27d9b3e3d0178c1c942632ff54b69f5b3cfbc6ad4ff5b2b9ce6eb771fd1eea8edf4a74c97027265", + "0xa446cfded353cdd9487783b45846402b973cdeddf87e2bf10cf4661610fff35743cc25e8d3b5771dcedfb46b018a5d18", + "0x80998377b3b393ef3073f1a655ad9d1e34980750e9a5cfb95f53a221b053ddb4d6985747217e9c920735b0c851d7551f", + "0xa35ac469790fac6b8b07b486f36d0c02421a5f74ea2f0a20ffc5da8b622ac45dfccabfb737efa6e1689b4bd908234536", + "0x8fb1f6d8e9c463b16ac1d0f36e04544320d5a482dd6ffaec90ea0f02b4611aaca984828bf67f84dcc3506b69af0a00a1", + "0xb6e818d61aea62c5ed39c0a22ccbb327178feebdabda0c9927aa1549d2c5bb0637785c4aed2a6d9a7b4989fa8634c64a", + "0xb4e7208d16018bf67caafe996d436113eac619732e3f529a6efb7e6f094d8ebea55b7be0e122be075770f5957b6ea6f0", + "0xb691d38b552befac61f6d367287c38d01fec73b7f2efdb6713ca30314a37fb7c177eb111fe6bee657f2681014e07630a", + "0x9817587e418e6e7e8e97ae27067f17b55d25dfb14e98f63f530620c855d9a348c9fa571c8508e2741f902f8b9fdc0c5c", + "0xb6a6e5ca779ba140bf1d84cd5394ede8262f7479637ec0087a4b152243a1774ba916d8115ce759a3bebd1b409de5f2fc", + "0xb53d1c84ad766ff794bf497db3228efd2cc8ed5fc1958d89c1126efdff361610ecb45ea8e329b39035ab00a66c1259c7", + "0xadc31333c507c8e0f4aa2934fcdca57fd9c786722a50dbd5404e129541f7ac182cc7373bf14e1e4e06e6cf94b31b90eb", + "0xa82b7fde4642d982d95cec669efee140ad797a2442c7f6620580527d163accbf021b893446cbb8038ea82fe25b15d029", + "0x91f7acf8a8903979afa281646fdecb54aa4d2ed905748e156e92f0910de268fa29d67107d40863935d677d1de8039be2", + "0x86fea71c6d43a7d93216a92fc24dfce8521fd4534a9558b33762d002081247867a6eff54cad7116023277fb4049403ad", + "0x8ae5369a7f9f4c91f3be44b98089efd9c97c08f5bb4cd8b3150c115ecd86288fa0865a046a489c782973a111eb93966e", + "0xb6fb9e829aa2c81c2d9eac72bb2fd7f3a08e0cd763532c2ce3287444d33cf48b3621f205e9603ec58525934b61a795a9", + "0x83e35ca808d84e41fc92115e9f6e283e928c3a614e6dfc48fe78c33b6411262e7bfa731eadb1e1937bc03cff60032e1d", + "0x832fca5196c95098ad47b7d24ba2f9d042e1c73ad2273edd1c2ce36386796ccc26e8567847697f3fcc2a0536a2a2087a", + "0x8fdb7038bc8f462ab2b76bf7053362f9c030019f1b6105cf42219a4e620ecc961e3eacb16a8e581a562a97f1418b0128", + "0x8d3a5a404b51b1ad8ce3b23970e0d5cc57b573922341008e3a952a1dd24a135e19e55b79d86a70cfd82e1c0e9630f874", + "0xba00c025c1c21c57c03cdfc0bfd094b35422281ff0a64b68b240617aa58c6b18800af5f2047d3ff9068bbe987d6c7980", + "0xb468f0dd51964b3806b0aa04f3fe28a035e8f5567fc7d27555be33d02701a838b8dbfe1348b6422c4eac46d2c75c40c7", + "0x8a73a18c97da9958903c38584b08d0e7e26993a5d9b068a5e0e1ee0d8a873942745cf795f94f7a3d3ba88790a9fbb2f6", + "0x953a0a40c2c8102723736854d13b228698c14a02d85c8d2e61db1a768019ac305faf0d5db62ac976430ce087a5b20f1e", + "0x8998219da6b34f657cb8a621c890a52cb98c2bc0f26f26e2af666eebeadadc5e8bdf4f830a91d04aca8ce186190152c8", + "0x8941e08c3155ad432236ed05460420a05dd0aaab30477493ffb364b14c00ea5b9183d30d3442b6321d2d20c36e4f5c7e", + "0x93f293ff7fb56cf5b03aee6f3ad2ad78444398ed5b3be56d7bf5b56b5aa5a2b980d13895dd57a5726d1b067c20cc55e2", + "0x84a16f313e3f75e31824f58d19ab24c6611fb4c75140a7cadc3c166f68819547c1d0ff7f7d13f5d8ae30dff1d80e2aa4", + "0xb6e3e830b15039d3e28b08f5465bb089eade11ee3bd80afe39e010df7db1fcf0c56d698717677a41ddbc91eeaf6544d3", + "0x95e928e6dfff51351281568ae72da7d1edeb6e9fe01f30af0499e7505ba35a22b5bb919d41bb809a432dce83f3977663", + "0xaabeeb60ca46f9b0232ff82ea7766dcab8cc5aaf9d23539f30174f9486640bc9312868ca493b59b314519fc399973e47", + "0xb393a11e957d0bbb3ecf617b075b5906a3450b348e62916c04791b366f0a7397cccd6648440ac544bc30526e1f95aad8", + "0xabb5bfc3964a6d246da60bd809d0ea6daf4f8222efdc12ceb6730194e85f413ee7eb03bae300abf7ea900dbbc3d08971", + "0x96c1bd1d1d216a4bfbcf000c123f296c0d31e1684e9e3884c14df23bf528c8d599f82bb98fcea491716b617216a8e0be", + "0x92d1e570a56f1741fd9f3d9f488cc336421c6256c14a08d340a63720be49b0029e3780e3e193a2e22bf66cc652fa22a3", + "0x8769c08551e3a730e46f8e5d0db9cf38e565a001dfb50db3c30fa7fa0e98b19438edc23c6e03c8c144581b720d7b33a4", + "0xb850bd67fdf5d77d9288680b2f6b3bc0f210580447fb6c404eb01139a43fccb7ed20051999ae2323ea5a58de9676bfb4", + "0x80285da7a0aaf72c4528a137182d89a4db22a446e6c4a488cf3411937f4e83f7b00ec7549b0b4417682e283f91225dfe", + "0x80520368a80b97d80feb09dbc6908096c40ff7120f415702c1614d7112b0b57f6729581c71f4a3ce794ac959a46494ff", + "0x9817b4c27a490b1cd5a6337e7bc7e8005fa075dd980c6bf075ddfa46cd51cc307ad1d9f24e613b762a20fc6c877eab41", + "0xad66bda1a3034ec5e420b78107896ecf36126ce3ef9705163db259072dfa438c6107717a33572272062b9f60cb89557c", + "0x876114ef078c2915288e29c9abe6b0ad6a756b5ee2930ba1b8a17257f3f0557602d1225e8aa41ce8606af71ada2a971b", + "0xaa3d6cde4c3b9d3d5d0c77a33e67f182a3e1cf89b0921423b2024236171955b34afc52b1f25b1dad9da9b001371771d7", + "0x984d3e3a72412d290e3459339757af7520d1739c7af0cbcf659c71999328db44f407d92e8a69fea11625612c49eac927", + "0xae890d0faf5bd3280dcad20a5f90e23a206661be8842375fea2ab22aadc500849ffbc52fe743b376d46bb926cedae6a6", + "0xb1f231f3f4d710c3fe80099faeb56dac67c1baf53b8fe67a9920fe4f90e52cb9a4bf19211249a6456613b28efe337f18", + "0x8caa54b418ba609d16520af3dff2e96d5f2eeb162c065a1763beb926547b2cfb3ae41d738db2c5681a9bc8bc9e6b9a1a", + "0x932157ff56c5ac29cf6cf44f450c882b3acfbb9f43d12d118da3d6256bde4e6eb3183aea304ab6967f37baa718ffec99", + "0x9360bed8fc5b6aac36aa69473040689bfc30411d20ffb7275ef39b9ff5789f9055d149383ce9f0f7709a1f9d683adbfe", + "0x98b5b33209068335da72782179d0c7aeeabe94b5560a19d72088fe8323e56db7ce65debe37a97536b6b8a0ca3b840b61", + "0x89a385c11be40064160b030a1bb28c3921fc8078522618a238c7ea0f86f34717ed9af9b4e2e20f5128e5f7fc66ad841e", + "0xb615703cbc64b4192990cc7e4903b74aed6a0076ce113b59ef7719197ffa46fb29eb78ca56b49873487432d0625c0faa", + "0x90f0d77abae9d3ad73a218e5ccec505ad108ea098451461567ae8ef9661606ca8e78df53b5d628b20b7037bd24622330", + "0x92e0e7cc4dfadc5fa0ee6da0c8de0493030db6e54ba0317f52f232a6708b732068b6077bd13a17eb7eb40b88368085b5", + "0xa24dad20094985bfccc6df1343506ed3bf9dcbdf4b2085a87627a5d71f7568db067304e465f8f380c5c88e8a27291a01", + "0x8629a45a10619354c84bdc2f6c42f540eab5a46f53f2ae11970433d7a2aef007897590bf31dfba1c921614c6d6fe1687", + "0x84ac64040d4206f82b08c771f375da4b7d752e41d2aa0da20ce845f6bc1b880a855d3ee966bca19b8ec327b4b43e7f0e", + "0x9608e6050c25996c052509f43f24a85cdf184135f46eaac520a9a6e78e0d44a6cee50ebc054048c708aefde8cd6651c2", + "0xa32032b0e0d7cc35e480c328f315327f9385adb102a708c9ba637878deb74582ae26bb6d6e5f8c9e3a839b0e0154b82a", + "0xb7e3c78d63acc6564a49e9f00b0a820b56d4f37a2374af1f7f1d016268011df9e7af0670ed2b0eee961f15aa948328dd", + "0x8b88bfdd353acc91ad0d308a43e5fb40da22c228f2fe093c6d6904d70f69c6203f56636ed898b05df51d33f1095ef609", + "0xb1d7a430c51fc857af55047683fc18c453b013527196c5e1bf776819a3dffca802217e9249ae03f084e2ea03ad67fcc2", + "0x80558e28a819ddb5e72e97c54be0f57c173ccf78038d360d190b7f1350a19577b8e3f43fa2f7bf113a228cd3b965b2e4", + "0xb4b2ec44e746c00dfc5661ba2514930934fc805cdc29adc531c02d28ce3cc754414b0485d4ee593232cd1175f357ad66", + "0xb57cee5d32835f76572330f61ccd25a203f0e4a7e5053d32965db283aad92f287645533e8e615137208383ec51b1fd99", + "0x930256086b419a8a6581c52590d0dbd9f8a3564c79424198fca3866b786df2f6098a18c50dc4abd20853a7184b1ce15d", + "0x8e75fd01181cffcd618a983492390f486e8c889972a46c1f34a4e1b38f384e8e4efc7e3c18533aa2057da9f9623e2238", + "0xb375d927dd988429f9e2764e5943916131092c394fce13b311baa10f34b023dd3571da02553176091a0738cc23771b9a", + "0xb9e28e4c0d0477518034d000e32464852e6951c8db6f64ccdb1d2566f5094716213fbf2fc0e29ac88d0e79f725e3c926", + "0x963981e99392afbd2b8318d5a6b2b0cc69c7f2f2f13f4b38dddbfedb2b0eaf0584aecfcbda20a4c60789c15d77970a58", + "0xa7804e1977aa77c263c7c001afa6cf568032dea940e350d6a58ce4614f1a91c13ae1c78bfea740c229dce2444556976a", + "0x8787204177da3cde6d35cd3497fa8774d244f9faa9f4bd91b636a613a32ce2ea0326378cf9c4cf475e73ef751b355c4b", + "0x895aeef46a07152a04ec812f1aa1fd431389fa0ef6c6e96a5b833e70ea14073bc9984757a8ee456dbec9788e74e6f0ca", + "0x8d17f0e5826783440d1f0ec868003510a4d9952bfe4a638e44a36d94482ac18ba70ef7ff773bdf7a3b62d714dcf0fcba", + "0x810d5e36b31310b2e054a666d3b3f7ed16dfcb1765532d87ca2a3920316f0187303c27dd113db145d47e8961062a6c03", + "0xb4e2fb48ae04cf8580bb6a28095076c9b95e5f13122b917328f334d4ac8a8648ce442919e28319a40148987350ab5303", + "0xb85549a313544fa1eb3ceb78473b7d3d717fc85b808de7b79db7dbd0af838ebb020622a7503f1cbacab688dddb648f84", + "0x80665adee057088eae827a5fe904ec3ad77d8843cdce0322d535e0659b4abc74a4d7ddd8a94c27f2def5c34ac2c038ee", + "0xad72fc19c2ce99b5b717e35528fe7d3ac8add340b02ebeb4889d9a94c32f312a0b45ea84d21c54f84cc40ee4958b72e1", + "0x99d530c843dff89a47a5ee8c87303ab18f8a82b0d5b808fca050354b35da5c5a5594d55921c6362d6cc917d75bdc18dc", + "0x99c7286c293e1be21c5b2a669dfdfcd5aa587105d2886fc5a8eaf8984da4e907f7d7b8c2362d64a4f1621b077a2a08a0", + "0xb4a39e1a9ed5d80c9563c3ca3fadf76f5478c63a98f4346a61b930c9c733e002f3ff02bc16abfdb53d776184cc3f87ba", + "0x9378ea71b941979404c92d01fb70b33fa68d085bf15d60eb1c9fc2b5fcdee6379f5583389a3660a756a50019a2f19a69", + "0xb68e17344a2bc45b8e2e19466b86dc139afefbf9bad2e2e28276a725099ebac7f5763f3cb52002261e3abe45ef51eb1a", + "0x819e64dc412b2d194d693b9b3157c1070a226af35c629837df145ea12ad52fa8eabd65b025a63c1fb0726207a58cdde8", + "0xa5e8ff8748419466ff6df5d389125f3d46aedacf44eaf12cbfe2f68d218c7d5ab6de4a8279d13aecc25f3b1d98230894", + "0x91560d54a9715cfda9cf7133ae51c432d0bf7fcbaeb468004994e6838bfc5ddcfa30e4e780667d0c4c0376780b083017", + "0xae8adb3309cc89d79a55ff74f129bb311fe4f5351a8b87600a87e0c3ba60825f71fccf67eadcf7e4b243c619417540fd", + "0x8d92cc1a6baa7bfa96fbce9940e7187b3d142f1888bdcb09bb5c8abf63355e9fb942ac4b4819d9be0e0e822d3e8e2e08", + "0xa6e8b79fdd90c34735bb8fbef02165ccbe55ea726dc203b15e7a015bf311c9cac56efd84d221cc55eaa710ee749dbdfe", + "0xa409b151de37bddf39ce5f8aa3def60ee91d6f03ddd533fce9bf7bdbeac618cc982c4f1ffbf6e302b8353d8f28f8c479", + "0xb9693975ef82171b3b9fc318ca296e4fe6110b26cbdfd653418f7754563fa7b6e22d64f8025ee4243483fa321572bfe4", + "0xa039ebe0d9ee4a03ade08e2104ffd7169975b224061924cca2aae71464d250851e9f5f6f6cb288b5bf15df9e252712a6", + "0xb27834db422395bd330e53736a001341ce02c9b148c277dabac67dc422741bfa983c28d47c27e8214cd861f2bad8c6f6", + "0xa2bafaf4e2daf629fd27d7d5ac09fb5efc930ff2ae610f37519808683aa583fe1c6f37207daf73de1d8a164f79a0c981", + "0xb856cee1cfcf5e50db9af4ab0aed3db2f43c936eaea369b5bba65582f61f383c285efbda97b1c068c5d230cbe94f7722", + "0xa61ab205554c0550fa267e46a3d454cd1b0a631646b3df140623ff1bfffaa118e9abe6b62814968cc2a506e9c03ea9a0", + "0x8c78edcd106377b9cbdfa2abd5278724aed0d9e4ae5869b5d2b568fdabb7804c953bae96294fcc70ef3cd52ba2cbe4ed", + "0x8570869a9bbf6cc84966545a36586a60be4d694839f367b73dfc40b5f623fc4e246b39b9a3090694aa2e17e652d07fd1", + "0xa905b82c4da8d866a894da72315a95dc98faa3c7b3d809aef18f3b2be4801e736a1b79a406179e8cac8f74d27e71ac52", + "0xa8eb8679ff1a64908515f6720ff69434cb33d63aeb22d565fde506618908b1d37585e3bd4d044fd0838b55787af06b42", + "0xaf4d86b2fbd1684a657dffe4210321a71e6ae560c144d44668d1f324dc9630e98348c3d444622a689327c1a59cc169dd", + "0x80359c6eab16954559ab0e6a1fee9a0526c45d3cae1a371159a2e3aa9b893afdc3a785c9559a5fd9cd8cd774234bf819", + "0x8d4e5ff81eb5d17bbe8ae6416538ca51a9427ce142b311f5cbb14febbbbb9c1ffc6489fd625b9266264c366c12a9d997", + "0x92e181c66489c5fa063ba2a1a354b6fd3439b8b4365a8c90e42e169bfaa1fb5766bf3e0fe804399d18bc8fbcafb5c3b1", + "0xa9ddf229360a095393885083716cb69c819b2d7cfb100e459c2e6beb999ff04446d1e4a0534832ae3b178cbe29f4f1d3", + "0x8e085ef7d919302a1cc797857b75cff194bdbc1c5216434fa808c3dea0cf666f39d9b00f6d12b409693d7a9bd50a912c", + "0x916dc4dc89e5e6acf69e4485a09fc66968f9b292eac61a146df1b750aa3da2425a0743d492179f90a543a0d4cd72c980", + "0xb9cbf17e32c43d7863150d4811b974882da338cf0ed1313765b431b89457021dd1e421eeaa52840ef00551bb630962dc", + "0xa6fb875786daec1a91484481787093d8d691dd07e15c9c0c6ae0404bf9dc26083ed15d03c6d3fe03e29f28e20da21269", + "0xa870fcb54b9a029e8086de9b08da8782c64ad2cc2e7fdf955b913d294038bb8136193256b85267e75a4ca205808a76b4", + "0x99883f057e09b88bf0e316f9814c091837fd5c26eeb16fec108c9fed4b7a2bd1c783dac0e4242b5a906621ab606c1e50", + "0x85d89069ca3190577dab39bbec43c16bf6dbca439ad3eebd8f5e9f507d84c3c43e77fd6323224582566a3aa2c8018951", + "0x9363ba219e0003f6e8a9d8937b9e1449e4b2c5cd57194563b758bea39deab88778e8f8e4f7816970a617fb077e1e1d42", + "0x820622f25553c035326145c1d2d537dc9cfd064c2f5bdf6d4ec97814de5fe9a0fbd443345fa2ea0a9d40d81d3936aa56", + "0x87e31110aaf447e70c3316459250e4f7f8c24420c97828f9eb33b22107542c5535bdb48b0e58682dd842edea2886ff08", + "0x95bf80cac6f42029d843d1246588acb40a74802f9e94b2bf69b1833936767e701ef7b0e099e22ab9f20f8c0c4a794b6c", + "0xa46ecf612b2763d099b27fb814bd8fdbaee51d6b9ac277ad6f28350b843ce91d701371adfaaf4509400dc11628089b58", + "0x8604decf299fb17e073969708be5befeb1090ab688ad9f3f97a0847a40ea9a11bbcfc7a91e8dc27bc67a155123f3bd02", + "0x8eb765c8dc509061825f3688cb2d78b6fef90cf44db33783d256f09be284bc7282205279725b78882688a514247c4976", + "0xb5c30b2244fa109d66b3a5270b178960fdec47d31e63db0b374b80d2b626409eb76d2e8d1ebf47ef96c166743032fc5e", + "0xaab01e76290a7e936989530221646160bf8f64e61e79282e980c8c5dcaaa805ff096efd01d075a2c75917a3f4bf15041", + "0xb9d79671debd0b83d0c7c7c3e64c0fb1274300564b262771f839b49218501e7f38ef80cae1f7e5a3c34acdc74c89dab6", + "0x92c0eaceadf036b3b9dfd2712013aba3dd7c30b7760f501f52141618265baa31840fe77850a7014dc528f71f8cf39ce6", + "0xb3cdd098059980455dd5b1c04182df1bd12fa844a866f02a9f8a86aab95b59945baa9af99f687410bffc5b07153cb23c", + "0xb361b73a62f71256b7f6ea8e0f6615e14fc5a06ee98b928ab3c9dd3eef9d9d30070e9855c82b7facb639cacb3401e01f", + "0xb9c85fc0f25a3271cf28b1ca900078eaaa66cbab0a3e677606e898ac32781a2dfce4d9cbd07404599e2c3c02fa161c9d", + "0xac5b4fdac2a0b2e6430d9fc72bde4249d72183b197fc7347bb1546ae6f544426686bbe0caec3ee973b6836da5e831c44", + "0xb675aebf24b92e398e166f171a6df442b3f5919b6bee192f31675a5e8eeb77d34c6590a6f0c0857417e0f78cfb085db8", + "0xa9bef942044d8d62e6a40169f7dc7b49e40cd0d77f8678dd7c7bae6f46c46786f9b1e319a3fa408f22a54fd2a4d70804", + "0xa20d19cd917d5102ae9ca0cf532127d2b953aa3303310e8a8c4b3da025dded993a47e3a28e6b02acfadb6d65dc2d41a3", + "0xa47fdb04059b83b2afb86a47b2368bbd7247c337a36d3333b6e5ef2cc9476a92c4907e4c58a845c9ef9b497621e0b714", + "0x94a9e9ffc14b411e11a4ffa59878d59460263589003dc7b6915247c549f67feede279bf3645fdd92379022fb21e3caeb", + "0xb92e1177dd9ecdaf1370c71b14954219cf0851f309bc216d5907a4e2e84e0df3457018224150c142cc6bf86644bb4b73", + "0x8bc57fadd68a265b7df9b42227a9c0968db7b1bb50dc12f7d755505779f1ff2c408672b3091e903366acc9ce15d19fb6", + "0xb6b5efbe1ac4e1bd2e8447c45000d09397b772ca5496acc447b881022608a41c4f60388814607a01890190105bee7be3", + "0x95f7c85fd614df968f8ccf8d086579c9e1cec4644ecf06da26e3511cb39635a7326b3cec47bd51cf5646f1c660425e9c", + "0xb81765fb319bcdc74b4d608383ccb4af7dd84413b23af637be12e2827a75f7e4bcd14441cf979ed9038ae366fbb6f022", + "0xa120ea76cda8c6c50c97035078f6648afe6537809bdba26e7c9e61de8f3070d2347160f9d34010effbf2ec7e94f5749f", + "0x92c1b8631953b40d3cc77eee2c72a064b999c09a9b92c11d8fa7b4072966273901c9dba25f9f79f384d9f11a56f3fc7a", + "0xa4b00dc0ab67b2300abc9c516e34daf444d6497b066a90cfe3381ed2812304ed37b14f3b948990443dc6c1cf1bed460c", + "0xa9e9f7e13c9f031bc7b9e6f1417c7abcc38894fe7d3f54869ee277afd2efa3e6fb50757dd36c8c94d591e0abdea322cc", + "0x84f3e98f831792b5ad14bcfe62a4c9f296476c6087c4c1ec7767fc642fbca141ff6a3deeb8b4d4106a9cda5a9937eea0", + "0x8eb1a7931bbea9a714226fd74b0100ab88355287d9b0a349c095e9b5809b98f237ffd706bce7d67a770da355fb9cec7b", + "0x9738ef8739e1742c1f26b51a1621be0b89d37406a370c531e236f635c7064c661818817bb3858908986aa687b28b21be", + "0xa9cf3ce8501b003ccaf57552a4c4ec31081e44526d3aa3791d3dc4a7e438a357c0956f93c500356186d8fd4588ffac5e", + "0xa7af6a219cca59225839a9de5b19263cb23d75557d448bc7d677b62591a2e068c45e5f4457cceb3e9efa01d0601fc18a", + "0x972a24ece5eda7692cbb6fb727f92740451bc1281835e2a02931b2b05824a16b01dbe5edd03a0ed5b441ff25a5cc0188", + "0xb21d1ec7597ce95a42f759c9a8d79c8275d7e29047a22e08150f0f65014702f10b7edce8c03f6e7ab578ce8c3b0ec665", + "0xa13a1c7df341bd689e1f8116b7afc149c1ef39161e778aa7903e3df2569356ad31834fa58ceb191485585ce5ef6835c3", + "0xa57bdb08119dc3bc089b5b2b5383455c4de0c2fcdac2dcfa21c7ac5071a61635ff83eceb7412f53fab42d1a01991de32", + "0xb2968748fa4a6921ee752d97aa225d289f599a7db7a222450e69706533573ded450380c87f8cdd4a8b8c8db1b42b5c97", + "0x8718ec04e0d5f38e3034ecd2f13dfde840add500f43a5e13457a1c73db0d18138f938690c8c315b5bcbeb51e8b9a2781", + "0x82094789e26c4a04f2f30bdb97b9aecca9b756cbd28d22ab3c8bed8afc5b2963340ddfc5a5f505e679bf058cbc5dcbb8", + "0xa35b8a566dd6ab67eddc2467906bffc76c345d508e52e9e4bb407b4f2b2c5f39b31d5a4bf5022f87bf7181dc6be2fe41", + "0xa8c93b1e893d4777c0e3a1b4bef3be90c215781501407c4011457fc3240e13524b4d2bea64a6d0a3efe3f3b0dae9b8ab", + "0x877095ad18b1e5870818f7a606127ba1736a0b55b0dbcd281ec307c84b08afc0c9117e3a880fe48bfc225fbf37671a97", + "0x84405ee0421ed2db1add3593df8426a9c1fcc8063e875f5311a917febc193748678dd63171d0c21665fb68b6d786c378", + "0xa52cdc8209c3c310bed15a5db260c4f4d4857f19c10e4c4a4cfe9dfc324dfac851421bb801509cf8147f65068d21603c", + "0x8f8a028a70dda7285b664722387666274db92230b09b0672f1ead0d778cee79aae60688c3dfd3a8ed1efdeda5784c9d4", + "0xa0be42fecc86f245a45a8ed132d6efc4a0c4e404e1880d14601f5dce3f1c087d8480bad850d18b61629cf0d7b98e0ae0", + "0x83d157445fc45cb963b063f11085746e93ab40ece64648d3d05e33e686770c035022c14fdf3024b32b321abf498689ad", + "0x8a72bbf5a732e2d4f02e05f311027c509f228aef3561fc5edac3ef4f93313845d3a9f43c69f42e36f508efcc64a20be0", + "0xb9ca29b0ec8e41c6a02f54d8c16aebf377982488cbe2ed1753090f2db4f804f6269af03e015d647a82ef06ffaa8cba6c", + "0xb4df3858d61bbb5ded1cf0be22a79df65ae956e961fbb56c883e1881c4c21fe642e3f5a0c108a882e553ac59595e3241", + "0x86457d8890ac8858d7bab180ef66851247c2bf5e52bf69a4051d1d015252c389684fcc30bb4b664d42fbf670574ab3a3", + "0x86d5576ea6dfa06d9ebce4cd885450f270c88a283e1e0d29cab27851c14ed2f00355e167b52e1539f1218ad11d8f13dd", + "0x883ad1364dc2a92388bfafaa9bc943c55b2f813525831e817a6208c666829a40455dde494eba054b2495a95f7ce69e8a", + "0x8942371e6925231c2c603b5f5a882d8404d39f0c7c4232557c2610b21c2c07f145466da798ea78b7932da2b774aa3128", + "0xa799eb71496783cc7faf12c9d9804bf6180699a004b2f07fc5cc36840f63ce7eee7dde9275819a9aa3f8d92dc0d47557", + "0x8eb3fb5c769548ee38c7882f51b959c5d5a42b5935269ccf987d6ddbb25a206e80c6000bcc328af149e0727c0b7c02c0", + "0x8f3910d64e421a8f2d8db4c7b352ba5b3fc519d5663973fea5962efe4364fb74448770df944ef37ffe0382648fb56946", + "0xb41413e0c26ff124cf334dab0dc8e538293d8d519d11cc2d10895a96b2064ac60c7da39f08589b38726cffa4c3f0bfef", + "0xb46ef2eb10abae0f35fa4c9c7ee2665e8044b8d9f91988a241da40fd5bbc63166925582151941b400006e28bbc5ba22a", + "0xb8baa8b4c420bb572a3b6b85479b67d994c49a7ebfe1274687d946a0d0b36dfed7630cfb897350fa166f5e2eff8f9809", + "0x964b46d359c687e0dcfbdab0c2797fc2bd1042af79b7418795b43d32ffca4de89358cee97b9b30401392ff54c7834f9f", + "0x8410d0203d382ebf07f200fd02c89b80676957b31d561b76563e4412bebce42ca7cafe795039f46baf5e701171360a85", + "0xb1a8d5d473c1a912ed88ea5cfa37c2aea5c459967546d8f2f5177e04e0813b8d875b525a79c29cb3009c20e7e7292626", + "0xafaab9a1637429251d075e0ba883380043eaf668e001f16d36737028fded6faa6eeed6b5bb340f710961cee1f8801c41", + "0xaef17650003b5185d28d1e2306b2f304279da50925f2704a6a3a68312f29fe5c2f2939f14e08b0ba9dee06ea950ad001", + "0x97bcc442f370804aa4c48c2f8318d6f3452da8389af9335e187482d2e2b83b9382e5c297dce1a0f02935e227b74e09a3", + "0x8a67a27b199f0bcd02d52a3e32f9b76a486b830ec481a49a4e11807e98408b7052b48581b5dd9f0b3e93052ec45dfb68", + "0xb113bf15f430923c9805a5df2709082ab92dcdf686431bbad8c5888ca71cc749290fa4d4388a955c6d6ee3a3b9bc3c53", + "0x8629ca24440740ce86c212afed406026f4ea077e7aa369c4151b6fa57bca7f33f9d026900e5e6e681ae669fd2bd6c186", + "0x933a528371dcecc1ec6ded66b1c7b516bd691b3b8f127c13f948bfbcda3f2c774c7e4a8fbee72139c152064232103bdf", + "0x8568ddd01f81a4df34e5fa69c7f4bb8c3c04274147498156aec2e3bd98ea3e57c8a23503925de8fa3de4184563a2b79e", + "0x8160874ec030f30fda8f55bcf62613994ff7ed831e4901c7560eac647182b4a9b43bfaff74b916602b9d6ae3bfcaf929", + "0xae71c48d48cf9459800cdf9f8e96bc22e2d4e37259e5c92a2b24fbe2c6ca42675e312288603c81762f6ceb15400bc4c9", + "0xb05f39bb83fda73e0559db1fd4a71423938a87ad9f060d616d4f4a6c64bf99472a2cbfb95f88b9257c9630fc21a0b81f", + "0x80c8479a640ed7a39e67f2db5ad8dfd28979f5443e8e6c23da8087fc24134d4b9e7c94320ffa4154163270f621188c27", + "0x9969ba20ee29c64cb3285a3433a7e56a0fe4ddc6f3d93e147f49fe021bed4a9315266ebb2fb0eb3036bb02001ae015e6", + "0xa198c89fef2ab88e498703b9021becc940a80e32eb897563d65db57cc714eaa0e79092b09dd3a84cfab199250186edcc", + "0x8df14a3db8fe558a54d6120bad87405ba9415a92b08c498812c20416c291b09fed33d1e2fcf698eb14471f451e396089", + "0x81e245ef2649b8a5c8d4b27188dd7e985ef6639090bdc03462c081396cf7fc86ed7d01bfe7e649d2b399255e842bdc21", + "0x8659f622c7ab7b40061bcf7a10144b51ad3ab5348567195924f2944e8c4ce137a37f1ba328e4716c10806f3fb7271689", + "0xa575d610fc8fe09334ca619ecdadf02d468ca71dd158a5a913252ca55ea8d8f9ce4548937c239b9cb8ab752a4d5af24a", + "0x94744549cd9f29d99f4c8c663997bdfa90e975b31f1086214245de9c87b0c32209f515a0de64d72d5ef49c09b0a031fa", + "0x80a8677862b056df59e350c967a27436c671b65d58854e100115bac9824ba177e94c2a1bfcaa191a071b9cefdbee3989", + "0x91be9a5504ec99922440f92a43fe97ddce2f21b9d94cd3a94c085a89b70c903696cec203bbab6d0a70693ba4e558fb01", + "0x8c5a0087bcd370734d12d9b3ab7bc19e9a336d4b49fc42825b2bfedcd73bb85eb47bf8bb8552b9097cc0790e8134d08c", + "0x933aa9e6bd86df5d043e0577a48e17eea3352e23befdbb7d7dcac33b5703d5ace230443ac0a40e23bf95da4cc2313478", + "0x984b7ee4bd081ee06c484db6114c2ce0ba356988efb90f4c46ff85ed2865fb37f56a730166c29ef0ae3345a39cdeae7a", + "0xae830f908ea60276c6c949fb8813e2386cf8d1df26dcf8206aa8c849e4467243e074471380ed433465dc8925c138ea4c", + "0x874c1df98d45b510b4f22feff46a7e8ed22cfc3fad2ac4094b53b9e6477c8dfc604976ca3cee16c07906dece471aa6c6", + "0xa603eb60d4c0fb90fa000d2913689126849c0261e6a8649218270e22a994902965a4e7f8c9462447259495fe17296093", + "0xa7c73d759a8ad5e3a64c6d050740d444e8d6b6c9ade6fb31cb660fa93dc4a79091230baccb51c888da05c28cb26f6f3f", + "0xa4411b79b6a85c79ea173bd9c23d49d19e736475f3d7d53213c5349ebb94a266d510d12ba52b2ac7a62deaaaec7339b8", + "0x943b84f8bbcee53b06266b5c4cd24d649d972593837fe82b0bf5d5e1bbc1a2bf148e1426c366d7c39ab566b10224cadc", + "0x8300012096a8b4cefecc080054bf3ceb0918162ba263c6848860423407796b5eb517170c0bad8e4905ac69a383055a21", + "0x8244a1e3ad41908c6f037e2f8db052e81f281646141334829f36c707f307448b9ab79a7f382a1e8d86f877c90b59271c", + "0x8eca1b74687802ecc36a5d39e4516a9dee3de61a2047252d9ed737b49e0090c386e9d792ac004c96337681c7f29a16ad", + "0xb70fa47535f0524835039a20036c61e77f66146ad79d3d339214d8744742db41ceeb577c829d000011aeafbb12e09579", + "0x84b3abbce48689f3adbb99889c7fd1f3e15ab455d477e34f5151c5c1c358ed77a5b6a581879f7e0f1f34106e0792e547", + "0xab45ecb58c0ef0dbce3d16afc6ac281e0d90ec48741ea96a141152647e98fcc87f3a3ff07ba81f3179118453ce123156", + "0x90d231a145ba36a59087e259bbfc019fa369201fcfeaa4347d5fd0a22cd8a716e5a797f3cc357f2779edb08f3b666169", + "0xa4f6074d23c6c97e00130bc05f25213ca4fa76c69ca1ace9dece904a2bdd9d987661f5d55023b50028c444af47ff7a08", + "0x933af884939ad0241f3f1f8e8be65f91d77ac0fb234e1134d92713b7cfb927f1933f164aec39177daa13b39c1370fac8", + "0x80d1db6933ce72091332ae47dc691acb2a9038f1239327b26d08ea9d40aa8f2e44410bbda64f2842a398cbe8f74f770f", + "0xa7a08605be2241ccc00151b00b3196d9c0717c4150909a2e9cd05538781231762b6cc6994bebbd4cddae7164d048e7b2", + "0x96db0d839765a8fdbbac03430fa800519e11e06c9b402039e9ae8b6503840c7ecac44123df37e3d220ac03e77612f4e4", + "0x96d70f8e9acd5a3151a8a9100ad94f16c289a31d61df681c23b17f21749c9062622d0a90f6d12c52397b609c6e997f76", + "0x8cf8e22273f7459396ff674749ab7e24c94fe8ab36d45d8235e83be98d556f2b8668ba3a4ec1cb98fac3c0925335c295", + "0x97b7e796a822262abc1a1f5a54cb72a1ea12c6c5824ac34cd1310be02d858a3c3aa56a80f340439b60d100e59c25097d", + "0xa48208328b08769737aa1a30482563a4a052aea736539eceab148fa6653a80cb6a80542e8b453f1f92a33d0480c20961", + "0xb612184941413fd6c85ff6aa517b58303b9938958aa85a85911e53ed308778624d77eadb27ccf970573e25d3dfd83df7", + "0xb3717068011648c7d03bbd1e2fc9521a86d2c3ae69113d732c2468880a3b932ebec93596957026477b02842ed71a331b", + "0xa0ad363e1352dcf035b03830fef4e27d5fd6481d29d5e8c9d51e851e3862d63cdcbaf8e330d61c1b90886921dac2c6fd", + "0x8db409fdacfa4bfdaf01cc87c8e97b53ca3a6e3a526d794eaad1c2023f3df4b888f1bf19fee9a990fe6d5c7c3063f30c", + "0xb34d6975310ab15938b75ef15020a165fc849949065d32d912554b51ffa1d3f428a6d1a396cb9329367670391de33842", + "0x9117285e9e6762853fc074b8a92b3923864de2c88c13cea7bab574aaf8cdd324843455d2c3f83c00f91f27c7ecc5592a", + "0xb4b2e8f190ea0b60819894710c866bf8578dd1b231ae701d430797cc7ede6e216e8ca6a304f3af9484061563645bf2ab", + "0x8c493c6853ab135d96a464815dd06cad8b3e8b163849cdefc23d1f20211685753b3d3e147be43e61e92e35d35a0a0697", + "0x9864d7880f778c42d33cf102c425e380d999d55a975a29c2774cad920dfddb80087a446c4f32ed9a6ab5f22ec6f82af0", + "0x90f67fe26f11ca13e0c72b2c2798c0d0569ed6bc4ce5bbaf517c096e7296d5dd5685a25012f6c6d579af5b4f5d400b37", + "0xa228872348966f26e28a962af32e8fa7388d04bc07cfc0224a12be10757ac7ab16a3387c0b8318fcb0c67384b0e8c1a4", + "0xa9d9d64bba3c03b51acf70aeb746a2712ddafe3b3667ae3c25622df377c2b5504e7ab598263bec835ab972283c9a168b", + "0x932128971c9d333f32939a1b46c4f7cf7e9d8417bd08dc5bd4573ccbd6ec5b460ac8880fb7f142f7ef8a40eef76d0c6d", + "0x964115e7838f2f197d6f09c06fbb2301d6e27c0ecdf208350cf3b36c748436dac50f47f9f9ac651c09ab7ad7221c7e43", + "0xa5941f619e5f55a9cf6e7f1499b1f1bcddcc7cf5e274efedaaad73a75bc71b1fc5c29cd903f6c69dc9a366a6933ca9d1", + "0xa154bf5eaec096029e5fe7c8bf6c695ae51ace356bb1ad234747776c7e1b406dee2d58864c3f4af84ed69f310974125e", + "0xb504e6209d48b0338ab1e4bdab663bac343bb6e0433466b70e49dc4464c1ec05f4a98111fd4450393607510ae467c915", + "0x813411918ea79bdde295393284dc378b9bdc6cfcb34678b9733ea8c041ac9a32c1e7906e814887469f2c1e39287e80f8", + "0x8be0369f94e4d72c561e6edb891755368660208853988647c55a8eed60275f2dd6ee27db976de6ecf54ac5c66aaf0ae6", + "0xa7e2701e55b1e7ea9294994c8ad1c080db06a6fc8710cd0c9f804195dce2a97661c566089c80652f27b39018f774f85e", + "0x956b537703133b6ddf620d873eac67af058805a8cc4beb70f9c16c6787bf3cc9765e430d57a84a4c3c9fbdd11a007257", + "0x835ae5b3bb3ee5e52e048626e3ddaa49e28a65cb94b7ecdc2e272ff603b7058f1f90b4c75b4b9558f23851f1a5547a35", + "0x85d67c371d1bf6dc72cca7887fa7c886ce988b5d77dc176d767be3205e80f6af2204d6530f7060b1f65d360a0eaeff30", + "0xa84a6647a10fcef8353769ef5f55a701c53870054691a6e9d7e748cbe417b3b41dbb881bae67adc12cb6596c0d8be376", + "0x87ffe271fc0964cb225551c7a61008d8bcb8b3d3942970dbcc2b9f4f9045a767971880368ea254e2038a3a0b94ecf236", + "0x964bb721c51d43ee7dd67c1a2b7dd2cc672ce8fad78c22dcddb43e6aab48d9a4a7dc595d702aa54a6fb0ffabf01f2780", + "0xa89b3f84bb7dcbe3741749776f5b78a269f6b1bebb8e95d3cc80b834fd2177c6be058d16cacfd0d5e1e35e85cde8b811", + "0xb4314538e003a1587b5592ff07355ea03239f17e75c49d51f32babe8e048b90b046a73357bcb9ce382d3e8fbe2f8e68b", + "0x86daf4bf201ae5537b5d4f4d734ed2934b9cf74de30513e3280402078f1787871b6973aa60f75858bdf696f19935a0e2", + "0xb1adf5d4f83f089dc4f5dae9dbd215322fa98c964e2eaa409bf8ca3fa5c627880a014ed209492c3894b3df1c117236c4", + "0xb508d52382c5bac5749bc8c89f70c650bb2ed3ef9dc99619468c387c1b6c9ff530a906dfa393f78f34c4f2f31478508a", + "0xa8349a5865cb1f191bebb845dfbc25c747681d769dbffd40d8cedf9c9a62fa2cbc14b64bb6121120dab4e24bef8e6b37", + "0xaf0500d4af99c83db8890a25f0be1de267a382ec5e9835e2f3503e1bac9412acf9ff83a7b9385708ef8187a38a37bc77", + "0xb76d57a1c1f85b8a8e1722a47057b4c572800957a6b48882d1fc21309c2e45f648a8db0fcff760d1dbc7732cf37c009b", + "0xb93c996cec0d3714667b5a5a5f7c05a7dc00bbc9f95ac8e310626b9e41ae4cc5707fac3e5bd86e1e1f2f6d9627b0da94", + "0x93216fdb864217b4c761090a0921cf8d42649ab7c4da1e009ec5450432564cb5a06cb6e8678579202d3985bd9e941cef", + "0x8b8be41105186a339987ae3a5f075fbc91f34b9984d222dfed0f0f85d2f684b56a56ab5dc812a411570491743d6c8b18", + "0x959b72782a6b2469e77fe4d492674cc51db148119b0671bd5d1765715f49fa8a87e907646671161586e84979ef16d631", + "0x86b7fc72fb7e7904ea71d5e66ba0d5d898ace7850985c8cc4a1c4902c5bf94351d23ce62eed45e24321fb02adfa49fc8", + "0xa2f244e7c9aa272cb0d067d81d25e5a3045b80b5a520b49fd5996ece267a7f1bea42e53147bbf153d9af215ea605fc9e", + "0x81aa2efa5520eebc894ce909ba5ce3250f2d96baa5f4f186a0637a1eea0080dd3a96c2f9fadf92262c1c5566ddb79bab", + "0xb607dd110cfe510d087bcff9a18480ba2912662256d0ab7b1d8120b22db4ad036b2266f46152754664c4e08d0fc583f6", + "0x8f588d5f4837e41312744caac5eee9ddc3ad7085871041694f0b5813edf83dc13af7970f7c9b6d234a886e07fa676a04", + "0x924921b903207783b31016cbec4e6c99e70f5244e775755c90d03a8b769738be3ba61577aca70f706a9c2b80040c9485", + "0xae0a42a222f1a71cd0d3c69ffb2f04c13e1940cce8efabe032629f650be3ceed6abb79651dbb81cb39a33286eb517639", + "0xa07d7d76460f31f5f0e32e40a5ea908d9d2aebf111ac4fadee67ef6540b916733c35a777dcdc05f6417726ca1f2d57dd", + "0x88d7f8a31f8c99794291847d28745e5d0b5d3b9684ca4170b686ffbb5bb521a3ef6746c3c8db22e4250a0cdff7939d96", + "0x849573071fd98c020dc9a8622a9eff221cb9f889bde259e7127a8886b73bef7ad430b87750915658918dcfb6b7b4d8d3", + "0xb12d59f732fa47fad175d6263734da8db89230fd340a46ad1cdee51e577041a5c80bf24cd195593e637daf1a66ef5a98", + "0xabbcfb8a4a6d5e269ee1ac5e277df84416c73ca55ec88317f73608201af25af0cb65b943c54684a5651df3a26e3daca2", + "0xab157f589bdbaf067a6a7ba7513df0492933855d39f3a081196cf2352e0ddc0162d476c433320366e3df601e0556278d", + "0xa86c0619b92e5ae4f7daa876a2abc5ba189156afc2fa05eef464dfa342ba37fc670d0dc308ad3822fcb461ab001bac30", + "0xa3f292946476cfe8d5e544a5325439a00e0165a5f9bf3bb6a53f477baeac7697cc0377745536681aa116f326ce911390", + "0x8aecbbfd442a6a0f01c1c09db5d9d50213eb6f1ff6fab674cde3da06a4edff3ed317e804f78300c22ef70c336123e05d", + "0x834ed4b58211fcd647d7bf7c0a3ba9085184c5c856b085e8a0fcd5215c661ef43d36f3f0f6329a9f1370501b4e73b6e4", + "0xa114ea5ad2b402a0de6105e5730907f2f1e458d28ae35144cf49836e0ad21325fe3e755cfb67984ae0a32e65402aad1e", + "0xa005f12bed97d71cee288b59afe9affb4d256888727343944a99913980df2c963fe02f218e6ea992f88db693a4498066", + "0xa010f286ab06b966e3b91ff8f1bdbe2fe9ab41a27bc392d5787aa02a46e5080e58c62c7d907818caae9f6a8b8123e381", + "0x857bd6df2ddef04dbc7c4f923e0b1696d3016c8bfed07fdfa28a3a3bd62d89b0f9df49aae81cbb6883d5e7b4fadae280", + "0xb3927030da445bc4756ac7230a5d87412a4f7510581fb422212ce2e8cf49689aca7ba71678743af06d4de4914c5aa4a0", + "0xb86403182c98fcce558d995f86752af316b3b2d53ba32075f71c7da2596747b7284c34a1a87de604fcc71e7e117a8add", + "0x98dd19b5527733041689b2a4568edaf6aa0fe1a3dd800c290cda157b171e053648a5772c5d3d4c80e5a795bc49adf12e", + "0x88a3c227bb7c9bff383f9ad3f7762245939a718ab85ae6e5e13180b12bf724d42054d3852b421c1cd1b3670baddecb63", + "0xb3cfd9ad66b52bbe57b5fff0fad723434d23761409b92c4893124a574acc1e6b1e14b4ec507661551cbbe05e16db362e", + "0x923e1bb482cf421dd77801f9780f49c3672b88508a389b94015fd907888dc647ee9ea8ec8d97131d235d066daf1f42b7", + "0x8d5e16240f04f92aa948181d421006bdbc7b215648fb6554193224d00cf337ebbb958f7548cf01b4d828acffb9fbc452", + "0x8b2b8f18ad0559746f6cda3acca294a1467fb1a3bc6b6371bc3a61a3bfe59418934fa8706f78b56005d85d9cb7f90454", + "0xa9316e2a94d6e31426d2ae7312878ba6baaac40f43e2b8a2fa3ab5a774c6918551554b2dbb23dc82f70ba3e0f60b5b0d", + "0x9593116d92cf06b8cd6905a2ce569ee6e69a506c897911f43ae80fc66c4914da209fc9347962034eebbc6e3e0fe59517", + "0x887d89d2b2d3c82b30e8f0acf15f0335532bd598b1861755498610cb2dd41ff5376b2a0bb757cb477add0ce8cfe7a9fc", + "0xb514cfe17875ecb790ad055271cc240ea4bda39b6cfa6a212908849c0875cb10c3a07826550b24c4b94ea68c6bb9e614", + "0xa563d5187966d1257d2ed71d53c945308f709bcc98e3b13a2a07a1933dc17bcb34b30796bd68c156d91811fbd49da2cb", + "0xa7195ccc53b58e65d1088868aeeb9ee208103e8197ad4c317235bb2d0ad3dc56cb7d9a7186416e0b23c226078095d44c", + "0xa838e7a368e75b73b5c50fbfedde3481d82c977c3d5a95892ac1b1a3ea6234b3344ad9d9544b5a532ccdef166e861011", + "0x9468ed6942e6b117d76d12d3a36138f5e5fb46e3b87cf6bb830c9b67d73e8176a1511780f55570f52d8cdb51dcf38e8c", + "0x8d2fc1899bc3483a77298de0e033085b195caf0e91c8be209fd4f27b60029cbe1f9a801fbd0458b4a686609762108560", + "0x8f4e44f8ca752a56aa96f3602e9234ad905ad9582111daf96a8c4d6f203bf3948f7ce467c555360ad58376ee8effd2ba", + "0x8fb88640b656e8f1c7c966c729eb2ba5ccf780c49873f8b873c6971840db7d986bdf1332ba80f8a0bb4b4ee7401468fa", + "0xb72aa3235868186913fb5f1d324e748cd3ce1a17d3d6e6ea7639a5076430fe0b08841c95feb19bb94181fe59c483a9eb", + "0xb8b102690ebb94fc4148742e7e3fd00f807b745b02cbe92cd92992c9143b6db7bb23a70da64a8b2233e4a6e572fc2054", + "0x8c9ae291f6cd744e2c6afe0719a7fc3e18d79307f781921fb848a0bf222e233879c1eca8236b4b1be217f9440859b6ce", + "0xa658ede47e14b3aad789e07f5374402f60e9cacb56b1b57a7c6044ca2418b82c98874e5c8c461898ebd69e38fecd5770", + "0x89c0cb423580e333923eb66bda690f5aca6ec6cba2f92850e54afd882ba608465a7dbb5aa077cd0ca65d9d00909348ab", + "0xaed8e28d98d5508bd3818804cf20d296fe050b023db2ed32306f19a7a3f51c7aaafed9d0847a3d2cd5ba5b4dabbc5401", + "0x96a0fcd6235f87568d24fb57269a94402c23d4aa5602572ad361f3f915a5f01be4e6945d576d51be0d37c24b8b0f3d72", + "0x935d0c69edd5dfa8ed07c49661b3e725b50588f814eb38ea31bcc1d36b262fae40d038a90feff42329930f8310348a50", + "0x900518288aa8ea824c7042f76710f2ea358c8bb7657f518a6e13de9123be891fa847c61569035df64605a459dad2ecc8", + "0x947d743a570e84831b4fb5e786024bd752630429d0673bf12028eb4642beb452e133214aff1cfa578a8856c5ebcb1758", + "0xa787266f34d48c13a01b44e02f34a0369c36f7ec0aae3ec92d27a5f4a15b3f7be9b30b8d9dd1217d4eeedff5fd71b2e5", + "0xa24b797214707ccc9e7a7153e94521900c01a1acd7359d4c74b343bfa11ea2cdf96f149802f4669312cd58d5ab159c93", + "0x97f5ee9c743b6845f15c7f0951221468b40e1edaef06328653a0882793f91e8146c26ac76dd613038c5fdcf5448e2948", + "0x80abd843693aed1949b4ea93e0188e281334163a1de150c080e56ca1f655c53eb4e5d65a67bc3fc546ed4445a3c71d00", + "0x908e499eb3d44836808dacff2f6815f883aeced9460913cf8f2fbbb8fe8f5428c6fc9875f60b9996445a032fd514c70f", + "0xae1828ef674730066dc83da8d4dd5fa76fc6eb6fa2f9d91e3a6d03a9e61d7c3a74619f4483fe14cddf31941e5f65420a", + "0xa9f4dbe658cd213d77642e4d11385a8f432245b098fccd23587d7b168dbeebe1cca4f37ee8d1725adb0d60af85f8c12f", + "0x93e20ee8a314b7772b2439be9d15d0bf30cd612719b64aa2b4c3db48e6df46cea0a22db08ca65a36299a48d547e826a7", + "0xa8746a3e24b08dffa57ae78e53825a9ddbbe12af6e675269d48bff4720babdc24f907fde5f1880a6b31c5d5a51fbb00e", + "0xb5e94dfab3c2f5d3aea74a098546aa6a465aa1e3f5989377d0759d1899babf543ad688bb84811d3e891c8713c45886c5", + "0xa3929bada828bd0a72cda8417b0d057ecb2ddd8454086de235540a756e8032f2f47f52001eb1d7b1355339a128f0a53b", + "0xb684231711a1612866af1f0b7a9a185a3f8a9dac8bde75c101f3a1022947ceddc472beb95db9d9d42d9f6ccef315edbc", + "0xaf7809309edbb8eb61ef9e4b62f02a474c04c7c1ffa89543d8c6bf2e4c3d3e5ecbd39ec2fc1a4943a3949b8a09d315a6", + "0xb6f6e224247d9528ef0da4ad9700bee6e040bbf63e4d4c4b5989d0b29a0c17f7b003c60f74332fefa3c8ddbd83cd95c1", + "0xadbcec190a6ac2ddd7c59c6933e5b4e8507ce5fd4e230effc0bd0892fc00e6ac1369a2115f3398dfc074987b3b005c77", + "0x8a735b1bd7f2246d3fa1b729aecf2b1df8e8c3f86220a3a265c23444bdf540d9d6fe9b18ed8e6211fad2e1f25d23dd57", + "0x96b1bf31f46766738c0c687af3893d098d4b798237524cb2c867ed3671775651d5852da6803d0ea7356a6546aa9b33f2", + "0x8036e4c2b4576c9dcf98b810b5739051de4b5dde1e3e734a8e84ab52bc043e2e246a7f6046b07a9a95d8523ec5f7b851", + "0x8a4f4c32ee2203618af3bb603bf10245be0f57f1cfec71037d327fa11c1283b833819cb83b6b522252c39de3ce599fa5", + "0xad06ed0742c9838e3abaaffdb0ac0a64bad85b058b5be150e4d97d0346ed64fd6e761018d51d4498599669e25a6e3148", + "0x8d91cb427db262b6f912c693db3d0939b5df16bf7d2ab6a7e1bc47f5384371747db89c161b78ff9587259fdb3a49ad91", + "0xae0a3f84b5acb54729bcd7ef0fbfdcf9ed52da595636777897268d66db3de3f16a9cf237c9f8f6028412d37f73f2dfad", + "0x8f774109272dc387de0ca26f434e26bc5584754e71413e35fa4d517ee0f6e845b83d4f503f777fe31c9ec05796b3b4bc", + "0xa8670e0db2c537ad387cf8d75c6e42724fae0f16eca8b34018a59a6d539d3c0581e1066053a2ec8a5280ffabad2ca51f", + "0xac4929ed4ecad8124f2a2a482ec72e0ef86d6a4c64ac330dab25d61d1a71e1ee1009d196586ce46293355146086cabba", + "0x845d222cb018207976cc2975a9aa3543e46c861486136d57952494eb18029a1ebb0d08b6d7c67c0f37ee82a5c754f26f", + "0xb99fa4a29090eac44299f0e4b5a1582eb89b26ed2d4988b36338b9f073851d024b4201cd39a2b176d324f12903c38bee", + "0x9138823bc45640b8f77a6464c171af2fe1700bdc2b7b88f4d66b1370b3eafe12f5fbb7b528a7e1d55d9a70ca2f9fc8e6", + "0x8ac387dc4cf52bc48a240f2965ab2531ae3b518d4d1f99c0f520a3d6eb3d5123a35ef96bed8fa71ee2f46793fa5b33b3", + "0x864adec6339d4c2ba2525621fceabd4c455902f6f690f31a26e55413e0722e5711c509dc47ce0bcc27bbdc7651768d2d", + "0xa0a52edb72268a15201a968dabc26a22909620bda824bd548fb8c26cc848f704166ed730d958f0173bd3b0a672f367bd", + "0x949e445b0459983abd399571a1a7150aab3dd79f4b52a1cd5d733e436c71c1d4b74287c6b0ce6cc90c6711ba4c541586", + "0x858966355dac11369e3b6552f2b381665181693d5a32e596984da3314021710b25a37d8c548b08700eea13d86cb22f21", + "0x974bcbb8d38c5e6518745cc03ad436e585b61f31d705e7e2e5085da9655d768ac4d800904f892c3dab65d6223e3f1fd6", + "0x8092b6506b01308bf6187fde5ebd4fa7448c9a640961ba231be22ac5fa2c7635ef01e8b357722c7695d09b723101ea2a", + "0xa5b8ef360bf28533ee17d8cd131fff661d265f609db49599085c0c7d83b0af409a1b5c28e3a5e5d7f8459a368aa121e8", + "0xb031b6d5e3ceab0f0c93314b3b675f55cf18cbc86f70444af266fe39cb22fd7dad75d8c84e07f1c1bfa2cb8283e1361a", + "0x93ad489e4f74658320c1cceed0137c023d3001a2c930ed87e6a21dbf02f2eb6ad1c1d8bcb3739c85dcfbecb040928707", + "0xb15e4ec2cdab0d34aec8d6c50338812eb6ecd588cf123a3e9d22a7ca23b5a98662af18289f09e6cdd85a39a2863c945c", + "0xb304f71a9717cf40c22073f942618b44bf27cd5e2ed4a386ad45d75b0fcb5a8dafd35158211eaf639495c6f1a651cedb", + "0xb82d78d3eaaa7c5101b7a5aae02bd4f002cd5802d18c3abcda0dd53b036661c6d3c8b79e0abe591eab90b6fdc5fef5e3", + "0xabbd1884243a35578b80914a5084449c237ee4e4660c279d1073a4d4217d1b55c6b7e9c087dfd08d94ac1416273d8d07", + "0x92f4b61c62502745e3e198ec29bca2e18696c69dcb914d1f3a73f4998d012b90caf99df46e9bb59942e43cce377fe8fd", + "0x906e79df98185820c8208844e1ba6bd86cb96965814b01310bd62f22cbec9b5d379b2ef16772d6fc45a421b60cfd68fe", + "0xa0eae2784ef596e2eb270dd40c48d6c508e4394c7d6d08d4cc1b56fde42b604d10ba752b3a80f2c4a737e080ef51b44f", + "0x94c084985e276dc249b09029e49a4ef8a369cd1737b51c1772fbb458d61e3fe120d0f517976eba8ffa5711ba93e46976", + "0x83619a0157eff3f480ab91d1d6225fead74c96a6fd685333f1e8e4d746f6273e226bad14232f1d1168a274e889f202f1", + "0xa724fe6a83d05dbbf9bb3f626e96db2c10d6d5c650c0a909415fbda9b5711c8b26e377201fb9ce82e94fa2ab0bf99351", + "0xa8a10c1b91a3a1fa2d7fd1f78a141191987270b13004600601d0f1f357042891010717319489f681aa8a1da79f7f00d5", + "0xa398a2e95b944940b1f8a8e5d697c50e7aa03994a8a640dfad4ea65cfb199a4d97861a3ec62d1c7b2b8d6e26488ca909", + "0xa2eedfe5452513b2a938fffd560798ef81379c5a5032d5b0da7b3bb812addbaad51f564c15d9acbbfc59bb7eddd0b798", + "0xab31c572f6f145a53e13b962f11320a1f4d411739c86c88989f8f21ab629639905b3eedb0628067942b0dc1814b678ca", + "0xad032736dd0e25652d3566f6763b48b34ea1507922ed162890cd050b1125ec03b6d41d34fccba36ec90336f7cdf788ed", + "0x83028a558a5847293147c483b74173eca28578186137df220df747fccd7d769528d7277336ea03c5d9cdd0bc5ae3d666", + "0xab5d182cd1181de8e14d3ef615580217c165e470b7a094a276b78a3003089123db75c6e1650bf57d23e587c587cd7472", + "0xa4793e089fbdb1597654f43b4f7e02d843d4ab99ee54099c3d9f0bd5c0c5657c90bb076379a055b00c01b12843415251", + "0x98bdc52ee062035356fb2b5c3b41673198ddc60b2d1e546cb44e3bb36094ef3c9cf2e12bbc890feb7d9b15925439d1ea", + "0xa4f90cca6f48024a0341bd231797b03693b34e23d3e5b712eb24aba37a27827319b2c16188f97c0636a0c115381dc659", + "0x8888e6c2e4a574d04ba5f4264e77abc24ccc195f1a7e3194169b8a2ceded493740c52db4f9833b3dbf4d67a3c5b252cb", + "0x83dc4e302b8b0a76dc0292366520b7d246d73c6aebe1bdd16a02f645c082197bcff24a4369deda60336172cefbcf09af", + "0xa4eb2741699febfeb793914da3054337cc05c6fa00d740e5f97cb749ae16802c6256c9d4f0f7297dcdbb8b9f22fc0afa", + "0x8b65557d5be273d1cb992a25cfce40d460c3f288d5cb0a54bdef25cbd17cdea5c32ec966e493addf5a74fd8e95b23e63", + "0x97c6577e76c73837bcb398b947cb4d3323d511141e0ddd0b456f59fbb1e8f920a5c20d7827a24309145efddee786140f", + "0xabcc0849ffe2a6a72157de907907b0a52deece04cf8317bee6fe1d999444b96e461eac95b6afde3d4fe530344086a625", + "0x9385c0115cb826a49df1917556efa47b5b5e4022b6a0d2082053d498ec9681da904ecf375368bb4e385833116ea61414", + "0x8b868c1841f0cdc175c90a81e610b0652c181db06731f5c8e72f8fafa0191620742e61a00db8215a991d60567b6a81ca", + "0xa8df15406f31b8fcf81f8ff98c01f3df73bf9ec84544ddec396bdf7fafa6fe084b3237bf7ef08ad43b26517de8c3cd26", + "0xa9943d21e35464ce54d4cc8b135731265a5d82f9ccf66133effa460ffdb443cdb694a25320506923eede88d972241bf2", + "0xa1378ee107dd7a3abcf269fd828887c288363e9b9ca2711377f2e96d2ed5e7c5ec8d3f1da995a3dcbedf1752d9c088fc", + "0x8a230856f9227b834c75bdebc1a57c7298a8351874bf39805c3e0255d6fd0e846f7ad49709b65ec1fd1a309331a83935", + "0x877bcf42549d42610e1780e721f5800972b51ba3b45c95c12b34cb35eeaf7eac8fa752edd7b342411820cf9093fea003", + "0x84c7a0b63842e50905624f1d2662506b16d1f3ea201877dfc76c79181c338b498eceb7cad24c2142c08919120e62f915", + "0x8e18b1bd04b1d65f6ed349b5d33a26fe349219043ead0e350b50ae7a65d6ff5f985dd9d318d3b807d29faa1a7de4fe42", + "0x8ea7b5a7503e1f0b3c3cd01f8e50207044b0a9c50ed1697794048bbe8efd6659e65134d172fb22f95439e1644f662e23", + "0xb1954a2818cad1dad6d343a7b23afa9aa8ad4463edc4eb51e26e087c2010927535020d045d97d44086d76acdb5818cbf", + "0xa5271ea85d0d21fa1ff59b027cf88847c0f999bbf578599083ff789a9b5228bc161e1c81deb97e74db1a82a0afd61c50", + "0xaa2fa4c05af3387e2c799315781d1910f69977ec1cfea57a25f1a37c63c4daaa3f0ecd400884a1673e17dd5300853bcf", + "0xb1cd2a74ca0b8e6090da29787aef9b037b03b96607983a308b790133bd21297b21ca4e2edec890874096dbf54e9d04c3", + "0x801931607ec66a81272feaa984f0b949ad12d75ecf324ba96627bd4dc5ddead8ebf088f78e836b6587c2b6c0b3366b6c", + "0x95d79504710bdf0ad9b9c3da79068c30665818c2f0cdbba02cc0a5e46e29d596032ac984441b429bd62e34535c8d55b0", + "0x9857d41e25e67876510ff8dadf0162019590f902da1897da0ef6fc8556e3c98961edb1eb3a3a5c000f6c494413ded15e", + "0x8740c9ffe6bd179c19a400137c3bd3a593b85bd4c264e26b4dfb9e2e17ac73e5b52dfacc1dcb4033cfc0cd04785f4363", + "0x977f98f29d948b4097a4abdf9345f4c1fb0aa94ba0c6bf6faa13b76f3a3efc8f688e1fe96099b71b3e1c05041118c8d1", + "0xa364422b1239126e3e8d7b84953ce2181f9856319b0a29fcab81e17ac27d35798088859c1cfc9fc12b2dbbf54d4f70b3", + "0xa0f6ba637f0db7a48e07439bb92ddb20d590ce9e2ed5bab08d73aa22d82c32a9a370fe934cbe9c08aeb84b11adcf2e0e", + "0xa2c548641bd5b677c7748327cca598a98a03a031945276be6d5c4357b6d04f8f40dd1c942ee6ec8499d56a1290ac134d", + "0x9863e9cc5fbcdbd105a41d9778d7c402686bfd2d81d9ed107b4fda15e728871c38647529693306855bee33a00d257a7e", + "0xa54173bf47b976290c88fd41f99300135de222f1f76293757a438450880e6f13dbde3d5fe7afc687bdfbcfc4fbc1fc47", + "0xb8db413917c60907b73a997b5ab42939abd05552c56a13525e3253eb72b83f0d5cc52b695968a10005c2e2fe13290e61", + "0xa1f8388ef21697c94ba90b1a1c157f0dc138e502379e6fc5dc47890d284563e5db7716266e1b91927e5adf3cde4c0a72", + "0x9949013a59d890eb358eab12e623b2b5edb1acbee238dfad8b7253102abc6173922e188d5b89ec405aa377be8be5f16d", + "0xa00fdb7710db992041f6ddb3c00099e1ce311dea43c252c58f560c0d499983a89de67803a8e57baa01ee9d0ee6fa1e44", + "0xa8b1bcbed1951c9cdb974b61078412881b830b48cd6b384db0c00fa68bcc3f4312f8e56c892ea99d3511857ef79d3db9", + "0x8f3ee78404edc08af23b1a28c2012cee0bdf3599a6cb4ea689fc47df4a765ef519191819a72562b91a0fbcdb896a937e", + "0x8155bbb7fa8d386848b0a87caae4da3dec1f3dade95c750a64a8e3555166ccc8799f638bd80ed116c74e3a995541587a", + "0xabfe30adbc0a6f1fd95c630ed5dac891b85384fa9331e86b83217f29dff0bd7cad19d328485715a7e3df9a19069d4d2f", + "0x89d0783e496ee8dbb695764b87fb04cee14d4e96c4ba613a19736971c577d312079048142c12ce5b32b21e4d491d281b", + "0x856b8dbc9c5d8f56b6bb7d909f339ca6da9a8787bba91f09130a025ab6d29b64dbf728ba6ed26e160a23c1cdb9bc037b", + "0x8a30dd2ea24491141047a7dfe1a4af217661c693edf70b534d52ca547625c7397a0d721e568d5b8398595856e80e9730", + "0xae7e1412feb68c5721922ed9279fb05549b7ef6812a4fd33dbbbd7effab756ab74634f195d0c072143c9f1fd0e1ee483", + "0xb7ce970e06fa9832b82eef572f2902c263fda29fdce9676f575860aae20863046243558ede2c92343616be5184944844", + "0x85ed0531f0e5c1a5d0bfe819d1aa29d6d5ff7f64ad8a0555560f84b72dee78e66931a594c72e1c01b36a877d48e017ca", + "0xb8595be631dc5b7ea55b7eb8f2982c74544b1e5befc4984803b1c69727eac0079558182f109e755df3fd64bee00fcaa5", + "0x99e15a66e5b32468ef8813e106271df4f8ba43a57629162832835b8b89402eb32169f3d2c8de1eb40201ce10e346a025", + "0x844c6f5070a8c73fdfb3ed78d1eddca1be31192797ad53d47f98b10b74cc47a325d2bc07f6ee46f05e26cf46a6433efb", + "0x974059da7f13da3694ad33f95829eb1e95f3f3bfc35ef5ef0247547d3d8ee919926c3bd473ab8b877ff4faa07fcc8580", + "0xb6f025aecc5698f6243cc531782b760f946efebe0c79b9a09fe99de1da9986d94fa0057003d0f3631c39783e6d84c7d5", + "0xb0c5358bc9c6dfe181c5fdf853b16149536fbb70f82c3b00db8d854aefe4db26f87332c6117f017386af8b40288d08f9", + "0xa3106be5e52b63119040b167ff9874e2670bd059b924b9817c78199317deb5905ae7bff24a8ff170de54a02c34ff40a4", + "0xad846eb8953a41c37bcd80ad543955942a47953cbc8fb4d766eac5307892d34e17e5549dc14467724205255bc14e9b39", + "0xb16607e7f0f9d3636e659e907af4a086ad4731488f5703f0917c4ce71a696072a14a067db71a3d103530920e1ec50c16", + "0x8ed820e27116e60c412c608582e9bb262eaaf197197c9b7df6d62b21a28b26d49ea6c8bb77dfde821869d9b58025f939", + "0x97bc25201d98cde389dd5c0c223a6f844393b08f75d3b63326343073e467ac23aacef630ddc68545ea874299ba4a3b4f", + "0xb73c9695ad2eefd6cc989a251c433fab7d431f5e19f11d415a901762717d1004bb61e0cc4497af5a8abf2d567e59fef4", + "0xadaabe331eea932533a7cc0cf642e2a5e9d60bbc92dd2924d9b429571cbf0d62d32c207b346607a40643c6909b8727e2", + "0xa7b1bbfe2a5e9e8950c7cb4daab44a40c3ffab01dc012ed7fe445f4af47fa56d774a618fafe332ab99cac4dfb5cf4794", + "0xb4a3c454dcd5af850212e8b9ba5fe5c0d958d6b1cabbf6c6cfe3ccbc4d4c943309c18b047256867daf359006a23f3667", + "0xa5c0b32f6cef993834c1381ec57ad1b6f26ae7a8190dd26af0116e73dadc53bb0eeb1911419d609b79ce98b51fdc33bc", + "0xac2f52de3ecf4c437c06c91f35f7ac7d171121d0b16d294a317897918679f3b9db1cef3dd0f43adb6b89fe3030728415", + "0x94722ae6d328b1f8feaf6f0f78804e9b0219de85d6f14e8626c2845681841b2261d3e6a2c5b124086b7931bf89e26b46", + "0xa841a0602385d17afabca3a1bb6039167d75e5ec870fea60cfcaec4863039b4d745f1a008b40ec07bca4e42cb73f0d21", + "0x8c355f0a1886ffced584b4a002607e58ff3f130e9de827e36d38e57cb618c0cb0b2d2dea2966c461cb3a3887ede9aef1", + "0xa6a9817b0fc2fd1786f5ba1a7b3d8595310987fb8d62f50a752c6bb0b2a95b67d03a4adfd13e10aa6190a280b7ee9a67", + "0xa1d2e552581ecbafeaef08e389eaa0b600a139d446e7d0648ac5db8bbbf3c438d59497e3a2874fc692b4924b87ff2f83", + "0xa1b271c55389f25639fe043e831e2c33a8ba045e07683d1468c6edd81fedb91684e4869becfb164330451cfe699c31a8", + "0x8c263426e7f7e52f299d57d047a09b5eeb893644b86f4d149535a5046afd655a36d9e3fdb35f3201c2ccac2323a9582e", + "0xb41c242a7f7880c714241a97d56cce658ee6bcb795aec057a7b7c358d65f809eb901e0d51256826727dc0dc1d1887045", + "0x93001b9445813c82f692f94c0dc1e55298f609936b743cf7aae5ebfa86204f38833d3a73f7b67314be67c06a1de5682d", + "0x82087536dc5e78422ad631af6c64c8d44f981c195ddea07d5af9bb0e014cdc949c6fa6e42fce823e0087fdb329d50a34", + "0x8e071861ceba2737792741c031f57e0294c4892684506b7c4a0fc8b2f9a0a6b0a5635de3d1e8716c34df0194d789ae86", + "0xb471c997e1e11774bd053f15609d58838a74073a6c089a7a32c37dd3f933badf98c7e5833263f3e77bc0d156a62dd750", + "0x8d2d8686fb065b61714414bb6878fff3f9e1e303c8e02350fd79e2a7f0555ded05557628152c00166ce71c62c4d2feaa", + "0xae4c75274d21c02380730e91de2056c0262ffcecf0cbdb519f0bdb0b5a10ae2d4996b3dc4b3e16dbaea7f0c63d497fef", + "0x97140d819e8ca6330e589c6debdee77041c5a9cedb9b8cbd9c541a49207eeb7f6e6b1c7e736ec8ba6b3ab10f7fcd443a", + "0xaf6659f31f820291a160be452e64d1293aa68b5074b4c066dac169b8d01d0179139504df867dc56e2a6120354fc1f5be", + "0xa5e5d8088a368024617bfde6b731bf9eee35fc362bed3f5dfdd399e23a2495f97f17728fec99ca945b3282d1858aa338", + "0xa59cfc79d15dbdde51ab8e5129c97d3baba5a0a09272e6d2f3862370fdbaf90994e522e8bd99d6b14b3bb2e9e5545c6f", + "0xa30499b068083b28d6c7ddcc22f6b39b5ec84c8ee31c5630822c50ea736bb9dca41c265cffc6239f1c9ef2fd21476286", + "0x88ffe103eca84bbe7d1e39a1aa599a5c7c9d5533204d5c4e085402a51441bb8efb8971efe936efbbfa05e5cb0d4b8017", + "0xb202356fbf95a4d699154639e8cb03d02112c3e0128aab54d604645d8510a9ba98936028349b661672c3a4b36b9cb45d", + "0x8b89bb6574bf3524473cff1ff743abcf1406bd11fb0a72070ccd7d8fce9493b0069fb0c6655252a5164aee9e446ea772", + "0x93247b1038fa7e26667ee6446561d4882dc808d1015daafb705935ddc3598bb1433182c756465960480f7b2de391649e", + "0xb027f94d3358cbb8b6c8c227300293a0dee57bf2fee190a456ad82ecfb6c32f8090afa783e2ab16f8139805e1fb69534", + "0xa18bb1849b2f06c1d2214371031d41c76ffa803ee3aa60920d29dbf3db5fbfac2b7383d5d0080ba29ce25c7baa7c306b", + "0x827bf9fd647e238d5ac961c661e5bbf694b4c80b3af8079f94a2484cb8fba2c8cf60e472ebcd0b0024d98ae80ad2ff5a", + "0x838e891218c626a7f39b8fd546b013587408e8e366ecc636b54f97fa76f0a758bc1effa1d0f9b6b3bc1a7fcc505970a0", + "0x836523b5e8902d6e430c6a12cff01e417d2bd7b402e03904034e3b39755dee540d382778c1abe851d840d318ebedce7f", + "0x850a77dda9ac6c217e2ef00bf386a1adec18b7f462f52801c4f541215690502a77ef7519b690e22fdf54dc2109e0ca38", + "0xa8265c6ae7b29fc2bda6a2f99ced0c1945dd514b1c6ca19da84b5269514f48a4f7b2ccbab65c9107cfd5b30b26e5462f", + "0xab3d02ee1f1267e8d9d8f27cc388e218f3af728f1de811242b10e01de83471a1c8f623e282da5a284d77884d9b8cde0e", + "0x831edaf4397e22871ea5ddee1e7036bab9cc72f8d955c7d8a97f5e783f40532edbbb444d0520fefcffeab75677864644", + "0x80484487977e4877738744d67b9a35b6c96be579a9faa4a263e692295bb6e01f6e5a059181f3dd0278e2c3c24d10a451", + "0xaae65a18f28c8812617c11ecf30ad525421f31fb389b8b52d7892415e805a133f46d1feca89923f8f5b8234bd233486a", + "0xb3a36fd78979e94288b4cefed82f043a7e24a4a8025479cc7eb39591e34603048a41ee606ee03c0b5781ebe26a424399", + "0xb748b3fc0d1e12e876d626a1ba8ad6ad0c1f41ea89c3948e9f7d2666e90173eb9438027fadcd741d3ae0696bd13840f1", + "0xacdd252d7c216c470683a140a808e011c4d5f1b4e91aeb947f099c717b6a3bad6651142cde988330827eb7d19d5fb25c", + "0xb9a25556a6ca35db1ed59a1ec6f23343eab207a3146e4fc3324136e411c8dba77efd567938c63a39c2f1c676b07d8cdb", + "0xa8db6aef8f5680d2bdb415d7bcaae11de1458678dcb8c90c441d5986c44f83a9e5855662d0c1aace999172d8628d8fe1", + "0xaf58147108e9909c3a9710cc186eab598682dca4bfd22481e040b8c000593ecb22c4ede4253ac9504e964dfa95a9b150", + "0x8dd8bb70f1c9aec0fcc9478f24dfc9c3c36c0bf5ff7a67c017fa4dab2ec633fbd7bc9d8aa41ea63e2696971ed7e375f5", + "0xaa98d600b22aff993a4d7a3ccabd314e1825b200cb598f6b797d7e4d6a76d89e34a4d156c06bddfc62f2ef9b4c809d1d", + "0x8a8fc960d6c51294b8205d1dabe430bef59bda69824fa5c3c3105bef22ac77c36d2d0f38ffc95ce63731de5544ccbeff", + "0xb6d1020efe01dc8032bd1b35e622325d7b9af9dcd5c9c87c48d7d6ebc58644454294c59b7f4b209204b5b1f899f473bf", + "0x8a750dc9fe4891f2dfe5759fb985939810e4cdc0b4e243ff324b6143f87676d8cb4bcb9dfb01b550801cedcaaa5349e2", + "0x98c13142d3a9c5f8d452245c40c6dae4327dd958e0fda85255ea0f87e0bcbaa42a3a0bd50407ed2b23f9f6317a8a4bc5", + "0x99f2b83d9ec4fc46085a6d2a70fd0345df10f4a724c1ba4dee082a1fde9e642e3091992ebf5f90a731abcb6ec11f6d9b", + "0xb218546ab2db565b2489ea4205b79daa19ef2acbf772ccaaa5e40150e67ea466090d07198444b48e7109939aa2319148", + "0x84f9d1d868e4b55e535f1016558f1789df0daa0ead2d13153e02f715fe8049b1ce79f5bc1b0bbbb0b7e4dd3c04783f3f", + "0x80d870d212fbddfdda943e90d35a5a8aa0509a7a1e7f8909f2fcb09c51c3026be47cc7a22620a3063406872105b4f81a", + "0xb5b15138ff6551fac535d4bbce2ea6adc516b6b7734b4601c66ec029da2615e3119dc9ad6a937344acfd7b50e4a1a2ae", + "0x95d2f97652086e7ceb54e1d32692b1c867ffba23c4325740c7f10d369283d1b389e8afa0df967831ade55696931e7934", + "0x8a5b580403e1a99cd208f707e8ce0d3f658c8280417683f69008d09cc74d835a85f7380f391b36ead9ac66d9eedd1cbe", + "0xa8b0c90bff34c86720637b5a2081f0f144cfe2205c1176cacd87d348609bc67af68aed72414dc9aa6f44a82c92c2a890", + "0x865abbdd96c496892c165a8de0f9e73348bf24fce361d7a9048710178a3625881afb0006e9f5ee39124866b87904c904", + "0xace67bb994adef4b6f841cdf349195608030044562780a7e9b00b58a4ff117268a03ff01e5a3a9d9d7eff1dd01f5f4bf", + "0xb9371d59185b3d2d320d3fefeadb06ba2aa7d164352fb8dc37571509509fa214d736d244ac625a09a033a10d51611e2e", + "0xa8ef992771422dcf2d6d84386fde9fe5dba88bfded3dfcd14074ca04331b4fd53a7f316615cdfaf10ed932cbb424a153", + "0x868cbc75f8f789ea45eded2768a1dac0763347e0d8e8028d316a21005f17be179d26d5965903e51b037f2f57fe41765d", + "0xb607111bcdfd05fa144aa0281b13ee736079ebbbf384d938a60e5e3579639ed8ef8eb9ca184868cdb220a8e130d4a952", + "0xaca55702af5cae4cae65576769effd98858307a71b011841c563b97c2aa5aeb5c4f8645d254f631ed1582df3dbbf17da", + "0xb9b5cbace76246e80c20dfcc6f1e2c757a22ab53f7fd9ff8a1d309538b55174e55e557a13bf68f095ff6a4fa637ef21a", + "0x8571b0a96871f254e2397c9be495c76379faf347801cb946b94e63212d6a0da61c80e5d7bebbabcd6eaa7f1029172fe5", + "0x902540326281e6dc9c20d9c4deaaf6fbbbcc3d1869bd0cf7f081c0525bea33df5cfa24ead61430fda47fb964fcc7994b", + "0x841af09279d3536a666fa072278950fabf27c59fc15f79bd52acb078675f8087f657929c97b4bc761cbade0ecb955541", + "0xa1f958b147ddf80ab2c0746ba11685c4bae37eb25bfa0442e7e1078a00d5311d25499da30f6d168cb9302ea1f2e35091", + "0x863d939381db37d5a5866964be3392a70be460f0353af799d6b3ed6307176972686bd378f8ad457435a4094d27e8dfb7", + "0x835cd4d7f36eff553d17483eb6c041b14280beb82c7c69bca115929658455a1931212976c619bafb8179aed9940a8cc6", + "0x8d0770e3cb8225e39c454a1fc76954118491b59d97193c72c174ecc7613051e5aed48a534016a8cf0795c524f771a010", + "0x91aa4edb82f6f40db2b7bd4789cc08786f6996ebed3cb6f06248e4884bc949793f04a4c5ea6eefe77984b1cc2a45d699", + "0x8fb494ca2449f659ff4838833507a55500a016be9293e76598bbae0a7cb5687e4693757c2b6d76e62bd6c7f19ed080bb", + "0xb59b104449a880a282c1dd6a3d8debb1d8814ef35aab5673c1e500ee4cb0e840fb23e05fa5a0af92509c26b97f098f90", + "0xaca908e3bad65e854ae6be6c5db441a06bcd47f5abafdfa8f5a83c8cd3c6e08c33cab139c45887887a478338e19ceb9f", + "0x806f5d802040313a31964fc3eb0ee18ac91b348685bed93c13440984ee46f3d2da7194af18c63dea4196549129660a4e", + "0xae4b2dca75c28d8f23b3ab760b19d839f39ff5a3112e33cb44cff22492604a63c382b88ec67be4b0266924dd438c3183", + "0x99d1c29c6bd8bf384e79cd46e30b8f79f9cbc7d3bf980e9d6ffba048f0fc487cac45c364a8a44bb6027ad90721475482", + "0xa16e861c1af76d35528c25bf804bfc41c4e1e91b2927d07d8e96bffe3a781b4934e9d131ecf173be9399800b8269efac", + "0xa253303234fb74f5829060cdcef1d98652441ab6db7344b1e470d195a95722675988048d840201c3b98e794b1e8b037c", + "0x905ac8a0ea9ce0eb373fb0f83dd4cbe20afb45b9d21ae307846fd4757d4d891b26a6711924e081e2b8151e14a496da18", + "0xb485315791e775b9856cc5a820b10f1fa5028d5b92c2f0e003ba55134e1eddb3eb25f985f2611a2257acf3e7cfdfab5e", + "0xb6189c0458b9a043ebc500abc4d88083a3487b7ac47ed5e13ab2a41e0a1bee50d54a406063f92bc96959f19e822a89a7", + "0xa30e15f995fd099a223fc6dc30dad4b8d40bee00caa2bc3223ba6d53cd717c4968a3e90c4618c711ed37cc4cd4c56cf3", + "0xa1b1ed07fcc350bb12a09cd343768d208fc51a6b3486f0ece8f5a52f8a5810b4bc7ab75582ec0bc2770aed52f68eace5", + "0x88aa739fbae4bece147ba51a863e45d5f7203dbc3138975dc5aef1c32656feb35f014d626e0d5b3d8b1a2bda6f547509", + "0xab570f3c8eabfca325b3a2ea775ef6b0c6e6138c39d53c2310329e8fb162869fde22b0e55688de9eb63d65c37598fca3", + "0x89d274762c02158e27cb37052e296a78f2b643eb7f9ae409f8dac5c587d8b4d82be4ef7c79344a08ebec16ac4a895714", + "0x99c411d2ad531e64f06e604d44c71c7c384424498ecd0a567d31ec380727fb605af76643d0d5513dd0a8d018076dd087", + "0x80d0777fa9f79f4a0f0f937d6de277eec22b3507e2e398f44b16e11e40edf5feff55b3b07a69e95e7e3a1621add5ed58", + "0xb2430a460783f44feb6e4e342106571ef81ad36e3ddd908ec719febeb7acaf4b833de34998f83a1dab8f0137a3744c11", + "0xb8f38ccfc7279e1e30ad7cefc3ea146b0e2dff62430c50a5c72649a4f38f2bac2996124b03af2079d942b47b078cc4f8", + "0xa178a450a62f30ec2832ac13bbc48789549c64fc9d607b766f6d7998558a0e2fad007ae0148fc5747189b713f654e6ba", + "0x98c5ede296f3016f6597f7ccc5f82c88fd38ed6dc3d6da3e4a916bfd7c4c95928722a1d02534fe89387c201d70aa6fd2", + "0xa8cc5e98573705d396576e022b2ba2c3e7c7ece45cd8605cb534b511763682582299e91b4bb4100c967019d9f15bbfaf", + "0x848480ea7b7d9536e469da721236d932870b7bbee31ccf7ae31b4d98d91413f59b94a1e0d1786ee7342295aa3734969c", + "0xb88ea38f9ee432f49e09e4e013b19dff5a50b65453e17caf612155fff6622198f3cba43b2ea493a87e160935aaaf20a9", + "0x949376934a61e0ef8894339c8913b5f3b228fa0ae5c532ad99b8d783b9e4451e4588541f223d87273c0e96c0020d5372", + "0x96f90bb65ca6b476527d32c415814b9e09061648d34993f72f28fae7dc9c197e04ef979f804076d107bb218dfd9cb299", + "0xa4402da95d9942c8f26617e02a7cef0ebc4b757fac72f222a7958e554c82cc216444de93f659e4a1d643b3e55a95d526", + "0x81179cbc26a33f6d339b05ea3e1d6b9e1190bd44e94161ae36357b9cdf1e37d745d45c61735feed64371fe5384102366", + "0xad4dc22bdbd60e147fdac57d98166de37c727f090059cfc33e5ee6cf85e23c2643996b75cf1b37c63f3dc9d3c57ffa18", + "0x8a9b1b93dc56e078ce3bb61c2b0088fd6c3e303ba6b943231cc79d4a8e8572f4109bbde5f5aa7333aae3287909cb0fe2", + "0x8876ef583bc1513322457a4807d03381ba1f4d13e179260eaa3bddfede8df677b02b176c6c9f74c8e6eab0e5edee6de6", + "0xb6c67e228bf190fbaeb2b7ec34d4717ce710829c3e4964f56ebb7e64dc85058c30be08030fa87cc94f1734c5206aef5f", + "0xa00cb53b804ee9e85ce12c0103f12450d977bc54a41195819973c8a06dcb3f46f2bf83c3102db62c92c57ab4dd1e9218", + "0xa7675a64772eefddf8e94636fb7d1d28f277074327c02eea8fae88989de0c5f2dc1efed010f4992d57b5f59a0ab40d69", + "0x8d42bb915e0bf6a62bcdf2d9330eca9b64f9ec36c21ae14bf1d9b0805e5e0228b8a5872be61be8133ad06f11cb77c363", + "0xa5b134de0d76df71af3001f70e65c6d78bed571bc06bfddf40d0baad7ea2767608b1777b7ef4c836a8445949877eeb34", + "0xaeadbc771eaa5de3a353229d33ed8c66e85efbd498e5be467709cb7ff70d3f1a7640002568b0940e3abd7b2da81d2821", + "0x8c28da8e57a388007bd2620106f6226b011ee716a795c5d9f041c810edf9cf7345b2e2e7d06d8a6b6afa1ee01a5badc1", + "0x8ed070626a4d39ffd952ddb177bc68fd35b325312e7c11694c99b691f92a8ea7734aeb96cf9cc73e05b3c1b1dcad6978", + "0xada83e18e4842f3d8871881d5dbc81aed88a1328298bfdc9e28275094bd88d71b02e7b8501c380fa8d93096cbc62f4fb", + "0x8befc3bec82dcf000a94603b4a35c1950ba5d00d4bed12661e4237afa75062aa5dcef8eac0b9803136c76d2dd424a689", + "0x97c6f36c91ca5ca9230bfcbf109d813728b965a29b62e5f54c8e602d14a52ac38fa1270de8bfe1ab365426f3fc3654c7", + "0xb01d192af3d8dbce2fe2fece231449e70eb9ac194ec98e758da11ca53294a0fa8c29b1d23a5d9064b938b259ea3b4fb5", + "0x819a2c20646178f2f02865340db1c3c6ebc18f4e6559dd93aa604388796a34bd9fed28ad3ccc8afc57a5b60bb5c4e4ec", + "0xa9ffc877470afc169fecf9ec2dc33253b677371938b0c4ffa10f77bb80089afa2b4488437be90bb1bcf7586a6f4286e3", + "0xb533051c7ce7107176bcb34ad49fdb41fac32d145854d2fe0a561c200dcf242da484156177e2c8f411c3fdf1559ecf83", + "0x8fe2caff2e4241d353110a3618832f1443f7afe171fd14607009a4a0aa18509a4f1367b67913e1235ac19de15e732eb1", + "0x84705c6370619403b9f498059f9869fdf5f188d9d9231a0cb67b1da2e8c906ead51b934286497293698bba269c48aa59", + "0x899dddf312a37e3b10bdaaacc1789d71d710994b6ee2928ac982ad3fd8a4f6167672bc8bf3419412711c591afe801c28", + "0xb2f7916d946b903ded57b9d57025386143410a41a139b183b70aeca09cf43f5089ead1450fce4e6eb4fba2c8f5c5bbe5", + "0x8d5f742fe27a41623b5820914c5ca59f82246010fa974304204839880e5d0db8bc45ebab2ad19287f0de4ac6af25c09e", + "0xb93d4a1f6f73ac34da5ffbd2a4199cf1d51888bc930dc3e481b78806f454fcb700b4021af7525b108d49ebbbaa936309", + "0x8606f8d9121512e0217a70249937e5c7f35fbfe019f02248b035fa3a87d607bc23ae66d0443e26a4324f1f8e57fd6a25", + "0xb21312cdec9c2c30dd7e06e9d3151f3c1aceeb0c2f47cf9800cce41521b9d835cb501f98b410dc1d49a310fdda9bc250", + "0xa56420b64286bdddda1e212bba268e9d1ba6bdb7132484bf7f0b9e38099b94a540884079b07c501c519b0813c184f6b4", + "0x80b2cf0e010118cb2260f9c793cef136f8fa7b5e2711703735524e71d43bce2d296c093be41f2f59118cac71f1c5a2ff", + "0xadcb12d65163804d2f66b53f313f97152841c3625dbbda765e889b9937195c6fcd55d45cc48ebffabb56a5e5fe041611", + "0x8b8a42e50dc6b08ab2f69fc0f6d45e1ea3f11ba0c1008ee48448d79d1897356599e84f7f9d8a100329ed384d6787cfc4", + "0xaaa9c74afa2dec7eccfbd8bb0fc6f24ed04e74c9e2566c0755a00afdfdf3c4c7c59e2a037ec89c2f20af3fae1dd83b46", + "0xaa9f6e8fd59187171c6083ae433627d702eb78084f59010ff07aff8f821f7022ef5fbbe23d76814d811b720a8bfa6cc3", + "0xa56a3ded501659ad006d679af3287080b7ee8449e579406c2cae9706ef8bf19c1fc2eb2a6f9eaf2d3c7582cded73e477", + "0x81971e077c1da25845840222b4191e65f6d242b264af4e86800f80072d97d2a27a6adc87c3a1cb1b0dd63d233fbafa81", + "0xa6fa5453c4aaad2947969ee856616bf6448224f7c5bf578f440bcfc85a55beb40bef79df8096c4db59d1bd8ef33293ea", + "0x87c545adbfaaf71e0ab4bac9ae4e1419718f52b0060e8bb16b33db6d71b7248ae259d8dd4795b36a4bbb17f8fae9fd86", + "0xb4c7a9bc0910e905713291d549cec5309e2d6c9b5ea96954489b1dff2e490a6c8b1fa1e392232575f0a424ba94202f61", + "0x802350b761bcaba21b7afe82c8c6d36ee892b4524ab67e2161a91bbfa1d8e92e7e771efb1f22c14126218dd2cb583957", + "0xb4e7ddb9143d4d78ea8ea54f1c908879877d3c96ee8b5e1cb738949dcfceb3012a464506d8ae97aa99ea1de2abf34e3d", + "0xa49a214065c512ad5b7cc45154657a206ef3979aa753b352f8b334411f096d28fd42bca17e57d4baaafb014ac798fc10", + "0x8a80c70a06792678a97fe307520c0bf8ed3669f2617308752a2ab3c76fdf3726b014335a9b4c9cbcfc1df3b9e983c56f", + "0xa34721d9e2a0e4d08995a9d986dc9c266c766296d8d85e7b954651ad2ca07e55abb1b215898ee300da9b67114b036e0d", + "0x8cfce4564a526d7dca31e013e0531a9510b63845bbbd868d5783875ed45f92c1c369ce4a01d9d541f55f83c2c0a94f03", + "0xab3f5f03a5afc727778eb3edf70e4249061810eba06dc3b96b718e194c89429c5bfbec4b06f8bce8a2118a2fdce67b59", + "0xaa80c2529fc19d428342c894d4a30cb876169b1a2df81a723ab313a071cba28321de3511a4de7846207e916b395abcc9", + "0x82b7828249bf535ef24547d6618164b3f72691c17ca1268a5ee9052dba0db2fdd9987c8e083307a54399eab11b0f76b1", + "0x8fbcb56b687adad8655a6cf43364a18a434bf635e60512fad2c435cf046f914228fb314f7d8d24d7e5e774fb5ffb1735", + "0xa3010a61a2642f5ebbce7b4bc5d6ecb3df98722a49eb1655fe43c1d4b08f11dfad4bcec3e3f162d4cc7af6a504f4d47c", + "0xb3dcc0fdf531478e7c9ef53190aa5607fd053a7d2af6c24a15d74c279dbb47e3c803a1c6517d7e45d6534bb59e3527f5", + "0x8648f6316c898baaca534dff577c38e046b8dfa8f5a14ee7c7bc95d93ae42aa7794ba0f95688a13b554eeb58aeedf9ba", + "0x89fca6fc50407695e9315483b24f8b4e75936edf1475bcf609eed1c4370819abac0e6a7c3c44f669560367d805d9ba63", + "0xa367a17db374f34cd50f66fb31ba5b7de9dbe040f23db2dcc1d6811c0e863606f6c51850af203956f3399000f284d05f", + "0x91030f9ca0fff3e2dbd5947dcf2eba95eb3dbca92ee2df0ed83a1f73dbf274611af7daf1bb0c5c2ee46893ab87013771", + "0x84d56181f304ce94015ea575afeef1f84ea0c5dbb5d29fb41f25c7f26077b1a495aff74bd713b83bce48c62d7c36e42d", + "0x8fe2f84f178739c3e2a2f7dcac5351c52cbed5fa30255c29b9ae603ffd0c1a181da7fb5da40a4a39eec6ce971c328fcf", + "0xa6f9b77b2fdf0b9ee98cb6ff61073260b134eb7a428e14154b3aa34f57628e8980c03664c20f65becfe50d2bdd2751d4", + "0x8c6760865445b9327c34d2a1247583694fbeb876055a6a0a9e5cb460e35d0b2c419e7b14768f1cc388a6468c94fd0a0f", + "0xaf0350672488a96fe0089d633311ac308978a2b891b6dbb40a73882f1bda7381a1a24a03e115ead2937bf9dcd80572ad", + "0xa8e528ec2ee78389dd31d8280e07c3fdd84d49556a0969d9d5c134d9a55cd79e1d65463367b9512389f125ed956bc36a", + "0x942c66589b24f93e81fe3a3be3db0cd4d15a93fb75260b1f7419f58d66afaa57c8d2d8e6571536790e2b415eec348fd9", + "0x83fe4184b4b277d8bf65fb747b3c944170824b5832751057e43465526560f60da6e5bbee2f183cb20b896a20197168c7", + "0x88a71aada494e22c48db673d9e203eef7a4e551d25063b126017066c7c241ee82bedaa35741de4bd78a3dd8e21a8af44", + "0x8c642a3186ca264aac16ee5e27bd8da7e40e9c67ae159b5d32daa87b7de394bf2d7e80e7efb1a5506c53bfd6edd8c2c3", + "0x81855d6de9a59cef51bef12c72f07f1e0e8fe324fcc7ec3f850a532e96dcd434c247130610aaee413956f56b31cbb0dc", + "0xa01e61390dcd56a58ad2fcdb3275704ddfbedef3ba8b7c5fce4814a6cdd03d19d985dba6fd3383d4db089444ea9b9b4d", + "0x96494e89cbf3f9b69488a875434302000c2c49b5d07e5ff048a5b4a8147c98291ae222529b61bb66f1903b2e988e5425", + "0xb9689b3e8dddc6ec9d5c42ba9877f02c1779b2c912bba5183778dc2f022b49aed21c61c8ec7e3c02d74fe3f020a15986", + "0xa2a85e213b80b0511395da318cbb9935c87b82c305f717a264155a28a2ea204e9e726bae04ce6f012e331bd6730cbb9d", + "0x91b70f44c7d8c5980ce77e9033a34b05781cbe773854d3f49d2905cc711a3d87c20d5d496801ad6fd82438874ce732b8", + "0x884596417ff741bb4d11925d73852ffeea7161c7f232be3bdce9e6bbe7884c3a784f8f1807356ae49d336b7b53a2b495", + "0xae2aed8ab6951d8d768789f5bc5d638838d290d33ccc152edfb123e88ba04c6272b44294b0c460880451ad7b3868cc6a", + "0x89d8ebfb9beebc77189d27de31c55f823da87798a50bca21622cbf871e5d9f1d3182cf32ee9b90f157e6ce298e9efccf", + "0xafd00a4db4c2ed93cf047378c9402914b6b3255779f3bb47ded4ab206acb7eaebba0fd7762928e681b1aebcfee994adc", + "0xa2e49b6cd32e95d141ebc29f8c0b398bb5e1a04945f09e7e30a4062142111cd7aa712ac0e3e6394cfb73dd854f41ad77", + "0xae8e714ab6e01812a4de5828d84060f626358bb2b955f6fb99ae887b0d5ce4f67ebc079ab9e27d189bf1d3f24f7c2014", + "0xa3100c1eebf46d604e75ebf78569c25acf938d112b29ccbe1a91582f6bd8ef5548ae3961c808d3fb73936ac244e28dbc", + "0xa9a02dcff0e93d47ead9cdddc4759971c2d848580bf50e117eb100cafca6afeaa7b87208513d5f96b1e1440ffc1b0212", + "0x894ab01462137e1b0db7b84920a3b677fbb46c52b6f4c15320ef64f985e0fc05cec84cd48f389ce039779d5376966ea3", + "0xb1e40e8399ee793e5f501c9c43bde23538e3ce473c20a9f914f4a64f5b565748d13ab2406efe40a048965ee4476113e4", + "0xa5a7d97a19e636238968670a916d007bf2ce6ae8e352345d274101d0bbe3ac9b898f5b85814a7e4c433dd22ac2e000ff", + "0xb6394c43b82923231d93fd0aa8124b757163ba62df369898b9481f0118cb85375d0caac979a198ece432dbb4eb7cc357", + "0x82d522ae3ff4fe2c607b34b42af6f39c0cf96fcfe1f5b1812fca21c8d20cece78376da86dcbd6cdb140e23c93ae0bcb2", + "0xb6e0d986383bc4955508d35af92f2993e7e89db745f4525948c5274cfd500880cb5a9d58a5b13d96f6368bb266a4433e", + "0xb0b4325772ec156571d740c404e1add233fb693579f653b0fae0042b03157d3b904838f05c321d2d30f2dbd27c4d08ad", + "0xac41367250263a2099006ef80c30bac1d2f25731d4874be623b6e315c45b0dc9a65f530fce82fb3dc25bd0610008c760", + "0xb6c0b1ed7df53da04a6f3e796d3bfa186f9551c523bc67898bc0ecfc6b4a4a22f8c4d3bfc740ebf7b9fa5b0ea9431808", + "0x8e78fca17346601219d01e5cd6a4837161a7c8f86fe2a8d93574d8006da5f06ae7c48eea7d2b70992c2a69184619663c", + "0xa21f91f47e04fafbfafacf3185b6863766a2d0c324ccac2c3853a4748af5897dbbe31d91473b480f646121339c9bae2d", + "0xa464d68786ab1fc64bd8734fce0be6fbe8dc021d3e771ff492ada76eedff466577c25e282b7c8ab4c1fd95ef5ff3631e", + "0x829a24badc7714081e03509ccfb00818ce40430682c1c0e4a399cd10b690bda1f921aabcbf1edfb1d8a2e98e6c0cedd6", + "0x87ccf7e4bbcb818ef525435e7a7f039ecbb9c6670b0af163173da38cbdb07f18bc0b40b7e0c771a74e5a4bc8f12dfe2c", + "0x94087bd2af9dbeb449eb7f014cfbf3ee4348c0f47cde7dc0ad401a3c18481a8a33b89322227dee0822244965ae5a2abb", + "0x896b83ed78724dac8a3d5a75a99de8e056a083690152c303326aa833618b93ef9ec19ab8c6ef0efe9da2dbcccac54431", + "0x821e6a0d7ccf3c7bd6a6cc67cde6c5b92fb96542cb6b4e65a44bbc90bbc40c51ff9e04702cb69dd2452f39a2ff562898", + "0xb35b2096cda729090663a49cb09656c019fef1fc69a88496028d3a258ad2b3fd6d91ab832163eaa0077989f647e85e7e", + "0xb7857ef62c56d8bce62476cdb2ab965eddff24d932e20fc992bd820598686defe6cc0a7232d2be342696c2990d80721a", + "0xb343d974dfda3f6589043acd25d53aecf7c34b1e980ae135a55cda554ff55e531bc7c2dfe89b0d2c30e523c7b065dad1", + "0x8d139e16a73cd892b75f3f4e445a10d55d1118f8eeafc75b259d098338419e72e950df6ca49cb45677a3c4e16fb19cdc", + "0x817b8535bd759da392b2c5760c51b3952ecf663662a137c997f595c533cd561ed7e655673c11144242160e41d1f2dd71", + "0x817ee0f0819b0ccb794df17982d5b4332abff5fec5e23b69579db2767855642156d9b9acccf6ceab43332ccc8d2744dc", + "0x9835d2b652aec9b0eba0c8e3b6169567e257a6a3f274ec705dbc250ee63f0f8e4b342e47b9e0c280c778208483d47af8", + "0xb78c40177f54f0e6d03083a4f50d8e56b5aafdb90f1b047bb504777d6e27be5a58170330aee12fbaa5f1e9d4f944acfc", + "0xab8eebacf3806fac7ab951f6a9f3695545e2e3b839ca399a4ef360a73e77f089bb53d3d31dbd84ddfde55e5f013626e0", + "0x96c411fc6aecca39d07d2aff44d94b40814d8cfc4ee5a192fd23b54589b2801694d820a0dd217e44863ccff31dda891b", + "0x8249c424a0caf87d4f7ff255950bbc64064d4d1b093324bfe99583e8457c1f50e6996e3517bf281aa9b252c2a7c5a83a", + "0xacf6ed86121821a3dd63f3875b185c5ebe024bdb37878c8a8d558943d36db0616545a60db90789c0925295f45d021225", + "0xa37f155621a789f774dd13e57016b8e91b3a2512b5c75377ec8871b22a66db99655d101f57acaecd93115297caabfc21", + "0x92e60ee245bd4d349f1c656e034b1a7f0c6415a39ac4c54d383112734305488b3b90b0145024255735e0a32f38dba656", + "0xacec614e562ccfc93366309cfdc78c7d7ee0a23e3a7782a4fc4807b8803e6ebfb894a489d03e9a3c817ff2ec14813eba", + "0xb912f9dd26ed552cb14b007b893e6ed2494d12517e5761dbeb88521270144f8c3eb9571a0ad444b30a8a65e80bd95996", + "0x8375408dae79c547a29e9a9e5d4ec8241b36b82e45e4ca3b0c36d2227c02d17bb171528d3778eac3bbdc75d6c4e8a367", + "0x8c2d0e6e4406836da112edbbb63996408bb3cda4a2712fd245e4bb29a0100fdc89a2746d859b84a94565bc1cfa681813", + "0xa7431bf59e111c072d28c97626cd54fcdf018421d053a787d2aef454b91251ee8ff9d3702d06b088f92b9ad2bbebff15", + "0x8f3659b0fbeb90b7f30b7a49233325e806551a32911a654dca86e290b314483bbb33fe6482387bc48c35d85c1dd0441c", + "0x8dca5ba23f0bb76f7dacabf12886053552ba829a72827b472a2f01e19a893155cdce65f1fb670000f43e8c75ba015a31", + "0x8c1514c083c77624eeb5d995d60994a2866192e15c4474d0be4189fae0e9dbd62494ebb4c02fbc176b53be548abbc5a1", + "0x80498d2ed153381baf3b0f81da839ed0eea6af5796c422b8e59be805dba48c4395bb97824ac308170bb4f14f319c5ddf", + "0x84f5ebc3bf96362457993e9fa31493c31c4283075e2403f63d581b6b0db8a3df294b2085643f2007f4de38cb5d627776", + "0x958e6e38774da518193a98397978dbc73d1c3827b4996ec00b4183da2c305a187a0ada9aa306242814b229a395be83c9", + "0xab8b8fbf73845615e7fab3e09e96cc181159eab09f36b4c1239b3c03313c9aeb4bbb51e16316fe338b2319ed2571b810", + "0x977e4e33b33bd53394e591eba4f9a183e13704c61e467d74b28f4ad0b69aa51501a5221cb1e0e42bcb548ca518caa619", + "0xa9bb7ecb9846cc30d04aad56d253c3df7004cebb272f6adf7b40a84adef9f57291e0d08d64c961b9fc406cdb198aab9b", + "0x8d2b72dc36406a545a9da44e1fddfb953d4894710ca026d6421a4ac91e02d0373a599f2acfe41d8258bc9679cf6f43d3", + "0x904192fc8fe250f61ecb8a36abbbccae85f592bbf00c10039c30b5a1c733d752a04e4fd8a1000c6578616f8a16aa83a3", + "0x87f5fdfe20bbbf931b529ec9be77bbfcc398cad9d932d29f62c846e08a91d2f47ae56ad5345122d62a56f629f9a76c4d", + "0x84cc3a53b2e7b7e03015f796b6cb7c32d6ded95c5b49c233ac27fafa792994b43c93cda6e618b66fce381f3db69838ba", + "0xaab58da10d7bbe091788988d43d66a335644f3d0897bbc98df27dcc0c0fcee0ac72e24f1abdd77e25196a1d0d0728e98", + "0xa10ea8677c2b7da563d84aa91a314a54cab27bb417c257826ebdd3b045d2a0f12729fe630bbbf785d04874f99f26bee8", + "0xacc4970ef2a4435937a9b8a5a5a311226ca188d8f26af1adfcd6efb2376a59155b9a9ff1cff591bde4b684887d5da6e5", + "0x8dc7cf6fcca483c44eb55e7fb924bf3f76cf79b411ae4b01c6c968910877ac9c166b71350f4d935f19bdffb056477961", + "0xac2dd1182ded2054c2f4dbf27b71a0b517fb57193733a4e4e56aca8a069cff5078ffd3fd033683d076c1c639a4de63c7", + "0x932ec87c450cd0dc678daf8c63cd1bf46124fa472934e517fbbfb78199f288ff7f354b36e0cc6c8739d3f496cfe0913b", + "0xb0d631ced213e8492be60ea334dbe3b7799b86d85d5e8e70d02beef3ae87b1d76e1df3bdb5f7ba8a41904c96f6a64455", + "0x929d7239ead7575867e26b536b8badf2e11ca37840034d0e5c77039f8cce122eff5a1bf6e0bcadde6b3858e9f483d475", + "0xaaae5d372d02ee25b14de585af6fbc48f2c7cd2a6af4f08352951b45aa469599eff41e820df642ca1a0f881120e89dbe", + "0xb23c411741a6b059f04fa4f5fd9dd10e2a64915f2de6ea31e39c32f2f347a776a953320e5f7613fcb1167efe502f5c5c", + "0xa4581b0ae633fe29c6f09928e5efb16db019eeac57f79fef2fa1d3c9bee42ce0e852bc60b9d0133265373747e52a67a4", + "0x81b33afffd7b2575d4a9a1c5dd6eee675c084f82e06b9b3a52a3c9f76e087f12dca6e0ffddc42fb81ce1adb559d47a38", + "0x89cc890f06b424591556aabdfdbb36d7a23700425e90c9cfed7d3da226b4debe414ac5bdf175273828ce6c5355712514", + "0xa4399438be75cfae2bf825496704da5ed9001bed8538d8ac346c8cf0d4407808e9ee67573eb95fe1c6872ac21f639aaa", + "0xad537f7ce74a1ca9a46fc06f15c1c8a6c32363bd6ac78a3c579ed8f84252e38a914cac16709fe65360e822ef47896de4", + "0x8e53b69f5e3e86b86299452e20ea8068b49565d0d0ab5d50ce00158a18403ae44e1b078a3cfd3f919aa81eb049a30c6e", + "0xa59f2542c67a430fd3526215c60c02353ee18af2ff87cb6231a2564fe59b8efec421f18d8b8cc7f084675ecf57b3fd05", + "0xb8d9bac93ef56cb4026dd1c731d92260a608fd55b8321e39166678e1dab834d0efddb717685da87786caeb1aaf258089", + "0xaa2df56f4c6fe9e0f899116c37302675f796a1608338700f05a13e779eb7cf278e01947864a8c2c74cc9d9a763804446", + "0xb0108ff2e327dcb6982961232bf7a9a0356d4297902f4b38d380ff1b954bfbcae0093df0f133dd9e84d5966c7b1aada7", + "0xb06b813b01fe7f8cf05b79dc95006f0c01d73101583d456278d71cd78638df2b1115897072b20947943fa263ddab0cd6", + "0xaa41e6c4d50da8abf0ea3c3901412fe9c9dff885383e2c0c0c50ed2f770ada888a27ea08bbb5342b5ff402e7b1230f12", + "0xa48635dbb7debac10cb93d422c2910e5358ba0c584b73f9845028af4a763fd20da8f928b54b27782b27ca47e631ebf38", + "0x80a574c208e994799e4fa9ef895163f33153bc6487491d817c4049e376054c641c4717bda8efbeb09152fa421a7268a7", + "0xb592bfd78ae228afc219c186589b9b0b5c571e314976d1ed5c1642db9159d577679a73c049cfc3dcfefcd5a4f174eeea", + "0xaa1f08af3918c61eadf567a5b1a3cdcdfb1b925f23f1f9e3c47889762f4d979d64686ce1ce990055ef8c1030d98daa3b", + "0x857df4cfd56d41c6d0c7fcc1c657e83c888253bae58d33b86e0803a37461be5a57140a77fb4b61108d1d8565091ada1c", + "0x8fae66a72361df509d253012a94160d84d0b2260822c788927d32fd3c89500500908c8f850ef70df68ddaeb077fd0820", + "0xaa1dbefc9aef1e7b896ff7303837053c63cfb5c8a3d8204680d3228ac16c23636748fe59286468c99699ae668e769a0c", + "0xb64b1cb2ba28665ed10bad1dddc42f3f97383c39bad463c6615b527302e2aaf93eb6062946d2150bd41c329697d101be", + "0xb6d35e3b524186e9065cee73ea17c082feff1811b5ab5519dd7991cdff2f397e3a79655969755309bd08c7d5a66f5d78", + "0xa4dae7f584270743bbba8bb633bdb8bc4dcc43580e53d3e9e509ff6c327e384f14104b5bdfe5c662dc6568806950da37", + "0xaae84d3d9ad4e237b07c199813a42ed2af3bf641339c342d9abf7ebec29b5bd06249c4488ce5c9277d87f7b71b3ddd37", + "0xb82a463cf643821618a058bddf9f2acb34ac86a8de42a0fa18c9626e51c20351d27a9575398a31227e21e291b0da183e", + "0x8b6c921e8707aded3ea693f490322971b1a7f64786ef071bc9826c73a06bd8ae6bf21bc980425769627b529d30b253ce", + "0x80724937b27fc50f033c11c50835c632369f0905f413b1713a2b0a2274bec5d7a30438e94193d479ba6679dbe09a65ef", + "0xa1d9b259a2ca9cff8af6678b3af0a290c2f51e9cf26d5fe3c6a4fa3d28cbf33cb709b7f78b4f61cb9419427983c61925", + "0x96a3e69a5ed7a98ce59c4481f2ffb75be9542122ad0eb4952c84d4536760df217854d4ec561ce2f4a79d3793c22fa4f4", + "0x990c4d9a4a22d63a8976d34833cafc35936b165f04aed3504e9b435f0de1be4c83b097bbaa062483cf3dee3833b4f5b6", + "0xb9bf5e4b270aec4a0dc219457b5fed984b548892c4b700482525ba1a7df19284464f841dab94abfabcaa9a7b7a757484", + "0xacaecf49cb4786d17cf867d7a93bd4ffee0781766e11b5c1b29089ae0024c859d11b45828fbff5330b888543264d74a9", + "0xb0e1a0865b1e6f9e4a0e31d0c885526ac06678acc526fda5124742a2c303bd0e8871a0cb7951ec8ed9540fc247c8d844", + "0x82b3d327b3d1a631758451e12870816956cd0cef91fcf313a90dd533d5291193a0ff3cc447054564ce68c9b027a7ffd7", + "0xa2843602abb98f0f83e000f3415039788da1e9a096bfe8fed6b99bab96df948c814560424ffebe755cb72f40436fb590", + "0xab1c7b43cf838798d1e314bc26e04fc021e99a7bfbfc8ffde62fa8d7f92139de86a377289d5177021154229de01ede15", + "0x95e5cf5dd87ae3aed41b03c6c55f9dfad38dc126b17e7e587c156f7745c8da0bd1d60acb718fc1a03b61344f01e3de4d", + "0x86f021a3762bb47167f80d4ef1b1c873a91fe83409f9704f192efeebbc3ece0729cd2f92f63419907ea38ae47bc907d2", + "0xaaa1445dafbbcd645d4332d9806225e9346ee5ac6b22ad45e8922134fe12f3d433f567a6a4c19efdd9d5775a7de1e92f", + "0x8fd7e15688eef75df7b8bca3d61bc9fca4f56e047cdb6d0b864e7d1c4966eac27d6094b0c8482b49739f83ec51050198", + "0x80aab8b4d394eb011d4ec6a4c2815617308c9b847c6fa6a3d7e6af1c79420ef6ff2a13934a398581c40ee4cf1cac02ac", + "0x8970b97ac076a1d8a321ce00eada0edf974a46bf3cc26f6854e4218cdfc8d2b0c32199d9658f254b4fbae5a2c5535f41", + "0xa1aa2ec5b03df0a630e73dd048680ed6d3032c324941423f45cd1f16038789e5e75b876a13948732e9079a422f66a9fc", + "0xb5fe5f5e2f2ae2beeb8e95859a02fc45f01f9fb0ebb2bd8ec9ec976b3e806228821a9775096d341d662bc536c4d89452", + "0xa2bc1f170b62d0d5788b02391337b2ab157c38e725694e80aeead7383e05599be0e2f0fa27ef05db007061809356e147", + "0xa8a69701d4a8d0d972390e9f831fd8e9f424b2c2ef069e56bd763e9e835b3ce5f7cf5de5e5c297c06ace4aa74df1067c", + "0xb43d551af4ff3873557efe3f3fb98e5ede9008492f181f4796dd1a6bcda8b9445c155e8146966baa812afae1abe06b48", + "0xb4b1dae44fd596813f30602ab20e9b1fb20cb1bd650daacc97b7e054e5c0178b8131d439a9e5b142ca483cc012a362b3", + "0xb95b8a94c30a831eaaebea98c65cc5d0228c78afd6603d4aa426d8186aecc951f1a11c33951f51df04c7e6fa43ffb5ae", + "0xb100059624cf9db371bec80013a57a8f296d006c139a8766308f1ea821c7eccc26cad65bc640ab3f6cef9062653bf17d", + "0x8e5a2cb76716e0000d13bce5ef87acac307362a6096f090f5f64e5c5c71a10fddfdee8435e7166ba8c3ad8c3f540f3e4", + "0x93d2c43e21588c1e83c4255c52604b4ac3f40e656352d1827e95dd5222a45aebff9674e34fbbe7ed21eca77bd9b8dcbc", + "0x8aeaed611546bb9073b07512a9a1f38a7f436ab45e11775a0f9754baaf63e9bcc7bb59b47546a5ded5e4ba2f698e3b5f", + "0xaf9e6792e74a1163fe27612f999a2f3cfa9048914c5bef69e3b2a75162bb0ce6ece81af699ad7f0c5278a8df0ba000d2", + "0x850bf2d5d34791c371a36404036ad6fdcd8fb62d1bb17a57e88bda7a78ea322397ce24d1abf4d0c89b9cf0b4cc42feb3", + "0x87f7e2a1625e2b7861b11d593aaac933ed08a7c768aebd00a45d893ed295bbb6ed865037b152bb574d70be006ddc1791", + "0x8dcce8f4ad163b29a2348ea15431c2c6ea1189ece88d2790e9f46b9125bd790b22503ec391bc2dee8f35419863b2c50c", + "0xb4bf5266c37f12421dd684b29517982d5e4b65dfdfba5fc7bd7479fd854aabf250627498f1e1188a51c0a88d848ec951", + "0x8651623c690247f747af8fdffdc3e5f73d0662bc3279fa2423a3c654af9b6433b9e5e0155f1ce53857e67388e7e3401d", + "0xb155120f196d52760129dde2e2b1990039b99484cdc948fa98095cd23da87679850f522e5955eae34ac267d2144160d3", + "0xaec8115e8d7b6601fbceeccf92e35845a06706d46acd188452c9f7d49abef14c6b3a9a9369a8bab2fd4eb9288e2aaca5", + "0x998a8ca4dc0f145f67a8c456f1d6a7323c4836fe036dcbb0f27eb1c596d121eb97369638a9908cfaf218c7706f266245", + "0xb235fbafac62802742ee3d26b1f4e887f7d2da4d711ba7f9bb6ca024de7beec1de66bb830ce96d69538f7dcb93c51b26", + "0x9258d2ddc21ab4e3edcde7eb7f6a382a29f1b626003cc6fdd8858be90f4ad13240072d8a8d44ef8de51ca4f477fa6c45", + "0x99d038487821c948142c678acd8c792960993dd8cb5e02cb229153a1ee9f88249f4ad9007f08e5d82e2a71fb96bb5f32", + "0xa88ee9dbc73d3d8e0f447b76fdb3a27936bde479a58d5799176885583dc93830ac58bca9087075950ea75100cf51af23", + "0x88b9b15816e5a0387153c1f4b90f613beb3ea4596037da01a81fdd2bcbd0baf5598db99f77e7694e5a0d35e822758108", + "0x907ae4b637d06b15846ee27d08c9c9af42df261c5bdd10cf5bc71f8e5ca34b33ac2405307023c50bdb8dc7b98a2cd5fe", + "0x9393d6900e1d2d1a1e42412fefd99578d9ac1d855c90a3e7930a739085496448609d674ca9b34016ad91f22d1cac538e", + "0xa28ac56b216730b7dcdb5ab3fc22d424c21a677db99a9897a89ed253ea83acfd9d83125133f5be6d9cd92298df110af8", + "0xb027590ee8766f1e352f831fda732adbaf77152485223ad5489ef3b0ce2d2e9f98d547c111fe133847ebb738987fb928", + "0xa9cc08fbd5c3fee8f77cf6eb996a5cafa195df5134dab000e4d0312f970a5577942ee89794e618074f49841f1f933a42", + "0xa8b3535c3df0b1a409d3fc740527ee7dd5ac21756115cde6f87f98cc7623f50cfcf16790689cab113ee7c35a5bd4879f", + "0xb61420227b97e5603ae8a716c6759b619f02b8fdc48acbf854352aa6519dad74b97bacc1723ca564cbf3ca48539ed773", + "0x853762498de80eebf955a6c8ddd259af463e4e25f0b6ba7b6a27b19bdbf4c585de55760a16e2d9345cdba6b2a02610f3", + "0xa711c1b13fc6c30745203c5d06390e6c82bd7c50f61734aa8d99c626faba30119bc910be63ec916c91ba53f8483c05a8", + "0xb488c0a793f4481f46b5875d96eecd73e46209a91677769f0890c5e002ecd7d4b1c9f4ba68c47fbed40e3857b1d8717a", + "0xa651c5e812ae65b1c66d92c607e80be330737ea49c1dcfe019c0ecea0f41a320406935bb09206a4abff0d1c24599b9ad", + "0x85e34e7d96e4b97db98a43247b6c244383b11ca10bf4777364acf509a6faa618bc973e2136a4693fbc8ab597e308fd5a", + "0x99837214102b394fffa7f3883759554c6bb7a070f5c809303595a44195e02b9a169460dc6bbffb62bdc0e7ced5f0a5c1", + "0xa952f89c0afb4bdae8c62b89cc3cfb60d0576ba4fe01a5d99534792f38d8848d919b3fc7577435d8443a044d2ee0bcfa", + "0xa1ac1f81acb29798acdfc493854663519e2d1b0e9d23d286ce33882c34b4c1c0bb43dd9638166d8026315a44d9ec92a8", + "0xac9c58aa38219ae659d23007cc7b97fd25b7b610b2d81a8f9f94ddb089efc49c049a8ea4c56e6eaf7b6498f422a97b3c", + "0x87e61d501c242b484fb9a937ef21d485f6678d75257fc8fe831b528979068cadbe7e12b49c34058ec96d70a9d179ab14", + "0xaa45f6852f35cc8b65a4a8b5380641d2602a4fa4e3a035db9664df3ac2e170b1280c4a8b7b55161430063e54de4158a6", + "0xa46975614ddde6d134753c8d82c381966f87203d6e5a5fb99a93b0d43aa461466b37f07b8d0973a1abd6ee2b40f24348", + "0x8d35f97297773422351f4d99564c1359ef1a10cfb60aa0e6c8985a78f39b4268486312c8ebf9dd2ef50a771aa03158eb", + "0x8497c6242102d21e8b3ade9a9896c96308ab39171ab74cbd94e304c47598e2c2a7b0a0822492ac5c076ba91d4176481d", + "0x973f8fcb5f26915b3a3ef6fe58cc44bc7f4e115cd0ad9727d8d1b8113e126ae2e253a19922c5433be4ab2311a839c214", + "0xae3ee9f1d765a9baf54b4617a289c3b24930aa8d57658a6b0b113bbf9b000c4a78499296c6f428bbb64755dfd4f795d2", + "0xa5be7a8e522ef3dcf9d2951220faf22bb865d050f4af2880b8483222ff7aad7c0866219fcc573df9d829c6efbb517f98", + "0xa5f3c7fabd7853a57695c5ad6d5b99167d08b5414e35ed1068ae386e0cb1ee2afbbe4d2b9024379b6fc3b10c39024d36", + "0x978d5592d4798c9e6baceff095413589461267d6a5b56cd558ec85011342da16f4365d879b905168256f61d36d891b1f", + "0xb7b6eaffa095ecbd76d6e1e88ceebabaf674d9ef7e331e875c6d9b9faa1762c800ff1ea597c214c28080f67a50a96c1e", + "0x8a1ab53ae5ceaa42e06e58dd8faf6c215fc09ba111ca9eeb800612334d30d5971448be90fec62ed194328aadd8c8eecc", + "0xa9ca532cac8ace9a9e845382f8a7840bf40cb426f2fcad8a2f40aadbb400b3a74021627cc9351b0966b841b30284962e", + "0x8dddeda8854c8e7ddc52676dd1d0fed1da610ed5415ddd7d25b835bd8420a6f83d7b67ec682270c9648b2e2186343591", + "0x888906aac64fd41d5c518a832d4e044fdc430cfe142fd431caf4676cafc58853ce576f098910d729011be0a9d50d67b5", + "0x96a3f886a2824e750b1e2ea5c587132f52a0c5e3ff192260d8783c666206bd8ebd539933816d7cdd97e4bc374e0b1edf", + "0xa150a29ffb2632cc7ec560983d9804cd6da3596c0c25956d27eb04776508eae809659fc883834269437871735de5f9ed", + "0x81f7ad4d2959d9d4009d1dfbc6fee38f930f163eb5eac11e98dc38bd2f7f224e3f5c767583f8e52d58d34f3417a6cf90", + "0x97ccac905ea7d9c6349132dd0397b6a2de9e57fd2d70f55e50860e019de15c20171a50b28a5c00ef90d43b838253b3d1", + "0x95694f00c21e8a205d6cbda09956b5b6ec9242ec8c799a91f515b07dcc7de3b6f573e2c0ba149f5a83700cda2d1df0f5", + "0x82bbc3c4a3b3997584903db30fffd182a266c7d1df3e913f908d5a53122fa12cf5acd11d915d85d5bd110fcc43cee736", + "0x8d3f24b4949aa1b4162c28dfbb9f813dd1d8b330f71325448dc45ea34d59b69ca95059402aae011e1b5aba6e536bc6ec", + "0x92c734c19752d24782331e74c9af97a8399ddfdd32954e91cda7363dba876aca4f730b451c50a8913950420682da8121", + "0x8653d2c79f77b8c7dcdf7e8dee42433998aeedf1b583abfca686d47a854de1b75e9a4351580c96d1a2a9532659203361", + "0x886f0e414cb558c1a534a1916d3531320a9b6024639712ffe18164ce6313993a553e2b9aafe9c0716318f81a5d0bb1da", + "0xb31b5efaba5a5020c3bcea0f54860e0688c2c3f27b9b0e44b45d745158f484e474d5d3b1a0044dd6753c7fb4bf8ace34", + "0xb2d615bbdfdc042d6f67a6170127392d99f0e77ae17b0e1be6786ff2f281795f1bf11f83f2e0f8723b5cdd1db1856e09", + "0xa6e014cca531e6ac2922239b5bee39d69d9ba6d0fa96a4b812217dd342657d35606f0b9c5a317efd423cdb1047815e3d", + "0xa8921736b69c9fbb29f443715174bac753e908251804620c542fad6cfbfda7bdfe287f2902f30b043a8a4b4818cfdeef", + "0x8d73a9949a042ec2dcefa476e454cd9877eee543b1a6b3b96a78ffcff87421e8b26dd54d5b3192ac32073cb36497acc3", + "0xb936a71ee8df0e48867f3790adf55dc8efc6585024128de2495f8873bd00fd9fa0984472125e801ed9c3cdce6698f160", + "0x82f69c06209c28f64874e850601dda56af44ffc864f42efa8f9c6a0758207bf0a00f583840982dec0a517ab899a98e5b", + "0xb7a0a14411101473406f30e82f14b13e6efc9699e7193c0be04bb43d1b49e8c54812ce0f9b39131a20379c4c39d3bbe3", + "0x81159c969f38107af3b858d7582b22925a7ccced02fae3698482d7e9cdc6c568e959651991c6cf16c53a997442054b61", + "0x8bf1116a206e0ce9199fcab6ed2b44a9e46e8143bff3ed3f1431f8d55508fe2728b8902670cfd8d9b316f575f288ed9d", + "0xa279b2149824b64144eb92f5a36b22036d34a52bd5a66e5da4b61fbc95af6eda8e485c7914f448abd8674fc14d268d9d", + "0x8b98279b5f3588d1a2f8589d2756458690a502728800f8d94b28e00df842a101c96ab9c5aee87c5bbe65552c0c383b80", + "0xb4a27a351ec54420f94e0a0a79d7c7a7337940399646631baca93eeab5fd429d7fb39428be77dcbce64a13eaa3c8ca1d", + "0x90c08baa29ec8338ffce381eae3d23ce3f6ba54e5242dec21dc3caaed69cac13f2ab5e8d9d719bc95720fa182eee399c", + "0x85156d65bb4fef69ffd539ab918b3286105ca6f1c36a74351ab3310b339727483433e8f8784791f47b4ba35ca933c379", + "0x923005013c27209d07c06a6b92b0cbb248a69c5e15c600bbcc643e8dcd2402adebd94dd4cafb44ec422a127e9780aaec", + "0x863b23eb5463a6ef5a12039edc2f8e18e3c97b244841bc50af02459b1bcc558367edf2f6e4fe69f45f37887469dd536d", + "0x87a4a7708a112724ff9b69ebb25d623b5cae362ae0946daed2ec80e917800dbfcd69f999c253542533242e7b9a5cc959", + "0x8bf4347ceea7f94b53564f26b1a4749a16f13bf71a9e03a546f906f7c423089820ff217066159b0637d9d6824e9c101c", + "0xab07eef925d264145971628a39e4dd93ff849767f68ed06065802cf22756fc6bf384cf6d9ab174bfc1a87bcc37b037aa", + "0x8e3f10a42fad43887d522dc76b1480063267991c2457c39f1e790e0c16c03e38a4c8e79a0b7622892464957bf517ebd8", + "0xa8722fc7b1acf0be18f6ddf3ee97a5a9b02a98da5bc1126a8b7bf10d18ee415be9a85668eb604ef5a1f48659bc447eb5", + "0x878d6b2a9c0aca8e2bc2a5eb7dd8d842aa839bbd7754860c396a641d5794eab88a55f8448de7dbddf9e201cbc54fe481", + "0xada881c167d39d368c1e9b283cf50491c6bfc66072815608ba23ab468cfbd31ca1bd7f140e158e0d9e4d7ebfa670bc2d", + "0xa2b48578fa899d77a7ee1b9cb1e228b40c20b303b3d403fd6612649c81e7db5a7313ba9702adc89627b5fd7439f8b754", + "0x8e051280e10551558dcb5522120ac9216281c29071c0371aaa9bde52961fe26b21d78de3f98cb8cd63e65cff86d1b25c", + "0xa7c5022047930c958e499e8051056c5244ae03beb60d4ba9fe666ab77a913a067324dfb6debcb4da4694645145716c9d", + "0x95cff6ec03e38c5ab0f6f8dccde252d91856093d8429b7494efc7772996e7985d2d6965307c7fdfa484559c129cca9f9", + "0x993eb550d5e8661791f63e2fa259ab1f78a0e3edad467eb419b076a70923fede2e00ddc48a961d20001aaae89fad11e8", + "0xabb2826e4d4b381d64787a09934b9c4fe1d5f5742f90858228e484f3c546e16ee8a2a0b0a952d834a93154a8b18f3d16", + "0xa922ca9f2061996e65ef38a7c5c7755e59d8d5ce27d577abcdd8165b23b4877398d735f9cb470a771335fc7d99ecb7fc", + "0x90f22862216f6bc1bbf5437740a47605d1ff5147b1f06f7b13fec446e4c5a4a4a84792cb244a1905f3478a36f8d7065b", + "0x87f3d9a86afef5b79ea1ca690ee1ee4bb9754b66f7c50a42ad6b99af7c222c853ca161f440a0a2a60b3b5a54e3493240", + "0x80a9ca9a2d33b9cf61976b3860d79f5d00de89a06ef043d2a52931809018aeb4ce70423cbef375b29c2c750c2c8704c2", + "0xb4e798ef1d615896108dae37ac50c1e859216ab6dbac11653e44d06ce5209057b4b0dd6d31dcfcda87664a23c8ef1cbd", + "0xaaed6d1e7c5b1db06f80dae6c24857daadfb0268f20e48a98fba4b76de1ebf65fb84c3be95fd6a418b498f8285ec63bd", + "0xaeceaa316c6369492c939f94809bc80e0857abac86c0d85be8066bbf61afbaaec67e28c572437a8d35c49dd596b3134f", + "0xb791c3d53ed34a7d1c8aa89b7953e3684c3cd529230824dc529739a5fbe74b58b87f01e56e7a169f61c508237ef67160", + "0x9351f8c80634386c45c0050d2f813193f9d839173be941e2092d729be5403632a2f18dffdc323d69eb0dc31fa31c5866", + "0x97693184d5c0056ae244dfb6709cafa23a795dc22d497a307a7f9cf442d7452024023c54a8d6bda5d90a355ba2c84f3a", + "0x85362daa003d23511ca174a8caafe83d52b6436dc4e43c4c049e5388d9211b5cbef3885896914d86d39be0dd1f910511", + "0xa2511b5fa34b24eeb0e1bcbcf872a569d1ff5570fe7b0fb48f5542f7fe57bad808d34b50afa87580866a6cb0eba02f27", + "0xb382e3327eb1401f2d378dbb56ac7250adde0961bd718575a64d264ffd44772c20752d4035c3ba60eb435e160b375e20", + "0xafad8a5d40b536c0720556845a6b257ed42165c14fb4b4a874717d107752f49ed9380c5b048df3aca67287bb8fc411a8", + "0x8fad0c98434ca5373c2d767868f679b76b4a8d04bca8240ea3f388558262c2d61b73b16fc1160932652b5688c25fffcf", + "0x83898008b5cbb6f08f8ef3ec179427869682bb4e8d38f6e6a687a214d4a307436afc64ee67d70a5a8ba9730bf839aecc", + "0xb85232e79913785fd82b06890706972b4ad7a309489930ae23390d51aa5189731f8a2df24800409a8c36b3dd6fc91275", + "0xa24ff26ec792f3701da4c5638c1fca4fa4dae95b01827d6200d583c4caf17ea3171393ba2a8c23d1ee8b88402916f176", + "0xadc5c7a7ff6b41d6cc386b7fc69d7bb04179bdf267864f9aa577f0f6a88438191fa81ebaf13055c2f2d7290be6421ace", + "0xa05e835abd502d31454d40a019010ff90b6b0b1f993075a35c9907aeab7a342ac0ba6144dc9379aada6119157970e9b2", + "0x85ff07ba58463e7f153fc83f11302e9061e648a5cbd272bb0545030b20e11facd8b3ff90c9ac8c280a704fbda5c9d1b0", + "0xa6c735ada8f4587da8cdad7ea3ada01650b5a3ecab8d81daa7a5f5de51ef4a6592b524692584306f06be3f6701f2870c", + "0xb138deee4e53ae8d677fae104f713ef1b8babfecec16b6a85785a66a72784eb09d44c3b63567222ade714e98f7d1604e", + "0xae79c1a49dafcdd972acd95d8ad0a35c02adc7fd736d4c44c3cd13df5789d339b5ea16bddbbd43e486a061ab31baa5c0", + "0xab3cf2371a1d7dcd0ffe3869a0178230964b06694bf258b2073ea66a2afccd845b38485da83d02e1d607d4c5c36b78a8", + "0xab9609f28a325fd01cb39540e3a714506c44e52ef28ee640f361deb5760aadbb23e804663b0fa20a66e239c33f8d8bb8", + "0x8ed95ea8e76e1b42823d7915a6aae77d93746f846bf602841dfce0e47543a36efb9ee7e5b42c73c3209d911225cc471b", + "0xa80b6162036d43811482323f0ce59eb18740e33a63d7c7bbbf3be206985919e5342d53a69df537d43e8b7d7f51e8892f", + "0x93c03d0a5083408ba00c125a8a9385213d4c860072f0297857b1235045819b904e07f2425c13a661d0a01d2e53347f4b", + "0xa6581200f00f96c461621e1d26b14a23687dd97eb9f7df4ba641a84340ee7306dc1796248fba4804f185947ad13b4385", + "0x8be174018fa40f7e0cedc5ae68f38969eb7695f2205e9c573641e533d56f68c20abf38a23d2f0dcac371e60b21b18615", + "0x857ad4ee3218c647c58f09b8ab22bcc8976f00a768ab1f708618e868e6143474be846422ce2710a0ed39b5155b6f13a1", + "0xa490bec40f322d599f26bcefcdddd8f2ef6576aa737d5ce7e8d5d422741abe749e3e6a48489aed8c560633f72857e3c2", + "0xa9c0ee339621f1c4a2410f9b4d2f03f1b558dae2973807b8bccd920e8feb7f65dfde3e79986b72ad21fcc4567240381d", + "0x8592251568e750a430f7d2c6ddbb3ec82a4dd9fd83efe389e69aa177fd97ac2c96c59a6e86db20d8e6f125d65b46c4d3", + "0xa4e2f4aa6a682913b423b097c4069c4e46a1f3af9556b1bfd0580d0fc01e3991488458049e0735b2a629684a79271c8f", + "0x8c4f6a3e738cf74112b08b1680be08158013ef8a515a81215d8a36c9b756786d1b4cb4563923463f3329292f4b48bf6d", + "0x8bace547353c02ea00dd547eeda7259aa354d4772dd5e0c486c723cf88627b7112e196b879c3c92a9561b674d9fc486d", + "0x8d372f4901e25e8db64fa098148d4a4e709b0e9dcb756d0f90dad99dea393054193ae1a33d292a3dd772ff7ba05e4b71", + "0xa8c7ea6a6a031ed23d65639f01f5423190775558f479700597df7ae7e338a6ae5e9b32f470aff20787ac8b7eec84df6c", + "0xb6e9dcba240fdbbf66033410a79a2dd3e9e1ffdf2eae949b3a9ed720e939d92339991dc3e70a5ac7d5253f317daf0b7d", + "0x974dec4cd61af75721071752c664d9c2a5121f06ff1515c56139a177a3ca825f763b69d431d4607e393fa74dcc91cc58", + "0x958863e6ad583a9d370a6db3639066982e44766904e7afa849b132f6666b7d08ab931131b3bec7a506d6583e93d56767", + "0x8b93a33b5da9b3300c20a96d80b894e3789c77041183c2cb21751579c8c96857f60cfc2f075201b64e95a78985c5b321", + "0xb726cb9f7ef34ddbc2fad82b3b0af0b30cc913e26c5a614ae5c19cc9c55c8e6dae069db5315a8dcb6d987415bb550ca8", + "0xa730f515398a71bddd66cab2ff996659d4e47dfbb08ce7958a41021f76d269b91c7498b708cd14b183a8ef469c772803", + "0xa4eb3b18132eb0f5337f14e01d63ca0bec0db6a43870f800e5491db756c2f5fce519d8dba5528b4bcef550d06b33699c", + "0xb1ab6621eec1ee6784e632e214693f39a14f3715991996b883d66200963e065c86fa0667f7bc36b93b40b5d90ff708c2", + "0x80486a26c3532ad6e19f76d8c9344e2626c07363fd495264927cb5935fa9565ece670dc98767afb04af6a9a5c9231075", + "0x8ee20e0df3c84a1c6b0e21bcc325cf99235b747ffe47f17fdfba548a358ca75cbcc331dd50db2311b400ae882256a608", + "0xaef4268959e5541e7ec69c921a1e81a8374d7e44bf1bb2debf4101cf3cd6b7d6ca7f441758b388de96b3e0edb5b97be9", + "0x8793629bd29d689ec94b016de8886cac6e2ca6638911babb22db4a787661422da0639a4e4089ebeb689d173abfe75950", + "0xb487b3551c20a29e9a5abbda8c50ff594826283e443c09e3ae09b914e46060b3f9abf70434444ce1487e2a74e562616b", + "0x8f11531cfc5997dd04b997cb87ba1831aa7041d5434fe72de66304e3f165d882fac891391fbb1eb955c65319e65293b6", + "0xb195136875fd02a75676c33cb3e60504d5964f7a9e81f4c8c8fd38af62e2145c55f765b3158664566191188ac678f381", + "0xb374174b0b3eb04fa49eb4ece45173f0db5d829eac370a20a62309566e0f98b18f72f3633626893c053b7be6bfbd2366", + "0xb2a2f6b0cf652775679b2d677048f2ed8c31a3269e6cddcc7a10e3e6fee89e486b50d9d55fbe452b79c4157c0270fb77", + "0x892177c364dc59032594e7a6fd032286ffdf4fa0b9e3baeb37ec839faebfd2fd46c57b2c9bfe9977b59c93a9cc0ead1d", + "0x8ab7c0038a7dbb2ef200dbbe9acbc875829ecad4883792d5c6ce283de67ccd9aa935a9cc7b30b2bd9de7fca7bf2a9a05", + "0x83745cfc78ca709835aa6c6a233c2b86fb31e3f9f6a8becf63e501f2841c4366fb7d131b746c9d3291afda714ff05579", + "0xa723dcb67925ef007e8339dc578d2622d9bb77cfda87cca0088854a59414c02338752c56116a6c1281917842e8467c38", + "0x8a098142da0af2254c425fdbbd0d1b1a17b2bd781391ab37f181775524b8563c64ab8a1602aee2ac6c0a82ba11a8b1d1", + "0xb13bd7529a9b351c5d395c794c28bcb0a3167f1c992e8c062eef47be9be27895945231d249c73a0b6949daa295e14944", + "0xa20dcd2fc2222eaae467d9f5db861040f58bcb991a26e5663ac3aa5e1ff13d0010657c5af586cc4621757add2b905073", + "0xb818f660c3cc4e9f273c25ceeabe562c8afa8ff88529c26f2cf45ae6b2813cca5f350e3cbd56f6257c4df41722dabd25", + "0xb225d5987108b24411bc389276f12509a45e86d5ad6b6d929af5274df0be11109c0fed329669a0acafdf3b0beaa8f2ec", + "0x91fcb6d04576d3c6bae947bb7843b430e5fb0592ae49b0a65dfa5791f4eaa4bf2c7f436c8de7360f217001c2b4e5c67a", + "0x8821f7a1424ca3fdc5d4a5606ad10dfaba6094cf36669fa9f84cf7617e50425405d14980780e1e18a1ecea7913cda896", + "0x990dcb7f38f56521a70cb71bf4522649fcd46ac052c7feabb0748dfcac9f9c0f95d29e070d32af3cd0adbf869535e17b", + "0xb0fac1029fe2c1100f24e2f4bf10c7672199fce53513c7dde2e8d9b00702edf0143e0e1dc7ceae7dcc6994edc2422b6f", + "0xa514ebb1a33451b4915c05114db0b10168393613744df848b24e43e09f0bda23baefd9d731075198aace586615ac7911", + "0x8b77f7953c2e67049fdca3653b8d8cf3f799677f79b954da02bdad8cc4d6c855c1c7c16b4f6f9ba35f46426ec28b2d84", + "0x875520cfbda16ec5b1d1d00f578a910d0fc052f17870ba093e22e310bb07648d34817cc2b8811b6f52de535f7046a0d0", + "0xb8c77b4be0b430851c4ff69e91cb770db1935d848198601393810ef395efab52deb9d5c6525472bab720273d5e0e7a79", + "0xb6d4d437146671bdea62fb6545395ea3df39f1cdef21b8476b68e7a25aa7354f847740576d6c9f187bbae9941f0ae450", + "0x95c642f1bccdb62cd6a2212dcdd6ff8d49aee426ca08b7cf3a9d15249d24a9eed5533f92a70c84498c0797f8a57efa27", + "0xb617978047ed0f748c305aa7f30c2dacd0db00baa67fe0c5ce346ef0e6991dc7e05f18dcb2702467421f8390f27aa815", + "0x86411c7a00b3e8b43bf22fb061b1f54ad9bbf632cd74395a478218389c0f544668acf3dd7726532d080ca7da9a5f8608", + "0x97bf684a8849626c4710a6992f6c11f6b5406fd4dfe9e6aa502425aaafe9827e2c435aaf9a5d3d2ba3a4c0e8aec79ba4", + "0x8b178e2a125b461d3180906ffba0af3dce614c64058501fdd35243ababf892d6fcdea4834ce42c25d5569452b782a709", + "0x8ebed2c8a25c61da6a6a8cb0d8f5ea179e28869753eacc728f2c076f7aed8598cd3aa0981f120f9e7ea55b3a689ae882", + "0xa6f235b8e655ca3d634740b53d8c0a757ecc75d2b8838b7948997c1985473d01943d935f687b86cee56cd47c8e773443", + "0xa7959c465a9646908b9d8032a589e41a7dd999f2ffc54bb42f22e5f8a4d8c493a31bcc7ea2cac6c8dbcc59acace7181b", + "0x96d0532df2e12da20a57cadb6cf5f6c4ee1aa4775629358c25f1d51677a3e96d1fe3b232532324b4f02f941952d4cc68", + "0x90f493473d686b639a30d1ddc9c72eae6e983f1236e162e58e967a477c0654973ea2e1bdf4ba1a44d7247bc1befc2cab", + "0x8b2d87876d9c4085102a07ebb41c565ba69acab99ffc03efc18f20e48d3f3bbe4fc6ddab9c78fe479d9ada80504d85ba", + "0x829a0fb3200a28e09cacd6c5346000e7786116ddfd898f37dfd17bef454a8abc0fe939ed8735c00769f7f2f33cd4f906", + "0x86194ec9e88ddb7150e8b03e7a535b6e99863fc6762835601efd03615aa97aaeb413cb210e86035086ed852b39c9d019", + "0xb02efd116a7189cb317ceae392bc301ae55470f0489fa89934e182aeb8c67e280299b975786fe9a470bff46827defb9b", + "0x87d7c3903bd22b12d815506f150373f518d47dfc6e5fd74347d88b518124c9923d1e4c98defeb3a45d53d50b423e2175", + "0xa1a430406b28254a7d6348bc98e697e9bab43839aa05d53faee97546f84541ea0b559162619b2045182938f69bf61cae", + "0x99d243c226c61c6697fb3d2594f3533fa5dfd7cfc87107908cacde337d7a077fa5a9dc702d26081b065edb1227498e65", + "0x800ee5006ab6217161f42db0cfc552a81728bb4fbd7af6e4620ea099a65ef6664184af3f65a07fcec7e965529c5b49bf", + "0x91bfd307579cadc8f81009558605be3edbcb8dbba271475803484017f40130b2b216aef4f620d960193be681877d3a53", + "0x96a060459dec458d19a6f8af6e49dc6c7c58c55dd18915c5fce5e0f4b4a422fce3b9632f6059388fe760289abf70f173", + "0x9921a37f3e657222c7fda3588418a9071409711d9f1fccede7494429f02a45fbc52d79fbb64e9ccd518f60d06d0520d3", + "0x81052b0d15773cb75975ca9230ebb2579700e489c7e3f07cd9cde206fef38b8139bd4976d2b4a7840495fc645f96df03", + "0x88ac37ba66d1de5e23878c992e4d54023729e97e77351f50dc5918d738b5a73faf1dc6feec7e85784761836ba1c6f778", + "0xae1e6072c13060775f6086d1ae1f88b627ffcb810fc0e0e97deea1f3a15ef0aaa52a6dce2563e4beedadc131af2a8281", + "0x8b60a340f5e4f90badf83001b495ac9f13974c3d2054ddcb3e6b8ca99dec5cd63a263e05c282454191ab2e087d5a2911", + "0x832e2d56ba69dbf817b2b9dbd25c1538d5b8dbf5d9bc05e6be85054a423ebb66a71b157e166e0b9444ac171b34b7ccc9", + "0x8586036fc7dde1e7e3ecb61663130c4529866ae9f5f5095b9fccd24a4c70eea899aae5f10ea1ba66d1665b2d83be35b0", + "0xa77969453b5c083a207913272b5b69d4ccbd8718bdf54be8fbe11b4bd0a2168aae3ba8f9362afa69c0ffa28d7e5a2340", + "0xb7fe9568c214baad0ac5f83745611b481f744ec1c4fa78a549b180dcf79633e5ba75dc20055012a13d849eb7a9be57d3", + "0xb01cad1d2a6c51c0ce88243d1f52f95fb5ee315a905079688027511f0c4ecd0563a3a81846709d272fa5ccb9665e8043", + "0x8eae0a21adfc569aa57237654021c2bdb2c6f0f52ccc90a126682c21a1f9413c63d285f92b2b2f8649150a9284bf70b7", + "0x942acc947192b5f3cf60e92383e5d35f79e7a5904e8e9fd1c8a351676c83ad29b0afb6578d555457cf909f8f4d27adfd", + "0xa74e092f8628fba9abcabc27e2e9f3d5a9a941dfe50a2dfde2ad179aabc73afd196676925c2d98643ab8b3d02bdb66ad", + "0x896159daa2afd757cf3f9d34af248ad68bb3c62e4c9ac49919422727479cf669098f270b9e645607a7d11adad4c889b2", + "0xa428d8370813d78e7a2a24eebd36e9da2f8bb3605e5a39b5fcda939b531c35a8ebaaa642ba556250a37bddeec90326fb", + "0xa5fa04eb60a1d5ee9820e78f42f7be15e1c02757b539aead995768c6209684d6c183c71d282e0c12a4c15c03f9a89d4d", + "0x93c77d5d220e40affa7269a6915c076c9aef4db552c643ae5d560a79c955b491c6346ca4cf11cbb7fe1894e28d47b065", + "0x802e605d2de745eef6981d88e7a57ef4046a2062725e8080995374cea2b3273c27f35b7774d0dcba014710d8d6c501f2", + "0x82f7169e6ec9b3e2bd450f35ea2e66d06bcf900acf5b73139677b48e078ce2e16599103027b2326770c99c0a690f2015", + "0xb0c8581879439f9b997551233fe2de71aa03604f9cec37a7b18c5854342d9b67be468f3cac4bf6f64fe8a0066248c498", + "0xa3f626848a4db6e9fb01cac90d3362ec521e969ebd5228af694ea3671061476149f13d652942ac1e39f65591fed740f9", + "0x88a8e759b9cbe16a7c16e43f4afa2de6100d2eafa4dee75ccd653ec38c919013d0a6b35c1ee1eaee7c1985b58bcc9e92", + "0xa3d5fc7aaea072798490616552d947e95f49cf02a420314307aafb555287ec607d75589ba24b009cd68299dc6f7942fa", + "0xa809cceeb84f9bcf3c3ddafde3041e7bc3b1d14df8830ab849002176a0725e6f16f70774d8962cb0b8ac0dc43c4ac66f", + "0xb8f2e46c031cc8fa160a08c2ebdfa85345ed14771b06daa9636b0e7792b7fddbc501dfc85cc626a01104a43a7d3230c3", + "0xb5367e2a521c318b802ce16ceac80c4b8139f73ddb10ddf38433397cda70a86ea1f051cc55626a4e99d27f30f3975ff5", + "0x96d963660121c1441cd13141279cd371a6a0aa18b6a20761b18df60aa9c14e13489afd83695a0921d5232efe72045f07", + "0x80818d492fd85d666bd91aaf6257b86527fdd796773c793407df1d4a0f91d74649a6bab4d15155c36ed4c6e0a32c5636", + "0x931e22918905fd6c230d3d867ea42861f3074d320d14e1929031924c8ac209a5c552b679b24563bb12f9749b4ee983bd", + "0xa4de2c333e74ed9bfa3c0bf6a0beb90427abd9aa4221294cda74331646b58ef46ed57cccc8798ba2b9309894b17cfd69", + "0x883881554c1d88c0ed8d3b6dec3d200f6fea69a77ace3e4d6f86b41506a23724b4394ec8384075f9c75c3868ba8a8e8e", + "0xaa0539ecf6ec9bf06f24443027f8f24b6b3d8c5b2084248eecd4bcad3c9a69716e1a0d01057f09a65bff1006ac5e157a", + "0x856d74d44c943c9e809b42dc493dff20eca03cb0cf5ed45108c69b1f90d8592a53ae8100e99380a274fafad23e74cdfc", + "0x9188257446661c88da093b7c5ce998135913f63842d7c1586065377b169ee35b062d925367fb9b909ca971f1188667b1", + "0x8d3aa57cdafbe998938787479f5d590c1484c6dbe94e6c487e57a746ef5252be0eaa5976d6270de7db64b6b92e57a0f7", + "0xb8f4d6997240f9eda5aca0c43323a828d1563c491b3db2087f60ac4120a3fcd06075fb42bb19d0339ab5ee3fb7db25d2", + "0xad247ea94b8ae1e81eae4c9fd7b39e6601b53cff47b2547ff90a3cca87192eae28408082774a1fd14bf9ab459b7a4f1f", + "0x9598598070f8bdbcc49056c40971e673726cd8c1bc4baa0b5124dfb5fb750e7baa7a7df18eae2bd91955ddcb1ec67955", + "0xb874131ab1608667fa60ea29092d090859eed1812e90c609afff96d79e82c5ba546f617f4c96fc32c9bba97431c1e9af", + "0xb00750a9cdc75c2a54f0d3cc99b0fe02300754f25166f7ac85ff41ab5e9cfcca33a29be76a480f12a2d410c7cd5032e5", + "0x84b5bd1c90bb6c66755b28ba4af493ca1b0c3a4df9f436aac67d2e07289053f925cf6a149a84e74e1027dc8758150179", + "0x99caf64bd9d193ff306e8ab5da3f1bb2a190a60c3a82099b8d03d17fa810dc53d176c21379f479e828f60d25beb3ffd0", + "0xa8fd9de502f1c261d5733430e5a18d8b7892a98c9529a016fc2ee53892ae965dcd9c75850bcda4c7edb980b8d88e60ea", + "0x848c02cac636e047028a3fe8c1bf4066fb7591b96b0340f8fbd476ff01b35fa3e37d309333771a134f24800e5f3f9289", + "0xa1eab1a06dcca3439f0166441e7e7f2f5b56f5f8aa9f45e411c561f556e0fb71c514c06c26ac53b49a576caca5faac3d", + "0xaa603f970dcbe953e700e61c151182c8d32cbbb53ceef572ac93383db33a4b098b5c7b267e42d514ca66b740c0925efe", + "0xb55fd5301bd700ddb0b4f72fabe9a91ad49759506101fa802ed1677e9553595aa4d2c66f7574e78d21ce882ce0120ae7", + "0x829137bc4da7b4886d3d04d2c39cbf4b1dc40c813ac1adb425c7b9abf9142b516314cab79c68454df5d71994ce416144", + "0xb83a3a22735001f783dd48a01c4fb3598a51ff3987e842b8045c71c035b9e43645a55254ca5911a5676ef4a8af12d056", + "0x8ca8d463deb13f9eef5e533bc39efaeb0c15631282c5c0deee1673b0053a7cccd514af09801dd6c158caa159fe9351ac", + "0xa9ffb1427828f3c456b9c8cc50782de1ab0029b9233a0fd998bad0fd014d27e15c4a32d1e16ad41bff748378b5abdf49", + "0x9627e29f725ddd86456aff813976bbc4a836f4deabf5ad9f73d1a260ceb30948824df9c8841e6b3c529652202be181b3", + "0xb52c988647fe3d9276eed3c262e1044f57fbb116c64cf4f207235c205b3fda0f3d789bf90f5217401b468d85fdfda404", + "0x833bbd6e2924f5c4446cb76b881d1434a5badce9eb9b003f85d076e297ad7ef45b822069fe54d17427a348c3263fb838", + "0xa067a36352db6f82a116cb87d3db5f60b18576852409e2076cbbfc7843af78866313a4969385a40271051dd195d51116", + "0x902b99545971f9a103f99d7399acc347ac46fe156166e51deefc0e92aebf5893460c69aeeae11f5af9f49418e289ce6c", + "0x9206a0e9ce9b9880f29ef0417c96931985f5d83bb17cebdbba4ff2af81a3d37155b04649426f698aed372e4f669599e6", + "0xb54a5d7c976e45c0b1d44433595eae9d1ae9aeabfd58cd5ecb0c5804756a7b01c9a517754423b4714a3695533a3114c8", + "0x91b612131e84580ece228b81ace83da0269b53f94d3c02a1a0879ebbd81bdc252064b3d03a7e140b43a90f237d9a45a0", + "0xa6cead3b8607eaeafe37135bd6de8fbd16f806c131eb71c8d36bfbe295d45b070255e50dabf076e2c3f6b8699be71d6a", + "0x931da21e67b11ba6ce438546a24d063bcd51aebe39b4220a78d9c0aab88b2d37969b5ef3502d835507f9c8d6d006714c", + "0x8fda408caa9daf01122a2308b7b9d328f52e1e2f138a8bec30492488f4d710e5e52524a6455a3a2ae2818ec8a610b650", + "0xad8ad5c189644352d90c462731c46145410e5adf38682bb80f95495dd64d9d13782537d68690847bbb06c6be7175dbc7", + "0x87bb5cc466ade60feb0961421c3fabdc8a7e20f11df8437bfff63d3f8bd25305002a396c9d0fa4fb9a9986d4717f12c4", + "0x827cff72870ba00c29064a7d2b4973f322d6b6de7924c93d8bf8825e7a0e8478c7748f90f5c716bf83c55b2795d315d8", + "0xa225895a8e94229776ceb51b05356291f2dce748be17a60d5aeb33ef8507c368bafe5d1d6eea927f28b9d1422b661b9a", + "0x8e011323ce670ff51c964241a6b72e0e0ffbb3ff9bb2762492323fc3a4abf4718091be0945287c7329850e4f74462cde", + "0xa2c03c2e5f4e9d3ef361f68b188451994ad1b24de9f323370559c8abfcdc7bffd289d92e78a5f6b104b0a12c84dab2ef", + "0xa22b4771116ce22276fab1fec6826610707ce8a342f9f60b079c4e0259dac3cc41c96c560dfd0ada6edd2828f7c0e8d6", + "0x97c17441d0af9be83b42097aa8b7cec84a253b9a2b957214b8fa93c26d2add46144faffa7b8a55312059b10690f711f1", + "0x94bdf348849f31a2737cbae5e5848aee711067bac85c11c2e68b44c398cfafbf3493a3226cd1ddf7a916e7613fc7b6f6", + "0x838f59c6e8469a8ec6fd40b978a3607439aaebe1e50ff707eec72c0b8278af05b477bf12a384b56d03e3d4eb91e56f67", + "0xa1940f0db58185e2b3aedd2b0bc2b73b4a65c68e09b046f38e9dcd4e13c94f5406bea92635190bf315e48ec64eceef2f", + "0xb2f4e0ae44e1f1210a91d8f280f17091fa994034ba8c991583f8182a323e9b3001a712e3584fc2d64ecbf2d319d076b2", + "0x9342b89c721338d02c7854cd7466fb24d93d7313b6114ea591e6607439c8ddb911d1cf35f01898e9c557982bdff8f9b6", + "0x8583fcab15be1dd14d5a415f4b14d706c8c62f058500f1344b37730c8be6741779691f87ded3cbcf6516468b373cafb0", + "0x8fa9587c7989646571ad9032f34cedd353caee14f5be5cde1e9e0a1710f90c08faf6fa96a60e1f150f761c9c8ae7417d", + "0x8d9ff904cc08141f5a9879f5f77dc600e6edbe859082231a4d819953890199bcc5f940b730ea688332f07e5279d49e1c", + "0xb5f82b46e5ef9a2df8d144202d6e2e4f3bdae8e2048d2af5ea7deb3f722fbe6d370401954e74ff0d8cb1010ffb1f38d5", + "0xa3b5b57d435b06ed70530e060002a8fea71746ad07d969ca23f22b5e52624527595b6a6d54b4e953fb7b7596bac378f0", + "0xb90f89390df6d4b7879b915aa3c29b8d779d035033f8873bb7ac54a14ec98f0d08c0e3bf696e2ffa7b5730d736f571f8", + "0x8e81e371b92887e43d95c0dbdcc9575282b26ccebdc8cbf46587e4f2a83b61e9bc0c6d7d1f114b9d21e04fd6c180b12a", + "0x8d682947c51dffc6e0fe0a486293c9ed121f441805168236393087cf62f2a429cca60bf0e472564844347d32c6bea27e", + "0xa8341ec7dd189fa7168759240224192c58209b53fc961c18082deba217928c399bde08ceae42bffd37c1135b4d14a845", + "0xa94bb076dcc5ee5ec82fac57c5b384c690df12631882bd1b960e1eb8c04f787bc22b7bac315b9dc5a8a098f17f051a0b", + "0xab64e1c6f01b87706c88a3bd974454a438722768de7340b834ccf93ea9880c14ee7c2181432acf51f980d56de73832ee", + "0xb7b0058bb724d879e5ad7aed6230297c54cb599ef659e86bf2cc84c38225899fb388391df9b2e6fdf063171937fd8c72", + "0xae856f4fb74c27cc98b67429186e7df4feb01278cd57bfd3170af6e52e0a23b9e926bf9565a890cfb4ae8f2d590b2cd5", + "0x804b9c6702f0596d328f92fc1ed5a30a7ba17b9204524135001b569233fc4937035031d079f52fd04968f37c24013898", + "0x84274ed1af6bd6a968583995622b4d18c6a2bc703ce0d0edce45bb736529b4836343dcd11911a94a134dca7877e6cab8", + "0x88808098463f7505034c3b6328c8a08186b33f7a981c08376e429dd64b79b97753170531ed078dd265ded4ec0a1ed8d5", + "0x92823bfb23a4eb84d3759e7d717f0c8641ece0927cd2ba8c728c26bb35df2629a838002f353c8d3d75eb19520aab5f25", + "0x8db36bae4d960cdb9c51f419d7ddc81f372e56be605bc96a9d4072b829f05527c37c8f255cc6115300a2a0d2e6568d89", + "0xa8fcdbd7f3b4d7ff04149a209feb75e97149e7efceaa42d66a6b8e432590fe7bd01f1a77fa8b47108f670b612e33fee9", + "0xa9f4c53c62db7e5dbdea6918862d3c6d24b5bd8732a218edf0ba61e9d1861182323d8ecd7bef8f895b42970b492f6e40", + "0x8b95bc7f07818f4d7b409aff8da0b2c2ae136cde386f53a71565cae9fd14c73c13cc1cfd79c0f97cd77839fb738c5b9a", + "0xadbd1d11adc756b51a571ddbcbf4392415231ddad93da09acfafee03a9e4f9e1ce3826110619e5271feadfaffce3e793", + "0x95d327c8bb195cdf25fd79c98f9406a6b0316214b1630ebcce95bdaeffafa36fc1accc6882e0e5d13a8db5c0f3c0e61c", + "0x8cb2f1e2fb25558869afdacc7bb866544cfdd566cefcd048b48d458a886130bd086ecb7600a960a7f2563c61cb326510", + "0xb3aa8c4bf5b933d89cd74ca7f7176d6624d562d7d58b041328b49d7562a30b489cb606abb3c49e85baf04c28e9cd1f44", + "0x97f9053a85250c420599827297453c2cfde087065b823d9e43139e6a9cac3a2ec40a1b6e2f0726bdc870fff215462f0b", + "0x878d5dbe6b881389c2ca126ff66d87127c9aaa3f62f0d2c1ec0ea2b279ac95f8a06710dce166415db227655e2345a04d", + "0xb2c33a6b4203e3ca5247f0890e475518317ffc44cfbb1da9a1ba02114e8b752bea618050b876de5cf3b1906140a64471", + "0xa56170c8313d2b5541a795bea9934d4425b185b5c409f0484df6f44f0e4bcbf50b860ff46b7245cd99c1cfa8fc1965b7", + "0x96e2b658e2876a14147385fc423d2702a3cb76962b6b437222cf9cea39ebf4bdc03bbf434b747866d4bf72b4ceefa639", + "0x89c4a74fa2f067e7ae49c84ef782c331bcc9245db7e941804e2e99d12e987b4d25cb827778ad4c3566c4fc68018650b6", + "0xa01d30cea7d01c80ff26650020fab02e78fc3842e2398a81b44b21d58d4e9816166ff4ed2418831fa995a28ff35cb6f1", + "0xb960c80b55a8845bbf24bc3f23b0110ca701f9544ab6a5bb7929330213cb471321e55c390ceca3e24bff69bdb0d331c0", + "0x802c5b13f22be7be0e5db11eb3be0f0ea7f9182c932265060ba05fba20ea093dd2810d3b969ee3e387e60fe6ee834e8d", + "0x92478f88ef7435d15e39a97916c736abb28ea318394b88678fddbbaab3eaf31776110936abad116a8ff6ca632dd12043", + "0xa6d3da0370c303001d5ed99d1db8bce1f26b0e442f0f042e36db9674e92dcd6e80465e772f1e669f99221caee3392fe9", + "0x938f04f70a8f947d6df2f0c0e9af3cce0c06edbb3c131970dd60884fc0b0a0959c504a2a36c3ff76dfe919905671626a", + "0xa7117e55224230822e9983df2132347eb7208cb6798f291df926ab51e04b1a1f78d5568c9a8924ee6f57426134360f20", + "0xb91074c77ad93fe48dc2b10c0c5a62ca3ab7d98345b919c52d84a9dc419b59fc1b267e1c2d4b2e120016ef84bbdb0cbe", + "0xaa175c6b6edf02fe8778762c9575581c0ee6efc9dbf99c291a41444a23a056b893be6c45333d907d0bbe9fb0eef84d08", + "0xad36dcb4e2ab425aa339ae464b038d550cb11186741dcf257f1b8b80ed4f32ffabbece45e2dc1525d4c3eeed819ea04f", + "0x91cb35c1ffa9cd5aebef523edb8325078da3eb5cf9e95c675a76446fc7692aaee6f949de064ca2f3e0f082cc3fa93e20", + "0x82622f9410c143a86bc4d756b3c7b324dc295231ce865de020d61cc0868f2c150a473cea3a5b756b36771ce1032415a5", + "0xa5c29996ad3a53468ece9356a5b4ccb68971ea1c89cf39644f1da2d4a477c2ea99bf791ef902b87c225d8c53d67c4c92", + "0x92893eceed1af34fa92b23dcbab175b6a0188a27dbac9ad3317c4e39955a763cb383ab13fb1c519cde311d8a4d12e8b3", + "0x8a093cb191b94b0200e38d31955f9d240e2be1edcd6810a2396a061f17c3ddc9c4f4d56766ddff4e121be7110e03b869", + "0x93981473df0cb1f4b47c7d9b64e3123dcf1593845b401e619f5d7c70b5dbea375d1ca43fca65845fcf0a6b2e0af43791", + "0xa6beb6b0697070f9562910add88d9ba91992f8da127b27be81868b1596d1012f09ea7ed601b4a6474c921a1a1a6d866c", + "0x92026b1ee30f2ed61c9f30337c3356844217926aabdff383c19ca3c21e0bc49811ca5b308012bee4ef250cfae1615800", + "0xac0ebaea6d35f84dac4ce648af096305ba68a7a0aea0a11ab2fbe3162075444a158433c98141bc92ef3b3400d6deb46a", + "0x83046f482dee24ac3ca83373f0d1b82ac1c4beda0f229a9011a81ec659ff5fc1fb105e219975b5c744308c77a24f71e4", + "0xaa5a312c47ff7248dcb9c6ffbe5a0628ccd565c07365c4413734d415cd4fb35772622ed833862dddff520a67c509c6a5", + "0xa02fb88805c34018ac33582e19ed0a7e4616acc3dd0867e5f21914c2031c05c6dca30b8b35b57c2b137750f3878a6f8c", + "0xa60528f1f14bf0c496491d46a0fbbd6c343e4eb3f1631e92f96a3c5e5c684091aabe5801df7a67f7c6dfd1b0d35269d4", + "0xa1fd8e7fad8ca05a340c05a051bb0eb4197eed345f4104629a9e38e234b09d789cc5537024615feb4a6177d32d39e39e", + "0x8e70e36c1aa070815440e19443f1f04aae23b1b59fdbcba43b47b94a026c82c8f66c5dfe54f826f4d95ee1930cdb8008", + "0x8234c1969fa7e9079661e4ca309b71b1aaa10f4372be0b963205c23a81f5a3d52ec08ba9ff65b37f832b52d631580d61", + "0xa18cb4134127fb37c4abca328cd0047378a2e1423490af2bd3eba9ffcc99ca81a3c22404c0886f21f65c7b93c41d7981", + "0xb46fa45fe538816de776eec086e040005706cb3eca097e290abfb6864e745c879868aac8361894f3c3564373ef9ad55c", + "0xb96ca43b96c59e95439f75d1e726a35a9362f0dbd34963b156e103e080a8126a8dc3501f9fd541ff3bcf4677f5c4a86b", + "0xa8e8c87c7301613818d57387009e601a7ab5cbdc2890f63d985c30c74f9cea2d0584c116baf0d9cd5594386ee93fc661", + "0xb47e4f1b9153ef0981f813948150f283b47a7346fd9921d51fe8e4daedaef78ddeb4fd467c2ccb7cebd9816243da1c6e", + "0xa370c202a99c8441ffe96fad0f801086d4d7cc7b960f6e98cca29ceedf492afddfd0f351c9c4d29ac008bc255ec1a2a8", + "0x8f5e6ce1655d1c059b006174e3f5a55c88e1821c97f9702ad8e8455d46c2a83ae4482f2d43edda74a835686ec45a8a15", + "0xa30421e694930a3b65d397b2720d5f8e1eec2b6e2bb5a28d3f9b0a84db9aabd83850268bae64c2b10e313cccf120151b", + "0x8abe87163046f7a9b18e2a3c0b66e258facc1b31431420e0b70354b7a60ebd250a784634a76692e7d6f4330b62114945", + "0x894f033cf077d4eb312e3258d9dca414356271abce1d6094ecce6d018c5fadb1c15d8d69451574ad0701a2876db191c5", + "0xb0923d64f88ffc872654e1a294bb1af8681689c21cf08f39afe51448a68e60a9a0a74ccce9969276a932a52c07d095a3", + "0xb9ca23b5be8725fae7fa710eefd45522889c50c29c26384e00b78a962384f0aeff9d15cb5910e9565da12a577eb7e5ba", + "0xb242ccf292757197a9f470f2d80ccddc48c7f1235ba026bc68a93be2738bc968e8a200aff3e2f4807216442eb3fc50dc", + "0xadc2c3b375b308524b79a024ff87d122055440643fea6fc0a651bdb312c7cbe6a456afa9d342bc76446d77d8daf08bc2", + "0xab645955356c2ebf2f3df9da275e01daf0b44a52afc309277d6d9ad1b05484e5ae0d9d41ad485fe481e5e362826a86ae", + "0x8de96ac587a4449fcc8b7fd0a51b4b5185d9c2eb3434f94cbadd092de1e26b0f6b3f7b15a37e8424b1429121ddca0ecd", + "0x94c70ad4e9b871566f3da98170b665a09788d421818299857cde0853789fb943cbcf7d4b2c95246ea7b72edc56a8e36c", + "0xb2574be63497843340700b701d5cc8be6d23125bd62058802ee67cce1f3b5f5602b27c93fea5611f27dc695ac563f042", + "0x869ec89da7850cedd88bcb3a50a15cece233119b31b64a61bf6b2310892ce42d8b473b584b11e61db29ed24ce8033f83", + "0x8fbaa269da8e28e9adf4c1b08f109da786dbe9cba871c32eecbfb10619b7a5d65a26f9bb33e201a8ed20b3de94003fbb", + "0x8bf7a059c37242caf7f821a6314e4e4adf799e0dd86b37892a7172598892c07272acebd05b534755c57b51556b2d610f", + "0xb4e72645fca459898cdd9214892ed08b5c99f82049c0a30d72bac0b9717caa9c6cc16c3dc7aa6ea4d42dcd2a6c175df6", + "0xa39170da87a3495da55bbb9701c5461f3403447174ed6a4af75712f7ba4ac35f51a4234bc4b94da888a0959ee109c0c7", + "0xb45675b2774ea7696089dbf7a0afe6c22e85fd0e4ef3db508fbaf96c9d07f700c991789206da9309fd291be696357c5f", + "0xb52899e3e3f6341eefcbe1291db6664bf3b6e8021d32fb9c3e37b6258a35c1da927747b2ce990937d6f4c6c3e7d020d2", + "0x84e5bdb3dfe19700d79dd3fabb0159ccfa084f7288db836c855b827613ce8071067c8d7ac5cc2b4e88ed7f84b690f6e1", + "0x801477d200b6d12fc6e0a9bab1c8211193ab06e44551e037a9b4c36fc2d4f67760b9ff4eba9a3bc7b6e177e891f64ff6", + "0xb6b71a5116d3c22af26a7530f535e9b7851f25a84e562a8f17a125d55b9b3fc1bd8cfe65bdcbeeb328409521e802051c", + "0x8687e21c34d7804c12489d30680d131ce2133e2981bfa993afd8a8eeda958ebd5e6881d342d725338659882d9f21cf98", + "0xa024e97a7c4de32b6383c34431994abc533ecdbd6be9bff836ec1af022f5a86773bf345c6f33273797a61fb70a8fd5d6", + "0x83f784f095da20ce5b31f54d6cb14b32a8a12675f0029289c9cd036b7c87a8077be2d04a62618685720e6ee69c875e97", + "0xb4e9dfe7cb9d9efd3fe00d99ae5e48769d4af4bf43d4e05c0b54c9cfd8bc854de96b8d3ebf4dcc06b9dac66b7471a0de", + "0xa08b79f9d4673afcf7f38b57f484f88feb7c908f597663a2417f92c348150c2be6b5603f914eba0d9d5bdd4e5c5572c1", + "0xb0eaf919589988798cb01ba0610cd1b7fa3c08715675ece8ecd5f9ef6d5d7b2c4c8ae1ea7dfd202237171aa3e6f9de74", + "0xabff99a98baae4dd0954052503ce81827781694a5ea8c1149f96a3adde75dc2d630e138598cd2ae7fdc7a654aa17df8f", + "0x83e369b8680d8b9d995222b033b4f4f3e3b20e782113c941325c7fa9c742feef8747e4a212d9aa23285a259cc4faef8d", + "0xb16d5855dd2716613697eba36e2fae0872aaea6999e91cf6552f93f9a0b85ed4f6ff922a91b50816bd6cf8e7a4513fc9", + "0x848373db600e32e741aa1d37726bbb28956783f89ce2d781e95fb1ee1adf4359968a141678af268077eae4c25503204e", + "0x93a0dd0fdac18a31875564505b4e28f9e8bb2915faae666538597731ac56cd77f23f2456461e2f672983fb24ad91f6e0", + "0xab1ebbe49fa56524b564bc2e43784147073e6ea5d27a9540fbf2e04d0f87c645ed2fd28b3e4982cc4c0af1734ee47a6f", + "0xb3ee30b733839edab6f61f0738e3f4afaeccf700d8dc7415684f193b36d70d07acd5780cf539f12e0fbf8d4683be773a", + "0x88388f2cbdec47a6b3ae460b69eb0d2130ac14de950c22fd86de03e40d02292bb93cebe62432da39d509c1289f785fef", + "0x9370c41a54b68ff486b4cc6329c3a851716ebf1d088d77a6c56dec93a18b8a77b596cde74cc17d2adb2b2f411a2e4bbb", + "0xb9083b60dc16531f77b05a955b51a237a8f8c0173d72c352c5ca441b55abbc890b14937e457aaec4be5cbbf80cae0099", + "0xaafff8f6c6ebaad952c65054dfc7c829453ec735331bf8135e06406b7a9f740c9a200dc48bb2175516b41f77dc160121", + "0xb43d31fbbaf10526809e9e5bd8bb47a76e0fabd7852ee7744404559ab89f0f215ff518f3271a6aa972a459cab82ac558", + "0xb581ede48c6ef34e678f91dc4b89507413e00e70712e3e8c32a80eed770ec8d8b98caee9702d068aeaca6f704be57bd8", + "0x8cb0a137e68b001a5ccac61de27cac9fb78d4af7b2f5a00b8d95d33ac19cc50c69e760c5e0330a85c0ded1edce0fe6f9", + "0xb947fca07c7aa6c2bf13048275402b00b77b28f1d0ba4b589fbcede13f93b5b931c588560ab8ceba23bb8e748031b55d", + "0x81753cced5ff819901740a9a584334e355b497cb699f0be5a52cd555a4c9f149535c7bb355b54407f7f0ec27de6c2e19", + "0xb3d59273951ce97838c4853ec329782a255b5fc7c848e7992ded1be28a5ada7fa3254123afe32607b9991ec6e0659b08", + "0x86b253de246f82be1cb0cef01e87c3d022ca1829d2cc7e6a160a5afbd3ca6b94d75739b122e3bb16f8bde28a8f3223ba", + "0xb728b659fa2d8487e061a37f7d14a4c2d70cc37497a8715695d8d332cb274deee2ce23b9b5f6a7408516c02c3d526a49", + "0x81277b46d98848a45abfbe39842495659dcbb80dee985a4fc91d77d52b815487aa8bb455f411fcce4c3879c7a075a93f", + "0xb05b6f1fb4a6e654f0ee6b83e08b58b57059bb0b7c490405bc8d963c4a2d6be39c558917977e554e1e9e3169961cbf3e", + "0x88f75fa7d016fb6442551ec071cc1e2beeb3ccd213d16d744f573a82f5d70f41dd1b18af71d5f9e73d87f2f6b7dbe889", + "0x81a46434f1bbd65a661a0ff45a0295b8fd8a42a7969c5953721bc98698b64bddee3f806876d1e9983063fdd0c11f99df", + "0x8b4f6d33c510a4c9c7d623d9ae0c9aa631fcb987704726b2a4d8519372123bce3c439202f25b5b47045ec14ce39a21a8", + "0x8d5112b330fb63cf6ef3d2164b404c14ff9907d685015701399a260951912b19b8f270f869df317e9050a127763d7980", + "0xaadab394e84dfb82db15ecd2427f39b62352c3e1647c3bcd14fb24ae830ad0116f0fed87ddb63963b424a4741961386e", + "0x81ca4e5600d00a3bda24cbdea7a532a4cbbd893c10e7ff10667c15ffa8138b91667abe5466b31a3dcdd60155c48538c1", + "0xad943af1b8a5fcfcf309ed8f2f916339f254cd555c71a407a47365a139306286a05a8314e1c70e20a65fccd75d36fa12", + "0xb16597a0b437060a390467bbfab94c0bdd695ae898894f4689f939e30cc2119cc08ecb594546304adf876f4e275ebcd9", + "0xa44a4e0a6693be356065891c27eefa040a1a79475be53d54d5fdcea7e0668ff9b35f850974000ed119f6865aa6faa721", + "0xadef27d1b6e6921f4eaf69c79e2e01f5174f7033eaafdd33edcfa5119af23f3a834ffe1bdf19576581b797abd1865b34", + "0x90c1e9202f3ffe28f8e1f58e9650dc4ff4dbc158005b6f2296ec36147e524b4f2f87f8aafc39db5b006fe0c491c92f45", + "0xac817cd54288b6f7fe6338415344fc9e7b669414051631ab2f27851c052c044be06bf7235d668e194bef695923256368", + "0xab14944ef653a14456d4ebc12e3196df3f1b4707c4e50b317b5ccc8ca3a0720f0330609f0e7e71793f6ca01583f38c70", + "0xad5353f2f380837e5ffdf079350b3d42935a0517861d03af98db5ed3ea8501abd68885c8c65f5a66e944b1874826a450", + "0x8b5583863f84af8443ce8970b02e26cc5d959e47efbf8a66a54106ab165f1f76b36423aee74c7b5402fd1c4d7c1adfe6", + "0xb3b46037eed9fc30e4f8f0da8bdbdcc40a38e22e876ce9fde981883017854aba82c18eb00887d92ad847d30082fe7271", + "0x98a2b6fc90b7ad172e4368c1e54675b75c8bf2096d91c9f2b60b3397d3be3b705aed5389845dbd68f0f84438cd0f7687", + "0xb155e800852a5f90a2eac69cc4483428da1dc2c31588a13c924e60a7616ce9baeb7d4b829c772b260277cadd8ed84719", + "0xb8b92c520a1302b0cf7d993a52e1dacd7f27bda9868d59c55687d995ae676b7070af4c0792a9bc1c2635d44a4fee01bb", + "0x96dfe9bde526b8fc829eda825f55168b88e8f4e43d4d708cc3060df03437b46e12a8ac70d7788aa75760f6294d3e84d8", + "0xa3fa66c54e2fa084ced3bd838614c6c33042f492a5745d167a723c60d5e7d6020ffd1747981a23f8b68df21ad8f0fa77", + "0xb573ca10cc41fc04a642f6f62c355a4fda69b94b8e95dbb02fd1ccce4bce1191356e1fd66d372159944eb36a7071f005", + "0xacd0a1c9abddfd0ea223eda1722aaada362d34234455bd1c6be115d41e535b16f12ca428da7820a757fa4c98884a385d", + "0x96f242eee99c4db383b8754fa7987c0c159652e1866faec905a8d3f010e0a1ad05bd77b9ea8dfd653738959180f58430", + "0x9215a9b672a5d6e435e0e0a45156e0e20f75cbbdf1d14940fed3ddb63d433bef643796c7a4fff881829ebb2b2eba9460", + "0xb8ad9bfceaf08dc5a874387219ddd1170bc3a5e25ed72d321d59ae713be5ddf9fdfbd3aa7ab163be28dfa0dd14614e19", + "0xa19a1050590bc500b32c502f393e407abc3d8e683d6f6b978873aff3e3299b18b1f6b59e2b0fe237d819dbdfcfdc98ca", + "0xa6870fb11d4429686e52e1f44c8dcfc7ea24a020df9570c021578dbc1f9bdc8cf797cb3a72d7fc52805dba35d59f2cd0", + "0xa7be733b64d5c06c127bd1c87250e42bfe30ca91ed8ce51e0b6e377f454e8f6fef7f99bff650695df2fd10c375da349b", + "0xa1b97145dab30330eea2cdc8739b2446a3704b64505fcea3dd8a9b4a72edf222e98d967d6fd7f76794acfd97aa091065", + "0xb2127049907d2a3b654d1c940b740bfba3dbaf660f86ea79c2f909af7c9fe2a07a1caeb1be12370aeffaf8faa50f1582", + "0x8a207701214bb28e99b0784e9228b1c34afa701966267fe7110f6f29f5bb41eaae6cdb98844d0400787978fabd224de8", + "0x9925147a383b6f5f814520220ffdbf20b214225882c3ef49b1a1ca677709176ec82466fb9c4be2dfbe5640afb63b014a", + "0x8416ad93871623fb555b5390b80de99edaaf317350cc0c1ae9d54d59517074d40061f315cce8ba2026d9c1e6f6a1009f", + "0xa315f943deebbf0a2cdbcf3f8323e215a406e9cbfbcc3f6288714cb3a6befb1bf71b2a21ff7a2ec4731c65044c45b6b5", + "0x8213e0c2539c24efd186ffa8b6dd401ad2233bc19166a0623b26dd1e93614bbf792823f5599ac116231e2efde9885709", + "0x8e5cafd2f34a127a4a896f05e4d929eef06972a1826b3566446942198df26d62f7679b987db2b3765d9d8058b1cd85c2", + "0xb5302b399c9cdf912fd59007ad4737255552663b1e56dbe64a7b2ddd88d2093c73ea319b45db2dd49d1e03f5bef1a0ae", + "0xa0c2bcfbed4b008e1a56e5d2f2419aa59d7dd0ebd990f1c18588de702ad0fa79f445d69965fa9381e700eda13b309378", + "0x80a44eea1ffe24c26b16b8e2e70ee519258b9ad4b3e83cc4e5cca88ebc48d0160066f8b91d0581095b0de2428390c8b3", + "0x84a90cb9c7d2f799f1c4ed060387a4b793ab41c5c3eaffd3b60face9b9c3bae93cd2017283bf3de1e3dac63d0d84dd42", + "0x81d22febca276a05ba9bbc5591ee087b0491beb35b4d9f8fc0d041d642a574667ddc57660b20f5c568f7d61fdcb41bda", + "0xa3ac965ac27a28e102a439b74fbfc157e75fd57620e4c0750a466165f8aeecb2191dcf8e656f7525aa50d9c7c69b0b5c", + "0x913c17434ff0d9fc52e2ece4fec71b37d4474a18f3ea26925c1be2b250434d49759f58033ba0fce1c6862c6197930dc4", + "0xac430559c151a5e461f67b49c7786c97e1653fa8698e9759ddbdd99f5daf17fc5a012ae6330739440880728f24eba7c9", + "0xb10d8e9f8aed9361b042d1398ec74364f7c7c1cc5c7f917060572761138bdbe89bf409389ee3879f93bc8032dd67b308", + "0x937271005a4cc6a6ec134870c1b56471aa84ed4f4af1b3d5f334bc0c42762fae0c9a6a2828d3de6151a76dad7b72781c", + "0xa10e4dcf51889f69e6bd4c052f8d4036b9571ced98a3d7d779cbcb9fa5c3a82228566ea7cc1d012bf56dea0a40c5a64c", + "0xa0ed026528d9a8bb3201bc9dcd20598933e8c72fd315deea8da63d06e97392aa729d98a55a8a60fa4d5573513ba5c9fe", + "0xb723fcd04cddbd4c36feae827a03746ffef251c4f4c55a88beedaeeee194430a99f566f483668a0d88b13e7a4a37f1de", + "0x84a2cdceed44828c7c05a6a762edec0165e434e7029df617d6646aba48776e6c3b823f40689cee136536f8c93e08a629", + "0xb786264e3a237ac3a1d56c9f4e87438dfed620c867100fd38b01287f5b755c7820937403bfb86644e082094d3e410a00", + "0x92cc35b2065fca157c7bba54410f8bd85907a01c9f760aa0ddb7a82cb55811d24cb4dc6b725367a6a1c293b809a48ead", + "0xa12bbf22b117f00164a42515bc57cc9e6c43cc77fb737ee3d0c0cad94cb50cd3847d61cab469cf8ca76f7958bdcfc771", + "0x85985b00de533bde2a757eddf53be79ea39091d16af3fc92327bcd1cd59bf2bf4411a334da29ad775e8ffaf3cea7d7b8", + "0xaf9eb24185b0d330d0ea1d0b0fa78af0dcf42ced81cb0128f16cafdea687a9c5582bb6d7c5744117b271cd0b3303f0b5", + "0x8c8aaa1d85ed6327f85d579767c7a9158d209171b3efcb3e8a9d9e534c078e821b6aade255101d2c9ef6d67ba66f10be", + "0xa450518a03ffb40e1df89e0f88fd55b5b06f4872cdfb7ec55f40dc40d9424b3b289866336c195bdd54597d95569e0096", + "0x81e61cc69f93c435bd77f155e80626a9c764dd92b6c76af15c41346527948d8a6ca87d6351a0fe7987e2ee3aa66a9625", + "0xb615e0cebf4fdff4cb23a20c8389c370915ba26aa703b28efe4ab070b1603d1c5b6541684acf46b52a915f6aee447539", + "0xa7f51885c7a71885cc84ef734ecd107e8bf5f7a25131415f671d143cc1de92859e65001125323c7985799993af6c410d", + "0xabfbf7a46f32066989c32f774edcc68163f085ca81e94fe8c9fb32f8d451bbb2c20ac45cd8d97f9e618ab40186933b1a", + "0x8cf35a522b5cac1934004aa9dd236bc77198d43272888afa860cfc79b4b28dabf7a3c74098f84510897566fdd609aa45", + "0x86aa927df78f7a06a4985eb0a4f0b93529cef14f9fd2812d46abffbf25e618ead14d99c70e3c3bb2e17f3f7fabc9c264", + "0x860f1b4f4a398e9a8bb4739587cf96979cfbbe1687b7e91e5bd1198db726391b09b1a261bf12e96698818f60b5bd3537", + "0x8e7c4ee19ff115881051e8637dce1f5d6c65e865d0c757e8ce41b6d7bcd86c7070cce60649692bbf28c868c7e2e1e2f4", + "0xacf7ba01b0220419f09169ac8d16e5cc13dce08e88c90b8fdfaa33aab417f011a20b79a178d8a9f7211589d2e0affd7d", + "0xb404bde8e715aefbb9f20a353b911b79173ef3e2cf0aba98b5ae6190b90597d65043b0b4e014ad9ea6c77da2d213ea12", + "0x97e3615d1c77a402253bb55da2d1cdf82de316cefffe42b1022c94b4818d6dc4a313731db85321c537914bdf716a875c", + "0x940e950b96a4096a578c6874d747515936652b9b113a5f27f5a834a610867b05f9881e2679b0b289b8527baa0009b6dd", + "0x8de15a13ca236a3a285ce6e6826c502ae7365bbe468b6e8ac67b15b0bb49be0e996f1eec81ef69e4b7f54f8e4779a054", + "0xa12244777eacb08ecd42b5676b3a51153022ab97e9353ace0f47c6054c22de9ba60d2a60f59a36841c2a791cb1b7c288", + "0x94f7580203e39a2642ee2e7c969b9911f011d7f3a90c398e1302d26edb3df03df1d0c43baa1c6cf90dde95296d49e742", + "0x82ead33144aaecab965faf63af384565992f38fc1066e71e33d53f43ac93892e27fe78c4eaca1cccbc53364e26ff31e9", + "0xa0c129e9706d354249a7f8aa664ccd7ede89aa1445c5547410814b56d10dc086720953363ab1da8ff5f1ed5d8e575104", + "0x93b3057bf3f74edc95237781ae012cc4b1d3fd0455565ceaac7110290aa518ac32478ba4eb9851555fa87270fcc84f1f", + "0x949c2fd0b94f31f7cbf00c679bd3f6ec1a2f4056654708d39edf1a450b4e19a6e251d0bb24eb765087e698f61d3fca2c", + "0x99fd2e50e211ccb66b895eb2fc42f260f3ad5767f04c2fe238b81dae98aa6e3977443a51f4fe7b43f499caabe45699a5", + "0x84fe19626503218f327b5325bfd7c0c3d2614b47d34964aa0259d564e769c6c81502132cc1765b0b31fbe39852706927", + "0xb43287ec29d9010bec4284de58fed48dd1e129bac79f09d45153c9949131782f77b11b0c9f8ee06a39e5e9bbaa8e2c6d", + "0x908902f3ed45482df2f94415fc8e5a308057a40c8905d7cbbd58ec4848e19276577b7f7e69e5e684a8b981738e10f7ef", + "0x85cc7d9c1eae372b4f88758cd6e21604b4bc9f0794e1e74b6d9de96347f81944d01331385fae7a38e5f6096c1dc23465", + "0xaf60288c702082fc258b3dbd6952c6b75c1641a623905f491b1e72f49b9d39b33d150a336450abd3911a4c128166acdf", + "0xa7d8ac7e589558c4014369ab6f4c1f2196205b03e4278152ec0dbbd7ba54e803c3369a71d364a773aac8dbbd117e4a13", + "0x9833aed34e48c206e9328073597aee1123f5bec085339b4e6839a389a429bf3042798a31fac1464ce963204adface76b", + "0x84631a4f012bbb62133030224b57deb32dcf464cacc8ffde7775adbe68707263ab5527a1c75e597e03aa703ba658b889", + "0xa686a61f6467858a2a4c13e70ad81b1901290d3e51bbc0c6e366f9e652f575e91b11c75f640ccef8b0c6c1b05a43c9a0", + "0xb585f0ffd5144907703b41539bfad7f9f058f5985f63db911064ba6b07af8da2796b84b16db42b8d11135c3f846cd9e2", + "0xb525539516c7bb25f1d7e165f269dc8c9eedbba74df44887e178ab8fd798e2a31f39812ca922d6b64d91564f14012a64", + "0x91e480d7568fd2fae39c35b0a8d623e66a3160fee1dd4e9097255004938b11ac1cd3918dc6a1e5fbcb700c95a547e5e8", + "0x936ef55c69b842b6177de71fa48dc5442bf5132116b214302f8f242ca36a273a6bbfbfaf373777104dadbe8e7da5e970", + "0x8e950c0f6688abdff8a3b8bd77be6da6f2565c7b55711f5860ea62a3ab1d51aac31821c602bc11a45e33c69e7dde3ea4", + "0x90eed4595104a0527f8db1e028ff622ff70db4eae99cf47f6c2a0246ec7b103570a6a9a877e32e9647cc74969006743d", + "0xb756344f6c4ea05b792e416d9bd9ce9dd4bd904e7622761f28a85628506bfc9d88a25e5f04db62fad30a92fb1d8d8556", + "0xad79ba76534c1a02ac3e9b7308d390792984cd75b7e1d0e5e4ff123642d99d4ea1825643091aa8117336333c40d5bd94", + "0x832b08144887de0c0341d84f6945450af8d7a4eb32367d7703118186c1be525df9382ce61fed5f3b65a0bb3449185f7f", + "0xa322fb944e46d8e47994820890c94af423674716da810ea1da71e0a7733ad72c22114ca39a4b59c98ce4291a5684c154", + "0xb982851a65140dbea79bd3b5487e236feccee051deddcc17c2853032efca289ddb6eaf64be3dd85a73012fdbe9d2d4f3", + "0x8eed5e230e201830b44b9fadca4e156fe1a16bf840cf29da0f381ea0587b20c226de2465c67e6268973e776809af68e1", + "0x81c8f1c04490f36e41a53ee1b5185cb8adbb37c258fd6c3be8c56835bf574c37183a94d55b6554fca35d6e6dd9af0133", + "0x8c4928724107cc16d36f2976677eac0b852fc4c3c0bb2f9cd4d59cd24a113faf33b2faf405c3fcce25be51d41e42c2c4", + "0x8e4ba842636fdfc4d71f0983538ea5037d420acd26abd12efca48c252eea85544b2fa9fccdfec4e7c2a6359baffa112d", + "0xb4315b84700e26dec26f3488d308430fdff4809c10d4c24309627911cbb769ffaad0d1ecccd622dd02194eaf5ba59f91", + "0xab888308f757faef32648c1db01650dbc9aea248b09d06e6efcc996d395f48ec96f2d54a02de441d753fe8737862d991", + "0x805094cfd77e207d5c75f3cad99f41f763ec15443052cfd758c6a82ba422d831a1103a7f9b100da49c28198279c3d3dc", + "0xad857f33243e4a2cd2a773700def21fc7f94939d1a6d2c2125ecd58fc206ccafb07a2c02a1cfce19857d3654aca2c70c", + "0xa4d12d40149953daa70b89a329e918e9d93efb4e8004a9357fe76682dab9662c8507e16db83e849340f05cdb4933a373", + "0xa0dbac2ed4b5d03606524245e8a31080eb5bd3e9a0c51dad88c3b18e3e6bc5d64953a81c8e60425b80107ee6b62b1fb4", + "0x86da05355900f327164a78901f6e3db857531b33b1e855df1a67a9ba222c6b05fdb6b0ffbacaeb1ba5b45ff8979b6b68", + "0x932c9873aa3e226dd922b5a616c75153bd0390ce8f332a414b9c8cb6606c2501a37a2aa88097bc7d8e2c4261706eb38c", + "0xaccd9cdf07ccdd42033ce3b105e00bfd39e2304b1e3d66f8b1128645634452c20f759ec45adcef2fdf04408f62c4cc04", + "0xb75cfdfc1cb48918752eab17eb579820ee6e71e6667abdb64df834ffc8c1362fbbc23ca2c80dee248fe1fbb72d87dfc8", + "0x88b998c73b00638fde7d3dd650a08c5ab996dac6ac34251337fbff3fb5ae4a25dd20c1a16c987ad7ded19eca23cea891", + "0x8afef0956c942571a27f504553fb312cca9e50ce41b44e0466d0516c5abe4d8acf4594cdb03b1ccdbe3f2e6a9093b713", + "0x9042cd83c5ff261e9ebda26398caa16cac2cb840d19062fa8ae50e044c27104972948318f4c866dc4d578798272d3e49", + "0xad536719a64570a2cd1d72b6590ea1d02c8c49f259a7867be26c8191445165954bcfad50ea12688ace3fdfb0e98143bd", + "0x97c86328d63d297b6bc9718dc1ad5a05b908a750d1c455c700d84315589128ce4eea958aef2bcf0fcf4adbd8e3ce58d1", + "0x8e592cf0802e6a9541eeb654dc55055e11f3d757847285197132935ca35bbb1a9156829a39384dfa6f645ff89eb36738", + "0xac16c614998944f77590bf3913a010e13f2d3bbf6a172293baf5983506c1a2d89989fb72e598f5bba1ea10a691377c93", + "0xab8e6f5b46baa6632de3621497bcbdd584decb999fe7d8a3364843a1e0b76497600630b6a24dd30119d8bcbfca29f335", + "0xabe1d3af5279e60122d9cea8cc6581c819d7a0e20e3715da0f6da7e02d13a7653db643bd946e2fa9ba338eca81fbe140", + "0x8c33bd831ecfb18d1d0713e16beba768e9c42df62170c1f8a16764912be77f2ac5915623d1d25e8c462aa9c2f6669ca4", + "0x903692becae4a6409f7bdb127d9b11de57a5739fe24218dcbaa0092648d5332dfeef29a908ee9e43e5e0a51a4c3639bc", + "0x92591e90347ae286acd365eba32cd9ad8f20f4c9cad2dc579b195147ff290adf0d776bcb3d4b04a25d68a941fc0c781b", + "0xb64bbccf860299aec16e1f95c768a1f337c740bde612e6ba260e393edb8b04540127194761c42597abb9bcb771c576c3", + "0x9194f056ccfdfeb78a11c5347e2255d7a7ebd1251f9aebc0b58feb68d3e03a7dbbb74e3ef7309455853adfb4694bd01a", + "0xaa4f15f6d6a53ae65b7f6f91e8981d07a5919d2138679a561f7bb608dc4596e45ca06c9441d51fb678b2ad89ae7a17ae", + "0x90e3d18507beb30bde08c5001faf489a19ab545c177efb3f73fbf5605f9a0abcdc8bfbc44f832d6028e3e0a834bea98f", + "0x8f31dc0118c8c88a6e79e502d10e57652b7aba8409a5bf572ca63fed6b7cbad7f28bbc92ac2264f649792fc1d0715085", + "0xa307d1067ea4c56437b6f8913aa8fcbf4a24580fc1e3336e7f6518f0f3adb9c4733090e459a3f737414ec0048179c30a", + "0xb7cc41fdf89595cd81a821669be712cd75f3a6c7a18f95da7d7a73de4f51bb0b44771c1f7cd3cd949e6f711313308716", + "0xa9dc74e197fe60e8c0db06b18f8fe536381946edecdf31e9bd90e1ebfcad7f361544884e2fe83c23b5632912ec284faf", + "0x8b3e1e81326d611567e26ed29108f33ddb838c45bbd1355b3ae7e5d463612af64b63fff9fa8e6f2c14c8806021a5a080", + "0x92f6537bca12778866335acc1eb4c3dfc2c8e7e5cf03399743dcea46aa66cac92ac2963b0892784263ad0ebe26ffdbf6", + "0xb5cc0061f7a3e41513199c7dd91ac60d727366482a4c7328527f7bd4fc3509412f711bb722b4413b3736a219b843d15d", + "0xb3e9711d68d2c6f6e2cc27e385d5f603d9a1c9a96edeefa1ffdf390439954d19504d6aadc566b47e229ad4940ef020d2", + "0xa09d0d3f0e5dc73a4a0827b72710b514bbfce4a7fcd5141d498a5aad6c38071077f50d3f91af897d9ab677b7041dedda", + "0xb177fe260f3b86e9ac21f1bfbe2682ae5dd8c9aecebb84f37054bdab6e39094e611ce582210ceeddde66adf759dadb6d", + "0xb0ac6595eba9f5dc4b2fd21856267cfbcfb5b12aa34ec69ca32b80071c5b652e85c25a224d80443d503bf25fbbfe07e9", + "0x81f3c0e11b196bd4a2e8f07f8c037002566dc9037da81f3988add458a520c24dd1be3d43d851e28c0c6a85de4b57a542", + "0xa44308c95615f7fedb2d2127012924468c015df9f48359cc2e36ab4223870b0bfc1e9040baabefdf5266f93afaad896b", + "0x8493ec4c32d5a13b81039f1b436eb83f259945dc950e3c6c2ccf5087ec56dd2f60890ed4edf01728b6a54950e19b35c6", + "0xa1a439ec2a6a95bdac9aaa925ff337ba956c0d236ab5318354270e73ed6b73b4ae2d27b4c1686cf97b6526d04e65be81", + "0xb4659b7b53c55a4b2bbe210b53520b392f893500e18990d843b72d7379d45fb44dd1dd2184348d6fd853d6b9ecc6b7c6", + "0xafb2c68d75d00130b0e1b4f250001920213121791698ec04262db714cf7b1408d39f6cc10421f954845aad5b8250b77e", + "0xb22b843b40a97210f94043b552f348f66743055a3f274856a738e7d90a625b80e9bbb80cbbb450e1666eb56b8bd5c60f", + "0x800895ced82fe13d5fff65a93b0051c3df698bf1221b682accfdb63e3970f669ca37025750697f4e8ff2a3322ad57be4", + "0xb21f598c50d7b9f4a584d548f85e42055ef8e24991906d973749090261584c7f4f5e984b528926f7e75375dd84d51af8", + "0x849b1c68192d18274598dd6d0bf48fb5ee3b1ba25b331cff2d06f345bef3bed49760ca5690848cf33388f6a9a32cd646", + "0xaeb6fd9478b10ef456f6bbb1e6dd19b14475e65497772d12cfc097948383d3fbd191bf95f046b8bf1989954118e483d0", + "0xb1b5e0ea2835f7fc8b66e7731e392b43d16cbce04b52906b6751ab1b91978899db5fecbdabc23a19dabb253005468136", + "0x91b6b1284770cf6f7ef35bc0b872b76c7763ffcfa68f9c8cfabcb2f264a66d47598bb9293f6a40f4c3dd33c265f45176", + "0xb9ffed029846487c2cfb8a4bb61782bd8a878f3afdb73c377a0ebe63139fa070e3fcdc583eec3a53fdc5a421ff1fa877", + "0x998007249d041b0b40ff546131cfc86d0b3598dcedf9a8778a223f7ed68ba4833b97324cbb1de91292b8ff51beab44b3", + "0x8eb77ce9e0e406bf6f002870fb2fd1447646dd240df9bd485f8e0869298a1fc799d8a41b130c04370e9a9cc5c7540ca5", + "0x853db8157462c46f2af7e8f94f2ed1c9b9a7ba2896b4973296898ff3d523d6e29e0b63a5d26cecd5e490b33c87a4cecf", + "0xb1436b6f3278768f0979ee852944258f2599977d255bea6fc912ba17c5dff5bdc850cf3e1fc52be9d6d188e868670f4f", + "0xa76acbc5832019b3b35667ab027feff49f01199a80016620f5c463dfcbfb51bf276ed17b7b683158ba450660cc7973eb", + "0x94540cdb051faf3ae8b8c52662868c2dab66bd02505c4f5f8eb4d6b2e2e5fd9a610890c5dcf8fd887eee796d2b5753a8", + "0xaa35099666bceccf4eb3b65b13bba88e30a8be93693ab6761d8e5523343e8d6dd42d977e66499352fe4e9e9784a1dd0d", + "0x894471aad17be54319083c4b5e40adcfacf7c36c4aab0b671030b7ef321c53590a25eccd836efd20f32a93185fd315bb", + "0x8f52a9f705bb0dea958fcfbd52e2b6c08ad0f89a07a6b2942c1b4c37eead0d97a38a9e9aeb08d5d59b7fa2a9347f738b", + "0x9031c16b4f936c9cab55585dc5064739f696c3347ee2c0792320c9f749e760d120e396e8485ffc79d81c9f3337ad3d1c", + "0x82090a0d0d9b05459ec1c328ecd4707c333b784e3aaa0ef0072cee1eac83f9a653a75d83b9f63512a8c41200494826b4", + "0x92c3a9553001f9ea4d67236b8ad1a33275378202cc1babc03f313895458f4b2549bfbbbdd37bfb8fbff0decb6b9f820a", + "0x88651868f4da37338a22bc553388df5dd1dd0cb78c4d7d07c637d8f6faef4bed72476fdcd4304d5bedf3514011135f08", + "0x83fa0141bfebd88063f1d787719721b4c6b19ecf565b866de9d7d5d1a890e0e3d859b364bb65f8f8e688654456a40263", + "0x90a7fab753e5d56dfc0e53a6b4e6ab14508220f3a62b3f3f30570c4c9ad225e74122635826c92e8e3227ec45e551432a", + "0x8fa375b0345bf6e5e062d108f9feaec91029345ecac67ccf1264eac77b8654cbfdda1f10579f481889c0e210254eadde", + "0xb83f06116da9daebdb013b26724523f077debaf6bc618b48a7a68858a98d275f7899c4ec73a0a827219b9248dd81c8c9", + "0x8be1cada55e0c5ebb4fd460b2d209ae5326285a20c8bdd54ed9d1a87302f4063c8730bfda52d9d40e0d6fe43a0628465", + "0xa68ad6f813743ec13a811f2ef3982c82d9d9ac1f7733936aa1e122f8dc7f4a305cc221579ab8fc170c3f123a1576f9ab", + "0x8878f1128214fdbbb8a0edd85223741e021508ab6d36c50d38680f2951ee713ea056ed03f62b9461897963d50ceefe0b", + "0xacc0d43d1b0260528b7425b260a5dea445b232b37240759fc65fe26f7c9d8e51569c5722bc33e94de6492f4ba1783504", + "0xad80b1dd717b076910ee5ceabcb762e75e4d094dc83b93b65c16de1f75bc712cef223c05d5579c1561829406c07a97d9", + "0xa6fc9803f9c09d95fc326cc284f42ea5566255eb215dba8a9afb0be155ea11bcc55938b2d16f01cd2f2eda218c715efb", + "0x83ad733dbdfbaae8095a403dbf09130513f4ed4f08dcf8dd76ce83d1ea72999b7eea3a7b731da0d2bc80a83c6ee0e3e0", + "0x8748912fbd08cb34a85416b0937d9c4327e9eed20d6e30aeb024a7253f14f1e0d774f3326e54738d71aae080e28da0fe", + "0x8997e78d8acf23051428af67183ae9b2c4aa42b503745ffe33df35a35103c589987e1473ab14dcd28ee78ebcb10d8e95", + "0xa2f340502a7eb3c4a36412e6f028321372c4fa18a4743945607424e932af1271fa3e6598a162c872072529576eba6283", + "0x868ccf19b5044ab93b45c9ed3ae34fcb504fe1453d6c4a1d12c325032cf01eb90356de82080ed897e97dba13cae33a02", + "0xac8867005fe4354d67aa37b866a7e581d2f94f7bd0b9f4efb5c2d1370ec13147a60692051b02fd00ae60b512bce9b1ff", + "0x8fd01886b046819c83c12bb779e432b25ba13713f9227be702074ec3abb2bba6be37220a0a26a4bd4171b99b14e32bc4", + "0xa128981ed199f92b5959975c150a93a62fec50b61c80a3fa0634d90fc8058f76f5cbee77aae6889af12d296b30e613cd", + "0x81fe618552ff7a36c9235c6d4066cf2f930b5b38de4089e18166e4a06ca5723eadd1976d25e34b74b3ce942300b23e5b", + "0xab1223ea049e6e0fbf9b611de7fd7c15e5e9637cbd73aa0e36aea08a7503ba6804f2aa807186fdc9aa7f4f9195f72e24", + "0xb97285286981b2665f898abc13f3243b63005bef8db4cab3f658bf6167036b61af400f08db0fc3c640a9c623b760690d", + "0xae3ddff7c1f0fbb6a13dbbc667a61e863c2c7c51c2051e33cd61620142e7e30a7e0c4c1f8fbb512aa3a8640267c6ac26", + "0x99c2a89d5bef236060e51c4f952664094c20fbfca647e5d24a55c1fb8df2f3df58244fbbf3635db07b1c29ee3234fa6f", + "0xa5010764d4b9cd3b410638334d1f70c5f4843f45b4f4a9316aaea5fbb2c510a97449dd7a07b49f47334a69d37d9955d3", + "0x86706d011dcdc9e9d165d01fea1df68dd74bedaf15a39f92893c030cafe96f4498c4c1fec2d2136354341b3f440a1462", + "0x88fd57eb62bd7dc35722f3a0576c2138403a2f663a2603482e8974a895cf56ddbb02657dc6b89eb2cf5c1f9d1aff6426", + "0xb0dfd4c68e3acb6bb8a776adaa421fc5e268ed4d5964bb90a727091e5113b55b3f9c6d33cedb3ee47ff7acc5df8b1749", + "0x93b92bc942e1a636fc5c2dc1840de5faf158a113d640d5a475b48e2c56ccccaf9db0e37e90ce74c4b3f5c9ac3b2eb523", + "0xb29a16fa1ea95cbfc1873c435ad40dc8495ba6341801b72bd95d908147dcffb1b4bb426dd635f3af4c88984f56594dd8", + "0xb8f367105e1a2d554ac30200c66aeb579d3d30a8953d20fb6ebba2d876ec39c52ea5d654f1bb89b8ddf3d9d651f31cdf", + "0xb5fbc228c983d08adf8612eba5b3db3acff604439226f86aa133b02cce4ffde2f977c8dbb8b446b4375673f71634c89d", + "0xa399bea37d3056e0559f6644faa0af93063b4b545d504d7e228d3dbbc294af83d3c4cf37fe026b63899b4e7d50fd08f5", + "0x928ef411a36414b24aea26fdbed4bdb1bb6bdc2d967e2553ce54c7c4e077e76869cea590257645c9129dd55ce025295c", + "0x9684a4adeed416a9ce82ad79b55c4a3adcfbd43950bc442ed8a340381caedb70f4baaaf821e3a152f483f965d8f56162", + "0x92558a37f214d6f4cb6d72cd2f4ad24dff9d17611b9e4a41ee5c741a5d1ca9e4053b0584533ef4da206110b5dc3e2a35", + "0x973bf0724d1785cc5e85d2a8ee8c354ad4cf557217ced0b7940f6f064024c20b2bfc5b144c820b5083da4bf70690de4d", + "0xadaf1389dfa528210ca9c2657c5ff10d51f7e3b18e93a59c37211be0506c3576cb2c04ec80cd0f82605e53c5a3556620", + "0x85b58b223b09fda6f3ab674d75e780c49eb2167837243df049281e8f4fed653811138b398db9cdfe7405fdb8485602fe", + "0x849504d3db408d80745a07e850b0a804607b91a59922a5d3bc40da2748c029c029419cda38d2a4485cc0824c6b2504f0", + "0xa3f4afcb353bc2582a02be758ebf0cd18752410ca2e64231176bfa23828423e0a450a65f241a9ed8eab36cae8d9c567b", + "0xae362786cdf121206537af9590d330abbc6dc328b53cdd145dbed0e5df1364c816aae757c4c81f9d619e3698dd32bcdf", + "0x9024cfa5b0101eb02ab97866d5a3832944e5aa6888484cfba3d856576b920787b364fba5956bd7c68a305afedc958201", + "0x8a116df09fed923acefb2aecf38a4fbc4b973ee964d67f03791d70bee6356af43ffca117d4e9463ffaf0e0d5d5e5a69f", + "0x9163016175c73f1bbc912ddfe03bd4e1db19c64951c8909ee6befe71a1249d838e0db49f03670bb4c5c9b2ab0fb4fef3", + "0x8f6357318d8d16e7240a02b05ce5a4976b6079d49daa258789c6dbf4a47950ebe9de6411780fab06c7c1f35651433380", + "0x8e63cbae8be7341892dbedee3111adf0307c4ee9e375181aa53478f5ba9cdce164d6ae890e5f480119a3a51c6e989165", + "0xa9782f30674a4874d91bfba7eda63aeb5dbe66b040c768d6a925d8ee135f0655ea56276b105239cc0668fc91ddb68cd1", + "0x8d9d94b61ab84ec08665cbe0244ea41756785df019e453ef078c19380bd44c39d2958e8465c72eacf41eed5696037805", + "0xb1470e6f5d2e314474937cb5a3bc30c8bf5fc3f79014945f6ee895fe20028ffc272f9d3a7320aac93e36c96d8a5454e3", + "0xa444911bbafc71179766594f3606b6eaff041826607fd3192f62dec05cd0f01b78598609a530f6930e8440db66f76713", + "0xa9823d44e2638fca7bcc8796cc91c3eb17f46ad6db9f7f6510e093727614aa3a4f9b2c4011ef91dc1c2d224d08d8d05b", + "0xab86020972c359ab98294212558b4b14862040139876c67fc494184b5c9bcea1dbe32fe0c8dd9e60be9daa304acd599a", + "0xb7e5cb685bbdcfdb1e48259a5d68d047846c8a35c5b3f90172fb183d1df40d22eaf0edaca2761a07c29c577000ccfed0", + "0x8c88319dae4b28989817e79e6667fd891181e8d2ed91b9c6b614985bca14b12982462ec58b17be0463c24bbb79dd62a1", + "0x8c1c6867e7107fb2178157c991b9c8b0f90c8d57a51220bf3650438ccabccf62da4db8a9916491e730ff3d0c106496e3", + "0xa00a79bd58da6528b9af033087260f9f3d00519eafb4746b355204ee994e89481591b508eaa5402821083e250d38467b", + "0x8785abd7c37690f6aa870ee5c799eef72e398a7898b6767f698515be277b9c2fc1af12ea89b0620a848221343a3b5ec3", + "0x8aadae68543db65cef71d0e230a09508d72061398ef2fabec0f856aacff2125b79c70e620744aaf331faf3dfc8afb9bc", + "0x8ff0cd437fcad9630b8a2333176a55e178db4142ec841581590594d74d5b53baeac5fb903fdf7bcf83e245b95b58285e", + "0xaf274e8fad6b190be4e5dc92d2705ba6ac0d7e1ea29e958a5cdd4cb764de46a56d9eef62c999a16e7c50a50b2d9fe3a8", + "0x865e6ec7d1aa848786d6a7a4e87a24d442311f0810b01ef5a74928ab59fdfd651e48880b49680047e5b0df6b3c7c2ecc", + "0x800706baaeb35bf3bc33bdea9a8b5cb00d82df407b3b7e1b781a9359cf44fb410ed311591080181b768aae223d9246aa", + "0xa9496389d0780b309c6998374ae159f58a8d0fe9a1c24c36cebcb45b27d818e653b51a8ee1f01e30a9b2c46a548126ef", + "0xb5fccf4fc3186661939fbee2e89c2aa0e3a6ad4907bcc98c7750520540c4c183b1bbfcdf47f2f1c5e75c3a30cdf30c75", + "0xa90028e39081b736e628c2230cc1338f9210ed01309a40fdf08d39c10cced2cdf71271013bea6dba3a0444fe47963106", + "0xa0815cbb325a8fecf2e1bcc5046644be32d43a8001bd5d8cf0022e4572cd0d481b3e717002f7ab21e16da5f5d16886d6", + "0xb2024787fcda52abc4138150f15e81f4a5be442929b1651ddccbfd558029912be4d61c3c9b467605fff640edf7392494", + "0xab5aa60032304a584cc9245a33f528eae7157808dedd1ad83ebae00aadc25dbe1cd5917eb8b6b2c800df15e67bdd4c4d", + "0x866643847ef512c5119f2f6e4e3b8d3f4abb885f530bb16fcef0edb698a5b0768905e51536283925b6795a5e68b60ddc", + "0x806aa99c9a46ee11cc3ebf0db2344b7515db8c45b09a46a85f8b2082940a6f7263f3c9b12214116c88310e706f8e973a", + "0xa6eada8b9ff3cd010f3174f3d894eb8bb19efdbff4c6d88976514a5b9968b0f1827d8ac4fe510fb0ba92b64583734a1e", + "0x98480db817c3abbc8b7baedf9bf5674ec4afcfd0cd0fd670363510a426dad1bcf1b1cb3bf0f1860e54530deb99460291", + "0x81ab480187af4a3dfbc87be29eca39b342a7e8e1d1df3fc61985e0e43d8d116b8eac2f1021bde4ae4e5e3606c1b67a21", + "0x8a37df12dc997bf9b800f8fd581a614a1d5e32b843f067d63d1ca7fde2e229d24413d3a8308ec1e8389bf88154adb517", + "0xb045a55ca0bb505bd5e8fcc4cfdd5e9af1a7d5fe7a797c7ede3f0b09712b37f493d3fcf6ef0e759d7e0157db1f583c95", + "0xad502e53a50691238323642e1d8b519b3c2c2f0fd6a0dd29de231f453be730cf1adc672887d97df42af0a300f7631087", + "0x80597648f10c6d8fcd7421caf4e7f126179633078a1724817d2adc41b783723f302eabc947a7ba7767166dacf4ce8fa1", + "0xaefb56427966c81081999dffbe89f8a0c402041929cd4e83d6612866cfbb97744f4ab802578349fbecc641fa9955e81b", + "0xa340e493fb3fb604eab864d4b18a6e40ba657003f1f88787e88e48b995da3d0ab4926ce438bdc8d100a41912a47dace0", + "0xa6d777bfc0895eac541a092e14499ff8bf7156689d916a678b50a1460583b38e68158984bea113a0a8e970d8a6799a85", + "0x90ce469410f0e8cfff40472817eb445770833cdcf2895a69bc32bcf959854d41712599ceb2b0422008d7300b05e62e02", + "0x815c51be91d8516d5adc2fd61b6600957ed07cf5fdc809aa652b059bea8ed179638a19077a3f040334032f0e7900ac8b", + "0xb3ec6c0c3c007c49c6b7f7fc2ffd3d3a41cdff5ad3ac40831f53bfc0c799ffeed5f440a27acc5f64432e847cc17dd82e", + "0x823637abeab5fb19e4810b045254558d98828126e9a2d5895a34b9e4b4f49ab0a5b3ee2422f1f378995ea05df5516057", + "0xac05412bcf46c254f6548d8107a63928bba19ab6889de5d331eb68cf4d8ce206055b83af4cb7c6c23b50188391e93f84", + "0x88514163c587068178302bc56e9a8b3ad2fa62afd405db92f2478bb730101358c99c0fe40020eeed818c4e251007de9c", + "0xb1e657d0f7772795b3f5a84317b889e8ded7a08ea5beb2ab437bebf56bcb508ae7215742819ed1e4ae3969995fe3b35d", + "0xa727d4f03027fe858656ca5c51240a65924915bd8bd7ffa3cfc8314a03594738234df717e78bb55a7add61a0a4501836", + "0xb601682830fc4d48ece2bdc9f1a1d5b9a2879c40c46135f00c2c3ae1187c821412f0f0cfbc83d4e144ddd7b702ca8e78", + "0xb5cfea436aa1f29c4446979272a8637cb277f282825674ddb3acac2c280662fb119e6b2bdd52c4b8dbf2c39b1d2070d6", + "0x85c211645ff746669f60aa314093703b9045966604c6aa75aae28422621b256c0c2be835b87e87a00d3f144e8ab7b5f0", + "0x867628d25bab4cb85d448fd50fdd117be1decdd57292e194a8baa0655978fae551912851660a1d5b9de7a2afbb88ef5c", + "0xa4e79c55d1b13c959ff93ddcf1747722c6312a7941a3b49f79006b3165334bab369e5469f1bddebadb12bfaff53806d5", + "0xac61f0973e84546487c5da7991209526c380e3731925b93228d93a93bce1283a3e0807152354f5fe7f3ea44fc447f8fe", + "0xa1aa676735a73a671a4e10de2078fd2725660052aa344ca2eb4d56ee0fd04552fe9873ee14a85b09c55708443182183a", + "0x8e2f13269f0a264ef2b772d24425bef5b9aa7ea5bbfbefbcc5fd2a5efd4927641c3d2374d0548439a9f6302d7e4ba149", + "0xb0aacdaf27548d4f9de6e1ec3ad80e196761e3fb07c440909524a83880d78c93465aea13040e99de0e60340e5a5503cd", + "0xa41b25ae64f66de4726013538411d0ac10fdb974420352f2adb6ce2dcad7b762fd7982c8062a9bac85cdfcc4b577fd18", + "0xb32d87d5d551f93a16ec983fd4ef9c0efcdae4f5e242ce558e77bcde8e472a0df666875af0aeec1a7c10daebebab76ea", + "0xb8515795775856e25899e487bf4e5c2b49e04b7fbe40cb3b5c25378bcccde11971da280e8b7ba44d72b8436e2066e20f", + "0x91769a608c9a32f39ca9d14d5451e10071de2fd6b0baec9a541c8fad22da75ed4946e7f8b081f79cc2a67bd2452066a9", + "0x87b1e6dbca2b9dbc8ce67fd2f54ffe96dfcce9609210a674a4cb47dd71a8d95a5a24191d87ba4effa4a84d7db51f9ba0", + "0xa95accf3dbcbf3798bab280cabe46e3e3688c5db29944dbe8f9bd8559d70352b0cfac023852adc67c73ce203cbb00a81", + "0xa835f8ce7a8aa772c3d7cfe35971c33fc36aa3333b8fae5225787533a1e4839a36c84c0949410bb6aace6d4085588b1e", + "0x8ef7faa2cf93889e7a291713ab39b3a20875576a34a8072a133fed01046f8093ace6b858463e1e8a7f923d57e4e1bc38", + "0x969ecd85643a16d937f148e15fb56c9550aefd68a638425de5058333e8c0f94b1df338eaab1bd683190bfde68460622b", + "0x8982f4c76b782b9b47a9c5aeb135278e5c991b1558e47b79328c4fae4b30b2b20c01204ff1afb62b7797879d9dee48e2", + "0xb5098b7ba813178ced68f873c8c223e23a3283d9f1a061c95b68f37310bca4b2934a3a725fff1de1341c79bb3ba6007e", + "0x97b160787009f7b9649ed63db9387d48a669e17b2aba8656792eb4f5685bb8e6386f275476b4dfbb1b4cb0c2a69bc752", + "0x88b69369c71daad6b84fa51a0f64a6962d8c77e555b13c035ad6fa1038e7190af455b1bd61ae328b65d6a14cf3d5f0d5", + "0xaf88b87801361f0de26bd2533554ee6f4d8067e3122b54161c313c52cc9eafea00661c5c43e2d533485d1f26da4e5510", + "0x98ab18e3bbcb23ac1e34439849e56009bb765ab2f2558ebfd0a57cbe742169f114bceb930533fb911b22cb5a8fe172bc", + "0x9027507f1725d81e5ac0f0854c89ab627df3020fe928cb8745f887bf3310086c58fca1119fd5cd18a7d3561c042d58de", + "0xa676583f8a26e6f8991a0791916ce785b596ce372812f5eb7b4243ba9367ea95c797170fdac5b0c5e6b7f6519cc2b026", + "0xb91b0ab32638aef3365035a41c6068e36d2303bfee8640565e16c9a56c21703270fd45946ce663238a72c053eb3f2230", + "0xaaf4cd1ac0a30906dcd2b66b37848c6cc443da511e0b0367fd792887fdaf1500551590440e61d837dbee9d24c9801108", + "0xa06f20a02d3cd76029baad5a12592f181738378a83a95e90470fa7cc82a5ae9d2ed824a20eeb1e96e6edc0619f298688", + "0xa465d379c3481b294efc3f2f940b651c45579607cf72d143b99705eae42103a0279eb3595966453130e18935265e35d6", + "0x892a8af7816a806295278027a956663ea1297118ede0f2a7e670483b81fb14dccacc7a652e12f160e531d806ca5f2861", + "0xb480917c0e8b6e00de11b4416a20af6c48a343450a32ee43224559d30e1fecdece52cc699493e1754c0571b84f6c02c2", + "0xb3182da84c81e5a52e22cebed985b0efc3056350ec59e8646e7fd984cdb32e6ac14e76609d0ffaca204a7a3c20e9f95d", + "0xa04ea6392f3b5a176fa797ddec3214946962b84a8f729ffbd01ca65767ff6237da8147fc9dc7dd88662ad0faefdb538c", + "0x95c0d10a9ba2b0eb1fd7aa60c743b6cf333bb7f3d7adedce055d6cd35b755d326bf9102afabb1634f209d8dacfd47f1a", + "0xa1a583d28b07601541fa666767f4f45c954431f8f3cc3f96380364c5044ff9f64114160e5002fb2bbc20812b8cbd36cb", + "0xa1a0708af5034545e8fcc771f41e14dff421eed08b4606f6d051f2d7799efd00d3a59a1b9a811fa4eddf5682e63102ea", + "0xab27c7f54096483dd85c866cfb347166abe179dc5ffaca0c29cf3bfe5166864c7fa5f954c919b3ba00bdbab38e03407d", + "0xac8c82271c8ca71125b380ed6c61b326c1cfe5664ccd7f52820e11f2bea334b6f60b1cf1d31599ed94d8218aa6fbf546", + "0xa015ea84237d6aa2adb677ce1ff8a137ef48b460afaca20ae826a53d7e731320ebdd9ee836de7d812178bec010dd6799", + "0x925418cda78a56c5b15d0f2dc66f720bda2885f15ffafb02ce9c9eed7167e68c04ad6ae5aa09c8c1c2f387aa39ad6d1b", + "0x87c00bba80a965b3742deacafb269ca94ead4eb57fdb3ed28e776b1d0989e1b1dba289019cfb1a0f849e58668a4f1552", + "0x948d492db131ca194f4e6f9ae1ea6ebc46ebbed5d11f1f305d3d90d6b4995b1218b9606d114f48282a15661a8a8051ca", + "0x8179617d64306417d6865add8b7be8452f1759721f97d737ef8a3c90da6551034049af781b6686b2ea99f87d376bce64", + "0x918e3da425b7c41e195ed7b726fa26b15a64299fe12a3c22f51a2a257e847611ac6cfcc99294317523fc491e1cbe60c4", + "0xa339682a37844d15ca37f753599d0a71eedfbbf7b241f231dd93e5d349c6f7130e0d0b97e6abd2d894f8b701da37cb11", + "0x8fc284f37bee79067f473bc8b6de4258930a21c28ac54aaf00b36f5ac28230474250f3aa6a703b6057f7fb79a203c2c1", + "0xa2c474e3a52a48cd1928e755f610fefa52d557eb67974d02287dbb935c4b9aab7227a325424fed65f8f6d556d8a46812", + "0x99b88390fa856aa1b8e615a53f19c83e083f9b50705d8a15922e7c3e8216f808a4cc80744ca12506b1661d31d8d962e4", + "0xa1cbd03e4d4f58fc4d48fa165d824b77838c224765f35d976d3107d44a6cf41e13f661f0e86f87589292721f4de703fb", + "0xb3a5dde8a40e55d8d5532beaa5f734ee8e91eafad3696df92399ae10793a8a10319b6dc53495edcc9b5cfd50a389a086", + "0x996e25e1df5c2203647b9a1744bd1b1811857f742aee0801508457a3575666fcc8fc0c047c2b4341d4b507008cd674c2", + "0x93e0a66039e74e324ee6c38809b3608507c492ef752202fff0b2c0e1261ca28f1790b3af4fdb236f0ed7e963e05c1ec0", + "0xb6084e5818d2d860ac1606d3858329fbad4708f79d51a6f072dc370a21fdb1e1b207b74bc265a8547658bfb6a9569bb3", + "0xa5336126a99c0ecfc890584b2a167922a26cae652dfc96a96ab2faf0bf9842f166b39ceaf396cd3d300d0ebb2e6e0ebf", + "0xb8b6f13ce9201decaba76d4eca9b9fa2e7445f9bc7dc9f82c262f49b15a40d45d5335819b71ff2ee40465da47d015c47", + "0xb45df257b40c68b7916b768092e91c72b37d3ed2a44b09bf23102a4f33348849026cb3f9fbb484adfea149e2d2a180ff", + "0xa50d38ee017e28021229c4bb7d83dd9cdad27ab3aa38980b2423b96aa3f7dc618e3b23895b0e1379ca20299ff1919bbf", + "0x97542cf600d34e4fdc07d074e8054e950708284ed99c96c7f15496937242365c66e323b0e09c49c9c38113096640a1b6", + "0x822d198629697dcd663be9c95ff1b39419eae2463fa7e6d996b2c009d746bedc8333be241850153d16c5276749c10b20", + "0x9217bc14974766ebdfbf6b434dd84b32b04658c8d8d3c31b5ff04199795d1cfad583782fd0c7438df865b81b2f116f9c", + "0x93477879fa28a89471a2c65ef6e253f30911da44260833dd51030b7a2130a923770ebd60b9120f551ab373f7d9ed80aa", + "0x87d89ff7373f795a3a798f03e58a0f0f0e7deab8db2802863fab84a7be64ae4dcf82ece18c4ddbefccd356262c2e8176", + "0xa3ba26bd31d3cc53ceeced422eb9a63c0383cde9476b5f1902b7fe2b19e0bbf420a2172ac5c8c24f1f5c466eecc615d4", + "0xa0fe061c76c90d84bd4353e52e1ef4b0561919769dbabe1679b08ef6c98dcfb6258f122bb440993d976c0ab38854386b", + "0xb3070aa470185cb574b3af6c94b4069068b89bb9f7ea7db0a668df0b5e6aabdfe784581f13f0cf35cd4c67726f139a8c", + "0x9365e4cdf25e116cbc4a55de89d609bba0eaf0df2a078e624765509f8f5a862e5da41b81883df086a0e5005ce1576223", + "0xa9036081945e3072fa3b5f022df698a8f78e62ab1e9559c88f9c54e00bc091a547467d5e2c7cbf6bc7396acb96dd2c46", + "0x8309890959fcc2a4b3d7232f9062ee51ece20c7e631a00ec151d6b4d5dfccf14c805ce5f9aa569d74fb13ae25f9a6bbe", + "0xb1dc43f07303634157f78e213c2fae99435661cc56a24be536ccbd345ef666798b3ac53c438209b47eb62b91d6fea90a", + "0x84eb451e0a74ef14a2c2266ff01bd33d9a91163c71f89d0a9c0b8edfcfe918fc549565509cd96eed5720a438ff55f7f2", + "0x9863b85a10db32c4317b19cc9245492b9389b318cf128d9bbc7ec80a694fcbbd3c0d3189a8cad00cc9290e67e5b361ee", + "0x8a150ee474ebe48bdfcac1b29e46ac90dcded8abbe4807a165214e66f780f424be367df5ef1e94b09acf4a00cd2e614d", + "0xa6677a373130b83e30849af12475e192f817ba4f3226529a9cca8baaefb8811db376e4a044b42bf1481268c249b1a66e", + "0xb969cbf444c1297aa50d1dfa0894de4565161cb1fc59ba03af9655c5bf94775006fe8659d3445b546538a22a43be6b93", + "0x8383167e5275e0707e391645dc9dea9e8a19640ecfa23387f7f6fcaddff5cde0b4090dfad7af3c36f8d5c7705568e8d8", + "0xa353ddbc6b6837773e49bb1e33a3e00ca2fb5f7e1dba3a004b0de75f94a4e90860d082a455968851ef050ae5904452e0", + "0xadeccf320d7d2831b495479b4db4aa0e25c5f3574f65a978c112e9981b2663f59de4c2fa88974fdcabb2eedb7adab452", + "0xafa0eacc9fdbe27fb5e640ecad7ecc785df0daf00fc1325af716af61786719dd7f2d9e085a71d8dc059e54fd68a41f24", + "0xa5b803a5bbe0ca77c8b95e1e7bacfd22feae9f053270a191b4fd9bca850ef21a2d4bd9bcd50ecfb971bb458ff2354840", + "0xb023c9c95613d9692a301ef33176b655ba11769a364b787f02b42ceb72338642655ea7a3a55a3eec6e1e3b652c3a179e", + "0x8fa616aa7196fc2402f23a19e54620d4cf4cf48e1adfb7ea1f3711c69705481ddcc4c97236d47a92e974984d124589e5", + "0xa49e11e30cb81cb7617935e8a30110b8d241b67df2d603e5acc66af53702cf1e9c3ef4a9b777be49a9f0f576c65dcc30", + "0x8df70b0f19381752fe327c81cce15192389e695586050f26344f56e451df2be0b1cdf7ec0cba7ce5b911dcff2b9325ae", + "0x8fbbc21a59d5f5a14ff455ca78a9a393cab91deb61cf1c25117db2714d752e0054ed3e7e13dd36ad423815344140f443", + "0xa9a03285488668ab97836a713c6e608986c571d6a6c21e1adbd99ae4009b3dde43721a705d751f1bd4ebf1ea7511dfed", + "0xb2f32b8e19e296e8402251df67bae6066aeefd89047586d887ffa2eacdf38e83d4f9dc32e553799024c7a41818945755", + "0x942cf596b2278ad478be5c0ab6a2ad0ceafe110263cc93d15b9a3f420932104e462cf37586c374f10b1040cb83b862e0", + "0xaaa077a55f501c875ceae0a27ef2b180be9de660ef3d6b2132eb17256771ce609d9bc8aaf687f2b56ae46af34ad12b30", + "0x90ac74885be1448101cf3b957d4486e379673328a006ea42715c39916e9334ea77117ff4a60d858e2ccce9694547a14f", + "0x9256cdfc2339e89db56fd04bd9b0611be0eefc5ee30711bcece4aadf2efcc5a6dcc0cfd5f733e0e307e3a58055dff612", + "0xa4c7384e208a0863f4c056248f595473dcde70f019ddaede45b8caf0752575c241bac6e436439f380ac88eee23a858e9", + "0xa3aa67391781e0736dddc389f86b430b2fc293b7bd56bfd5a8ec01d1dd52ed940593c3ad4ce25905061936da062b0af6", + "0x80299275ec322fbb66cc7dce4482ddd846534e92121186b6906c9a5d5834346b7de75909b22b98d73120caec964e7012", + "0xaa3a6cd88e5f98a12738b6688f54478815e26778357bcc2bc9f2648db408d6076ef73cced92a0a6b8b486453c9379f18", + "0xb07c444681dc87b08a7d7c86708b82e82f8f2dbd4001986027b82cfbed17b9043e1104ade612e8e7993a00a4f8128c93", + "0xaf40e01b68d908ac2a55dca9b07bb46378c969839c6c822d298a01bc91540ea7a0c07720a098be9a3cfe9c27918e80e8", + "0xabd8947c3bbc3883c80d8c873f8e2dc9b878cbbb4fc4a753a68f5027de6d8c26aa8fbbafeb85519ac94e2db660f31f26", + "0xa234f9d1a8f0cb5d017ccca30b591c95ec416c1cb906bd3e71b13627f27960f61f41ed603ffbcf043fd79974ec3169a8", + "0x835aaf52a6af2bc7da4cf1586c1a27c72ad9de03c88922ad172dce7550d70f6f3efcc3820d38cd56ae3f7fc2f901f7a0", + "0xae75db982a45ad01f4aa7bc50d642ff188219652bb8d521d13a9877049425d57852f3c9e4d340ffec12a4d0c639e7062", + "0xb88884aa9187c33dc784a96832c86a44d24e9ffe6315544d47fc25428f11337b9ffd56eb0a03ad709d1bf86175059096", + "0x8492ca5afcc6c0187b06453f01ed45fd57eb56facbeea30c93686b9e1dab8eaabd89e0ccb24b5f35d3d19cd7a58b5338", + "0x9350623b6e1592b7ea31b1349724114512c3cce1e5459cd5bddd3d0a9b2accc64ab2bf67a71382d81190c3ab7466ba08", + "0x98e8bf9bed6ae33b7c7e0e49fc43de135bffdba12b5dcb9ff38cb2d2a5368bb570fe7ee8e7fbe68220084d1d3505d5be", + "0xab56144393f55f4c6f80c67e0ab68f445568d68b5aa0118c0c666664a43ba6307ee6508ba0bb5eb17664817bc9749af0", + "0x827d5717a41b8592cfd1b796a30d6b2c3ca2cdc92455f9f4294b051c4c97b7ad6373f692ddafda67884102e6c2a16113", + "0x8445ce2bb81598067edaa2a9e356eda42fb6dc5dd936ccf3d1ff847139e6020310d43d0fec1fe70296e8f9e41a40eb20", + "0x9405178d965ee51e8d76d29101933837a85710961bb61f743d563ef17263f3c2e161d57e133afac209cdb5c46b105e31", + "0xb209f9ed324c0daa68f79800c0a1338bbaf6d37b539871cb7570f2c235caca238a2c4407961fcb7471a103545495ef2c", + "0x92ae6437af6bbd97e729b82f5b0d8fb081ca822f340e20fae1875bdc65694cd9b8c037a5a1d49aa9cae3d33f5bad414e", + "0x9445bdb666eae03449a38e00851629e29a7415c8274e93343dc0020f439a5df0009cd3c4f5b9ce5c0f79aefa53ceac99", + "0x93fdab5f9f792eada28f75e9ac6042a2c7f3142ba416bfdb1f90aa8461dbe4af524eee6db4f421cb70c7bc204684d043", + "0xa7f4dc949af4c3163953320898104a2b17161f7be5a5615da684f881633174fb0b712d0b7584b76302e811f3fac3c12f", + "0xa8ac84da817b3066ba9789bf2a566ccf84ab0a374210b8a215a9dcf493656a3fa0ecf07c4178920245fee0e46de7c3ec", + "0x8e6a0ae1273acda3aa50d07d293d580414110a63bc3fb6330bb2ee6f824aff0d8f42b7375a1a5ba85c05bfbe9da88cb5", + "0xa5dea98852bd6f51a84fa06e331ea73a08d9d220cda437f694ad9ad02cf10657882242e20bdf21acbbaa545047da4ce5", + "0xb13f410bf4cfce0827a5dfd1d6b5d8eabc60203b26f4c88238b8000f5b3aaf03242cdeadc2973b33109751da367069e1", + "0xa334315a9d61b692ad919b616df0aa75a9f73e4ea6fc27d216f48964e7daebd84b796418580cf97d4f08d4a4b51037cd", + "0x8901ba9e963fcd2f7e08179b6d19c7a3b8193b78ca0e5cf0175916de873ca0d000cd7ac678c0473be371e0ac132f35a2", + "0xb11a445433745f6cb14c9a65314bbf78b852f7b00786501b05d66092b871111cd7bee25f702d9e550d7dd91601620abb", + "0x8c2f7b8e7b906c71f2f154cc9f053e8394509c37c07b9d4f21b4495e80484fc5fc8ab4bdc525bd6cfa9518680ba0d1a2", + "0xb9733cebe92b43b899d3d1bfbf4b71d12f40d1853b2c98e36e635fdd8a0603ab03119890a67127e6bc79afae35b0bef2", + "0xa560f6692e88510d9ba940371e1ada344caf0c36440f492a3067ba38e9b7011caac37ba096a8a4accb1c8656d3c019b3", + "0xac18624339c1487b2626eef00d66b302bdb1526b6340d6847befe2fdfb2b410be5555f82939f8707f756db0e021ed398", + "0xafd9a3b8866a7fe4f7bc13470c0169b9705fcd3073685f5a6dcff3bdbbc2be50ac6d9908f9a10c5104b0bffc2bc14dad", + "0x97f15c92fe1f10949ed9def5dd238bc1429706e5037a0e0afb71c2d0e5845e2fed95a171c393e372077a7c7059f8c0e0", + "0x9453a1d4d09c309b70968ea527007d34df9c4cfd3048e5391aac5f9b64ca0c05dde5b8c949c481cfc83ef2e57b687595", + "0xb80e4b7c379ad435c91b20b3706253b763cbc980db78f782f955d2516af44c07bbfa5888cbf3a8439dc3907320feb25a", + "0x8939f458d28fefe45320b95d75b006e98330254056d063e4a2f20f04bcb25936024efe8d436d491ed34b482f9b9ae49c", + "0xa9ead2e833f71f7e574c766440c4b3c9c3363698c7ade14499a56003a272832ee6d99440887fa43ccdf80265b9d56b97", + "0xb6547a36934f05ce7b779e68049d61351cf229ae72dc211cc96a2a471b2724782f9355fdb415ea6f0ea1eb84fe00e785", + "0x828bfb3099b7b650b29b0f21279f829391f64520a6ab916d1056f647088f1e50fac9253ef7464eceab5380035c5a59c4", + "0x8d714b9ea650be4342ff06c0256189e85c5c125adf6c7aeca3dba9b21d5e01a28b688fc2116ce285a0714a8f1425c0b8", + "0x8a82eda041b2e72a3d73d70d85a568e035fbd6dc32559b6c6cfdf6f4edcb59a6ba85b6294a721aa0a71b07714e0b99ae", + "0xaf5665ebc83d027173b14ffb0e05af0a192b719177889fadc9ac8c082fda721e9a75d9ce3f5602dbfd516600ee3b6405", + "0xa68fdddf03d77bebdb676e40d93e59bd854408793df2935d0a5600601f7691b879981a398d02658c2da39dbbf61ef96c", + "0x8c001ebc84fcf0470b837a08a7b6125126b73a2762db47bbdc38c0e7992b1c66bac7a64faa1bf1020d1c63b40adc3082", + "0x8553889b49f9491109792db0a69347880a9cf2911b4f16f59f7f424e5e6b553687d51282e8f95be6a543635247e2e2c2", + "0xa2c269d6370b541daf1f23cc6b5d2b03a5fa0c7538d53ae500ef875952fe215e74a5010329ff41461f4c58b32ad97b3d", + "0xa5dae097285392b4eba83a9fd24baa03d42d0a157a37fae4b6efc3f45be86024b1182e4a6b6eadcf5efe37704c0a1ae5", + "0x89871a77d2032387d19369933cd50a26bda643e40cfd0ce73febe717a51b39fae981406fd41e50f4a837c02a99524ef9", + "0x8a76d495e90093ec2ac22f53759dc1cf36fbb8370fb586acbd3895c56a90bbf3796bcc4fc422ca4058adf337ead1402e", + "0xad4eb7576c4954d20623c1336c63662c2a6fb46ec6ef99b7f8e946aa47488dcb136eab60b35600f98c78c16c10c99013", + "0x894c2b120cec539feb1d281baaadde1e44beafedeeec29b804473fe024e25c1db652f151c956e88d9081fb39d27e0b19", + "0x9196bd5c100878792444c573d02b380a69e1b4b30cb59a48114852085058a5fd952df4afee3ecceb5c4ede21e1ed4a1a", + "0xa996fffc910764ea87a1eedc3a3d600e6e0ff70e6a999cb435c9b713a89600fc130d1850174efe9fc18244bb7c6c5936", + "0x8591bb8826befa8bee9663230d9a864a5068589f059e37b450e8c85e15ce9a1992f0ce1ead1d9829b452997727edcf9d", + "0x9465e20bb22c41bf1fa728be8e069e25cda3f7c243381ca9973cbedad0c7b07d3dd3e85719d77cf80b1058ce60e16d68", + "0x926b5ce39b6e60b94878ffeae9ff20178656c375fb9cfe160b82318ca500eb3e2e3144608b6c3f8d6c856b8fe1e2fbcf", + "0xa1ef29cbc83c45eb28ad468d0ce5d0fdd6b9d8191ba5ffa1a781c2b232ed23db6b7b04de06ef31763a6bfe377fa2f408", + "0x9328e63a3c8acf457c9f1f28b32d90d0eeadb0f650b5d43486a61d7374757a7ada5fc1def2a1e600fa255d8b3f48036f", + "0xa9c64880fcb7654f4dd08f4c90baac95712dd6dd407e17ea60606e9a97dc8e54dd25cb72a9bf3fc61f8d0ad569fe369d", + "0xa908eb7b940c1963f73046d6b35d40e09013bfbfbeb2ccd64df441867e202b0f3b625fa32dd04987c3d7851360abdffc", + "0xb3947b5ed6d59e59e4472cdb1c3261de1b5278fb7cb9b5fca553f328b3b3e094596861ea526eca02395f7b7358155b7b", + "0x99da7f190d37bc58945f981cf484d40fcf0855cf8178e2ce8d057c7f0a9d9f77425fdbce9ef8366f44f671b20fd27d0b", + "0x913976d77d80e3657977df39571577fdf0be68ba846883705b454f8493578baa741cfaede53783e2c97cc08964395d83", + "0x8d754a61e5164a80b5090c13f3e936056812d4ae8dc5cc649e6c7f37464777249bc4ae760a9806939131f39d92cca5bf", + "0x82ffd098480828a90cb221a8c28584e15904bad477c13b2e2d6ef0b96a861ce4a309a328fe44342365349456ad7c654f", + "0x89ae3ce4b0357044579ca17be85d8361bb1ce3941f87e82077dd67e43ec0f95edd4bd3426225c90994a81a99e79490b7", + "0xa170892074016d57c9d8e5a529379d7e08d2c1158b9ac4487ac9b95266c4fd51cb18ae768a2f74840137eec05000dd5a", + "0xaafd8acd1071103c7af8828a7a08076324d41ea530df90f7d98fafb19735fc27ead91b50c2ca45851545b41d589d0f77", + "0x8623c849e61d8f1696dc9752116a26c8503fd36e2cbbc9650feffdd3a083d8cdbb3b2a4e9743a84b9b2ad91ac33083f2", + "0xac7166ddd253bb22cdbd8f15b0933c001d1e8bc295e7c38dc1d2be30220e88e2155ecd2274e79848087c05e137e64d01", + "0xa5276b216d3df3273bbfa46210b63b84cfe1e599e9e5d87c4e2e9d58666ecf1af66cb7ae65caebbe74b6806677215bd0", + "0x88792f4aa3597bb0aebadb70f52ee8e9db0f7a9d74f398908024ddda4431221a7783e060e0a93bf1f6338af3d9b18f68", + "0x8f5fafff3ecb3aad94787d1b358ab7d232ded49b15b3636b585aa54212f97dc1d6d567c180682cca895d9876cacb7833", + "0xab7cb1337290842b33e936162c781aa1093565e1a5b618d1c4d87dd866daea5cebbcc486aaa93d8b8542a27d2f8694c7", + "0x88480a6827699da98642152ebc89941d54b4791fbc66110b7632fb57a5b7d7e79943c19a4b579177c6cf901769563f2f", + "0xa725ee6d201b3a610ede3459660658ee391803f770acc639cfc402d1667721089fb24e7598f00e49e81e50d9fd8c2423", + "0x98924372da8aca0f67c8c5cad30fa5324519b014fae7849001dcd51b6286118f12b6c49061219c37714e11142b4d46de", + "0xa62c27360221b1a7c99697010dfe1fb31ceb17d3291cf2172624ebeff090cbaa3c3b01ec89fe106dace61d934711d42d", + "0x825173c3080be62cfdc50256c3f06fe190bc5f190d0eb827d0af5b99d80936e284a4155b46c0d462ee574fe31d60983d", + "0xa28980b97023f9595fadf404ed4aa36898d404fe611c32fd66b70252f01618896f5f3fda71aea5595591176aabf0c619", + "0xa50f5f9def2114f6424ff298f3b128068438f40860c2b44e9a6666f43c438f1780be73cf3de884846f1ba67f9bef0802", + "0xb1eee2d730da715543aeb87f104aff6122cb2bf11de15d2519ff082671330a746445777924521ec98568635f26988d0c", + "0x862f6994a1ff4adfd9fb021925cccf542fca4d4b0b80fb794f97e1eb2964ef355608a98eec6e07aadd4b45ee625b2a21", + "0x8ce69a18df2f9b9f6e94a456a7d94842c61dea9b00892da7cf5c08144de9be39b8c304aeca8b2e4222f87ba367e61006", + "0xb5f325b1cecd435f5346b6bc562d92f264f1a6d91be41d612df012684fdd69e86063db077bc11ea4e22c5f2a13ae7bee", + "0x85526870a911127835446cb83db8986b12d5637d59e0f139ad6501ac949a397a6c73bd2e7fba731b1bb357efe068242c", + "0x8552247d3f7778697f77389717def5a149fc20f677914048e1ed41553b039b5427badc930491c0bae663e67668038fd1", + "0xa545640ee5e51f3fe5de7050e914cfe216202056cd9d642c90e89a166566f909ee575353cb43a331fde17f1c9021414e", + "0x8b51229b53cff887d4cab573ba32ec52668d197c084414a9ee5589b285481cea0c3604a50ec133105f661321c3ca50f5", + "0x8cdc0b960522bed284d5c88b1532142863d97bbb7dc344a846dc120397570f7bd507ceb15ed97964d6a80eccfef0f28e", + "0xa40683961b0812d9d53906e795e6470addc1f30d09affebf5d4fbbd21ddfa88ce441ca5ea99c33fd121405be3f7a3757", + "0xa527875eb2b99b4185998b5d4cf97dd0d4a937724b6ad170411fc8e2ec80f6cee2050f0dd2e6fee9a2b77252d98b9e64", + "0x84f3a75f477c4bc4574f16ebc21aaa32924c41ced435703c4bf07c9119dd2b6e066e0c276ff902069887793378f779e0", + "0xa3544bc22d1d0cab2d22d44ced8f7484bfe391b36991b87010394bfd5012f75d580596ffd4f42b00886749457bb6334b", + "0xb81f6eb26934b920285acc20ceef0220dd23081ba1b26e22b365d3165ce2fbae733bbc896bd0932f63dcc84f56428c68", + "0x95e94d40a4f41090185a77bf760915a90b6a3e3ace5e53f0cb08386d438d3aa3479f0cd81081b47a9b718698817265cd", + "0xb69bd1625b3d6c17fd1f87ac6e86efa0d0d8abb69f8355a08739109831baeec03fd3cd4c765b5ff8b1e449d33d050504", + "0x8448f4e4c043519d98552c2573b76eebf2483b82d32abb3e2bfc64a538e79e4f59c6ca92adff1e78b2f9d0a91f19e619", + "0x8f11c42d6a221d1fda50887fb68b15acdb46979ab21d909ed529bcad6ae10a66228ff521a54a42aca0dad6547a528233", + "0xa3adb18d7e4a882b13a067784cf80ea96a1d90f5edc61227d1f6e4da560c627688bdf6555d33fe54cab1bca242986871", + "0xa24d333d807a48dc851932ed21cbdd7e255bad2699909234f1706ba55dea4bb6b6f8812ffc0be206755868ba8a4af3f9", + "0xa322de66c22a606e189f7734dbb7fda5d75766d5e69ec04b4e1671d4477f5bcb9ff139ccc18879980ebc3b64ab4a2c49", + "0x88f54b6b410a1edbf125db738d46ee1a507e69bc5a8f2f443eb787b9aa7dbd6e55014ec1e946aabeb3e27a788914fb04", + "0xb32ee6da1dcd8d0a7fd7c1821bb1f1fe919c8922b4c1eeed56e5b068a5a6e68457c42b192cbaef5dc6d49b17fa45bc0f", + "0x8a44402da0b3a15c97b0f15db63e460506cb8bef56c457166aea5e8881087d8202724c539ef0feb97131919a73aefca8", + "0xb967e3fead6171fa1d19fd976535d428b501baff59e118050f9901a54b12cc8e4606348454c8f0fc25bd6644e0a5532e", + "0xb7a0c9e9371c3efbbb2c6783ce2cc5f149135175f25b6d79b09c808bce74139020e77f0c616fa6dcb3d87a378532529d", + "0xa54207782ffc909cd1bb685a3aafabbc4407cda362d7b3c1b14608b6427e1696817aeb4f3f85304ac36e86d3d8caa65b", + "0x98c1da056813a7bfebc81d8db7206e3ef9b51f147d9948c088976755826cc5123c239ca5e3fe59bed18b5d0a982f3c3f", + "0xae1c86174dfafa9c9546b17b8201719aecd359f5bbeb1900475041f2d5b8a9600d54d0000c43dd061cfda390585726ff", + "0xa8ee5a8be0bd1372a35675c87bfd64221c6696dc16e2d5e0996e481fec5cdbcb222df466c24740331d60f0521285f7d3", + "0x8ddadbe3cf13af50d556ce8fc0dd77971ac83fad9985c3d089b1b02d1e3afc330628635a31707b32595626798ea22d45", + "0xa5c80254baf8a1628dc77c2445ebe21fbda0de09dd458f603e6a9851071b2b7438fe74214df293dfa242c715d4375c95", + "0xb9d83227ed2600a55cb74a7052003a317a85ca4bea50aa3e0570f4982b6fe678e464cc5156be1bd5e7bba722f95e92c5", + "0xb56085f9f3a72bea9aa3a8dc143a96dd78513fa327b4b9ba26d475c088116cab13843c2bff80996bf3b43d3e2bddb1d6", + "0x8fa9b39558c69a9757f1e7bc3f07295e4a433da3e6dd8c0282397d26f64c1ecd8eb3ba9824a7cacfb87496ebbb45d962", + "0x879c6d0cb675812ed9dee68c3479a499f088068501e2677caeae035e6f538da91a49e245f5fcce135066169649872bee", + "0x91aa9fd3fed0c2a23d1edda8a6542188aeb8abee8772818769bdee4b512d431e4625a343af5d59767c468779222cf234", + "0xa6be0bb2348c35c4143482c7ef6da9a93a5356f8545e8e9d791d6c08ed55f14d790d21ee61d3a56a2ae7f888a8fd46ca", + "0x808ee396a94e1b8755f2b13a6ffbedef9e0369e6c2e53627c9f60130c137299d0e4924d8ef367e0a7fad7f68a8c9193c", + "0xad1086028fcdac94d5f1e7629071e7e47e30ad0190ae59aaebfb7a7ef6202ab91323a503c527e3226a23d7937af41a52", + "0x9102bdaf79b907d1b25b2ec6b497e2d301c8eac305e848c6276b392f0ad734131a39cc02ed42989a53ca8da3d6839172", + "0x8c976c48a45b6bc7cd7a7acea3c2d7c5f43042863b0661d5cd8763e8b50730552187a8eecf6b3d17be89110208808e77", + "0xa2624c7e917e8297faa3af89b701953006bf02b7c95dfba00c9f3de77748bc0b13d6e15bb8d01377f4d98fb189538142", + "0xa405f1e66783cdcfe20081bce34623ec3660950222d50b7255f8b3cc5d4369aeb366e265e5224c0204911539f0fa165e", + "0x8d69bdcaa5d883b5636ac8f8842026fcc58c5e2b71b7349844a3f5d6fbecf44443ef4f768eac376f57fb763606e92c9f", + "0x82fce0643017d16ec1c3543db95fb57bfa4855cc325f186d109539fcacf8ea15539be7c4855594d4f6dc628f5ad8a7b0", + "0x8860e6ff58b3e8f9ae294ff2487f0d3ffae4cf54fd3e69931662dabc8efd5b237b26b3def3bcd4042869d5087d22afcf", + "0x88c80c442251e11c558771f0484f56dc0ed1b7340757893a49acbf96006aa73dfc3668208abea6f65375611278afb02a", + "0x8be3d18c6b4aa8e56fcd74a2aacb76f80b518a360814f71edb9ccf3d144bfd247c03f77500f728a62fca7a2e45e504c5", + "0x8b8ebf0df95c3f9b1c9b80469dc0d323784fd4a53f5c5357bb3f250a135f4619498af5700fe54ad08744576588b3dfff", + "0xa8d88abdaadd9c2a66bc8db3072032f63ed8f928d64fdb5f810a65074efc7e830d56e0e738175579f6660738b92d0c65", + "0xa0a10b5d1a525eb846b36357983c6b816b8c387d3890af62efb20f50b1cb6dd69549bbef14dab939f1213118a1ae8ec2", + "0x8aadf9b895aeb8fdc9987daa937e25d6964cbd5ec5d176f5cdf2f0c73f6f145f0f9759e7560ab740bf623a3279736c37", + "0x99aeda8a495031cc5bdf9b842a4d7647c55004576a0edc0bd9b985d60182608361ed5459a9d4b21aa8e2bd353d10a086", + "0x832c8b3bfcd6e68eee4b100d58014522de9d4cefa99498bc06c6dca83741e4572e20778e0d846884b33439f160932bca", + "0x841f56ebefc0823ab484fc445d62f914e13957e47904419e42771aa605e33ab16c44f781f6f9aa42e3a1baf377f54b42", + "0xa6e40271d419e295a182725d3a9b541ffd343f23e37549c51ecaa20d13cf0c8d282d6d15b24def5702bfee8ba10b12ac", + "0x8ac00925ac6187a4c5cde48ea2a4eaf99a607e58b2c617ee6f01df30d03fafada2f0469178dd960d9d64cbd33a0087d8", + "0xb6b80916b540f8a0fe4f23b1a06e2b830008ad138271d5ba3cd16d6619e521fe2a7623c16c41cba48950793386eea942", + "0x8412c0857b96a650e73af9d93087d4109dd092ddf82188e514f18fcac644f44d4d62550bfa63947f2d574a2e9d995bbb", + "0xb871395baa28b857e992a28ac7f6d95ec461934b120a688a387e78498eb26a15913b0228488c3e2360391c6b7260b504", + "0x926e2d25c58c679be77d0e27ec3b580645956ba6f13adcbc2ea548ee1b7925c61fcf74c582337a3b999e5427b3f752f2", + "0xa165fa43fecae9b913d5dcfc232568e3e7b8b320ce96b13800035d52844c38fd5dbf7c4d564241d860c023049de4bcbc", + "0xb4976d7572fd9cc0ee3f24888634433f725230a7a2159405946a79315bc19e2fc371448c1c9d52bf91539fd1fe39574b", + "0xa6b461eb72e07a9e859b9e16dfa5907f4ac92a5a7ca4368b518e4a508dc43f9b4be59db6849739f3ef4c44967b63b103", + "0xb976606d3089345d0bc501a43525d9dca59cf0b25b50dfc8a61c5bd30fac2467331f0638fab2dc68838aa6ee8d2b6bc9", + "0xb16ea61c855da96e180abf7647fa4d9dd6fd90adebadb4c5ed4d7cd24737e500212628fca69615d89cb40e9826e5a214", + "0x95a3e3162eb5ea27a613f8c188f2e0dcc5cbd5b68c239858b989b004d87113e6aa3209fa9fad0ee6ecef42814ba9db1a", + "0xb6a026ab56d3224220e5bce8275d023c8d39d1bdf7eec3b0923429b7d5ef18cf613a3591d364be8727bb1fa0ba11eabb", + "0x949f117e2e141e25972ee9ccdd0b7a21150de7bbf92bbd89624a0c5f5a88da7b2b172ba2e9e94e1768081f260c2a2f8d", + "0xb7c5e9e6630287d2a20a2dfb783ffe6a6ff104ff627c6e4e4342acc2f3eb6e60e9c22f465f8a8dc58c42f49840eca435", + "0x872be5a75c3b85de21447bb06ac9eb610f3a80759f516a2f99304930ddf921f34cbffc7727989cdd7181d5fc62483954", + "0xa50976ea5297d797d220932856afdd214d1248230c9dcd840469ecc28ea9f305b6d7b38339fedb0c00b5251d77af8c95", + "0x80b360f8b44914ff6f0ffbd8b5360e3cabe08639f6fe06d0c1526b1fe9fe9f18c497f1752580b30e950abd3e538ad416", + "0xa2f98f9bf7fac78c9da6bb41de267742a9d31cf5a04b2fb74f551084ec329b376f651a59e1ae919b2928286fb566e495", + "0x8b9d218a8a6c150631548e7f24bbd43f132431ae275c2b72676abbea752f554789c5ff4aac5c0eeee5529af7f2b509ef", + "0xaa21a243b07e9c7b169598bf0b102c3c280861780f83121b2ef543b780d47aaa4b1850430ee7927f33ece9847c4e0e1a", + "0x8a6f90f4ce58c8aa5d3656fe4e05acccf07a6ec188a5f3cde7bf59a8ae468e66f055ac6dfc50b6e8e98f2490d8deedc5", + "0x8e39f77ca4b5149ffe9945ceac35d068760ba338d469d57c14f626dd8c96dbe993dd7011beff727c32117298c95ee854", + "0x83bd641c76504222880183edd42267e0582642c4993fe2c7a20ce7168e4c3cbf7586e1d2d4b08c84d9b0bf2f6b8800b8", + "0xa9d332993cf0c1c55130e5cf3a478eb5e0bfb49c25c07538accc692ef03d82b458750a7b991cc0b41b813d361a5d31e3", + "0xa0fc60e6a6015df9bee04cea8f20f01d02b14b6f7aa03123ab8d65da071b2d0df5012c2a69e7290baae6ed6dd29ebe07", + "0xa2949dde2e48788ceaac7ec7243f287ffe7c3e788cdba97a4ab0772202aeef2d50382bed8bf7eff5478243f7eabe0bda", + "0xa7879373ea18572dba6cf29868ca955ffa55b8af627f29862f6487ee398b81fe3771d8721ca8e06716c5d91b9ac587cb", + "0xb3c7081e2c5306303524fbe9fe5645111a57dffd4ec25b7384da12e56376a0150ab52f9d9cc6ca7bdd950695e39b766d", + "0xa634a6a19d52dcb9f823352b36c345d2de54b75197bcd90528d27830bd6606d1a9971170de0849ed5010afa9f031d5be", + "0x88f2062f405fa181cfdb8475eaf52906587382c666ca09a9522537cfebbc7de8337be12a7fd0db6d6f2f7ab5aefab892", + "0xb1f0058c1f273191247b98783b2a6f5aa716cf799a8370627fc3456683f03a624d0523b63a154fe9243c0dfd5b37c460", + "0xae39a227cc05852437d87be6a446782c3d7fbe6282e25cf57b6b6e12b189bdc0d4a6e2c3a60b3979256b6b5baf8f1c5f", + "0x802a1af228ab0c053b940e695e7ef3338f5be7acf4e5ed01ac8498e55b492d3a9f07996b1700a84e22f0b589638909cd", + "0xa36490832f20e4b2f9e79ee358b66d413f034d6a387534b264cdeac2bca96e8b5bcbdd28d1e98c44498032a8e63d94d2", + "0x8728c9a87db2d006855cb304bba54c3c704bf8f1228ae53a8da66ca93b2dac7e980a2a74f402f22b9bc40cd726e9c438", + "0xa08f08ab0c0a1340e53b3592635e256d0025c4700559939aeb9010ed63f7047c8021b4210088f3605f5c14fb51d1c613", + "0x9670fd7e2d90f241e8e05f9f0b475aa260a5fb99aa1c9e61cd023cbad8ed1270ae912f168e1170e62a0f6d319cf45f49", + "0xa35e60f2dd04f098bf274d2999c3447730fe3e54a8aff703bc5a3c274d22f97db4104d61a37417d93d52276b27ef8f31", + "0x859df7a21bc35daec5695201bd69333dc4f0f9e4328f2b75a223e6615b22b29d63b44d338413ca97eb74f15563628cb7", + "0xb2b44ad3e93bc076548acdf2477803203108b89ecc1d0a19c3fb9814d6b342afc420c20f75e9c2188ad75fdb0d34bb2d", + "0x941173ee2c87765d10758746d103b667b1227301e1bcfecef2f38f9ab612496a9abd3050cef5537bf28cfecd2aacc449", + "0x92b0bea30ebed20ac30648efb37bac2b865daaa514316e6f5470e1de6cb84651ff77c127aa7beed4521bda5e8fc81122", + "0xaf17bf813bb238cf8bb437433f816786612209180a6c0a1d5141292dc2d2c37164ef13bfc50c718bfcc6ce26369298a2", + "0x8461fd951bdfda099318e05cc6f75698784b033f15a71bce26165f0ce421fd632d50df9eeced474838c0050b596e672c", + "0x83281aa18ae4b01e8201e1f64248cc6444c92ee846ae72adb178cef356531558597d84ff93a05abf76bfe313eb7dbe86", + "0xb62b150f73999c341daa4d2f7328d2f6ca1ef3b549e01df58182e42927537fc7971c360fe8264af724f4c0247850ef12", + "0xa7022a201f79c012f982b574c714d813064838a04f56964d1186691413757befeeaada063e7884297606e0eea1b1ed43", + "0xa42ac9e8be88e143853fd8e6a9ff21a0461801f0ac76b69cca669597f9af17ecb62cccdcdcbe7f19b62ab93d7f838406", + "0x80f1ca73b6ba3a2fbae6b79b39c0be8c39df81862d46c4990c87cbf45b87996db7859d833abc20af2fcb4faf059c436a", + "0xb355943e04132d5521d7bbe49aea26f6aa1c32f5d0853e77cc2400595325e923a82e0ff7601d1aee79f45fd8a254f6ae", + "0x87142c891d93e539b31d0b5ead9ea600b9c84db9be9369ff150a8312fe3d10513f4c5b4d483a82b42bc65c45dd9dd3bd", + "0x823c3d7f6dda98a9d8c42b3fee28d3154a95451402accadb6cf75fc45d2653c46a569be75a433094fa9e09c0d5cf1c90", + "0xb3c3497fe7356525c1336435976e79ec59c5624c2fb6185ee09ca0510d58b1e392965e25df8a74d90d464c4e8bb1422b", + "0x88c48d83e8ddc0d7eea051f3d0e21bc0d3a0bb2b6a39ece76750c1c90c382a538c9a35dc9478b8ceb8157dcccbbf187a", + "0x93da81a8939f5f58b668fefdc6f5f7eca6dc1133054de4910b651f8b4a3267af1e44d5a1c9e5964dc7ab741eb146894b", + "0x8b396e64985451ac337f16be61105106e262e381ea04660add0b032409b986e1ac64da3bc2feae788e24e9cb431d8668", + "0x9472068b6e331ea67e9b5fbf8057672da93c209d7ded51e2914dbb98dccd8c72b7079b51fd97a7190f8fc8712c431538", + "0xac47e1446cb92b0a7406f45c708567f520900dfa0070d5e91783139d1bfc946d6e242e2c7b3bf4020500b9f867139709", + "0x896053706869fb26bb6f7933b3d9c7dd6db5c6bd1269c7a0e222b73039e2327d44bda7d7ae82bf5988808b9831d78bcd", + "0xa55e397fa7a02321a9fe686654c86083ecedb5757586d7c0250ec813ca6d37151a12061d5feca4691a0fd59d2f0fdd81", + "0xae23f08ac2b370d845036518f1bddb7fea8dc59371c288a6af310486effeb61963f2eef031ca90f9bdbcf0e475b67068", + "0xb5462921597a79f66c0fec8d4c7cfd89f427692a7ce30d787e6fd6acd2377f238ec74689a0fdbe8ef3c9c9bd24b908dc", + "0xae67e8ea7c46e29e6aae6005131c29472768326819aa294aaf5a280d877de377b44959adb1348fa3e929dcbc3ae1f2c0", + "0x84962b4c66500a20c4424191bdfb619a46cda35bdb34c2d61edcb0b0494f7f61dd5bf8f743302842026b7b7d49edd4b5", + "0x846f76286dc3cc59cb15e5dabb72a54a27c78190631df832d3649b2952fa0408ecde7d4dfdae7046c728efa29879fb51", + "0x8f76c854eaee8b699547e07ad286f7dadfa6974c1328d12502bd7630ae619f6129272fdd15e2137ffef0143c42730977", + "0x8007b163d4ea4ec6d79e7a2aa19d06f388da0b3a56f3ee121441584e22a246c0e792431655632bf6e5e02cb86914eebf", + "0xac4d2cecc1f33e6fb73892980b61e62095ddff5fd6167f53ca93d507328b3c05440729a277dc3649302045b734398af1", + "0x92d2a88f2e9c9875abaff0d42624ccb6d65401de7127b5d42c25e6adccd7a664504c5861618f9031ced8aeb08b779f06", + "0xa832c1821c1b220eb003fc532af02c81196e98df058cdcc9c9748832558362915ea77526937f30a2f74f25073cb89afb", + "0xb6f947ab4cc2baec100ed8ec7739a2fd2f9504c982b39ab84a4516015ca56aea8eef5545cfc057dd44c69b42125fb718", + "0xb24afacf2e90da067e5c050d2a63878ee17aaf8fd446536f2462da4f162de87b7544e92c410d35bf2172465940c19349", + "0xb7a0aa92deac71eaab07be8fa43086e071e5580f5dbf9b624427bdd7764605d27303ae86e5165bed30229c0c11958c38", + "0xb0d1d5bfa1823392c5cf6ed927c1b9e84a09a24b284c2cd8fcb5fda8e392c7c59412d8f74eb7c48c6851dff23ae66f58", + "0xa24125ef03a92d2279fb384186ca0274373509cfec90b34a575490486098438932ee1be0334262d22d5f7d3db91efe67", + "0x83e08e5fba9e8e11c164373794f4067b9b472d54f57f4dbe3c241cf7b5b7374102de9d458018a8c51ab3aed1dddf146f", + "0x9453101b77bb915ed40990e1e1d2c08ea8ec5deb5b571b0c50d45d1c55c2e2512ec0ceca616ff0376a65678a961d344d", + "0x92a0516e9eb6ad233d6b165a8d64a062ce189b25f95d1b3264d6b58da9c8d17da2cd1f534800c43efcf2be73556cd2ff", + "0x958d0b5d7d8faf25d2816aa6a2c5770592ad448db778dd9b374085baa66c755b129822632eaabcb65ee35f0bf4b73634", + "0x90a749de8728b301ad2a6b044e8c5fd646ccd8d20220e125cba97667e0bb1d0a62f6e3143b28f3d93f69cdc6aa04122a", + "0x84bd34c8d8f74dec07595812058db24d62133c11afed5eb2a8320d3bfc28e442c7f0cfd51011b7b0bb3e5409cb7b6290", + "0xaecc250b556115d97b553ad7b2153f1d69e543e087890000eaa60f4368b736921d0342ce5563124f129096f5d5e2ca9d", + "0x977f17ac82ed1fbf422f9b95feb3047a182a27b00960296d804fd74d54bb39ad2c055e665c1240d2ad2e06a3d7501b00", + "0xaf5be9846bd4879ebe0af5e7ad253a632f05aedfe306d31fe6debe701ba5aa4e33b65efc05043bc73aadb199f94baed4", + "0x9199e12ec5f2aaaeed6db5561d2dcc1a8fe9c0854f1a069cba090d2dff5e5ba52b10c841ccbd49006a91d881f206150d", + "0x8f4a96a96ed8ceaf3beba026c89848c9ca4e6452ce23b7cf34d12f9cc532984a498e051de77745bdc17c7c44c31b7c30", + "0xaf3f2a3dbe8652c4bfca0d37fb723f0e66aab4f91b91a625114af1377ad923da8d36da83f75deb7a3219cd63135a3118", + "0xa6d46963195df8962f7aa791d104c709c38caa438ddd192f7647a884282e81f748c94cdf0bb25d38a7b0dc1b1d7bbcf7", + "0x86f3de4b22c42d3e4b24b16e6e8033e60120af341781ab70ae390cb7b5c5216f6e7945313c2e04261a51814a8cb5db92", + "0xb9f86792e3922896cfd847d8ff123ff8d69ecf34968fb3de3f54532f6cd1112b5d34eeabdca46ae64ad9f6e7e5b55edc", + "0x83edfbcbc4968381d1e91ab813b3c74ab940eaf6358c226f79182f8b21148ec130685fd91b0ea65916b0a50bccf524ea", + "0x93b61daca7a8880b7926398760f50016f2558b0bab74c21181280a1baf3414fc539911bb0b79c4288d29d3c4ad0f4417", + "0xad541aeb83a47526d38f2e47a5ce7e23a9adabe5efeae03541026881e6d5ef07da3ac1a6ed466ca924fa8e7a91fcff88", + "0xac4bba31723875025640ed6426003ed8529215a44c9ffd44f37e928feef9fc4dfa889088131c9be3da87e8f3fdf55975", + "0x88fa4d49096586bc9d29592909c38ea3def24629feacd378cc5335b70d13814d6dac415f8c699ee1bf4fe8b85eb89b38", + "0xb67d0b76cbd0d79b71f4673b96e77b6cda516b8faa1510cfe58ff38cc19000bb5d73ff8418b3dab8c1c7960cb9c81e36", + "0x98b4f8766810f0cfecf67bd59f8c58989eb66c07d3dfeee4f4bbce8fd1fce7cc4f69468372eaec7d690748543bd9691d", + "0x8445891af3c298b588dec443beacdf41536adb84c812c413a2b843fd398e484eb379075c64066b460839b5fe8f80177c", + "0xb603635c3ed6fdc013e2a091fc5164e09acf5f6a00347d87c6ebadb1f44e52ff1a5f0466b91f3f7ffc47d25753e44b75", + "0x87ec2fc928174599a9dafe7538fec7dcf72e6873b17d953ed50708afff0da37653758b52b7cafa0bf50dfcf1eafbb46c", + "0xb9dbd0e704d047a457d60efe6822dc679e79846e4cbcb11fa6c02079d65673ee19bbf0d14e8b7b200b9205f4738df7c7", + "0x9591ec7080f3f5ba11197a41f476f9ba17880f414d74f821a072ec5061eab040a2acba3d9856ff8555dfe5eaeb14ca19", + "0xb34c9d1805b5f1ce38a42b800dec4e7f3eb8c38e7d2b0a525378e048426fed150dbfe9cc61f5db82b406d1b9ff2d10bf", + "0xa36fdc649dc08f059dfa361e3969d96b4cc4a1ebf10b0cd01a7dd708430979e8d870961fef85878f8779b8e23caafb18", + "0x88dfc739a80c16c95d9d6f73c3357a92d82fa8c3c670c72bee0f1e4bac9ec338e1751eb786eda3e10f747dd7a686900f", + "0x84a535ad04f0961756c61c70001903a9adf13126983c11709430a18133c4b4040d17a33765b4a06968f5d536f4bfb5c5", + "0x8c86d695052a2d2571c5ace744f2239840ef21bb88e742f050c7fa737cd925418ecef0971333eb89daa6b3ddfede268c", + "0x8e9a700157069dc91e08ddcbdde3a9ad570272ad225844238f1015004239c542fceb0acce6d116c292a55f0d55b6175e", + "0x84d659e7f94e4c1d15526f47bc5877a4ef761c2a5f76ec8b09c3a9a30992d41b0e2e38ed0c0106a6b6c86d670c4235f3", + "0xa99253d45d7863db1d27c0ab561fb85da8c025ba578b4b165528d0f20c511a9ca9aff722f4ff7004843f618eb8fced95", + "0x89a3cacb15b84b20e95cd6135550146bbe6c47632cc6d6e14d825a0c79b1e02b66f05d57d1260cb947dc4ae5b0283882", + "0x8385b1555e794801226c44bd5e878cbe68aeac0a19315625a8e5ea0c3526b58cdd4f53f9a14a167a5e8a293b530d615a", + "0xb68c729e9df66c5cd22af4909fb3b0057b6a231c4a31cd6bf0fa0e53c5809419d15feb483de6e9408b052458e819b097", + "0x924f56eda269ec7ec2fc20c5731bf7f521546ddf573ccbe145592f1c9fee5134747eb648d9335119a8066ca50a1f7e50", + "0xb2100a26b9c3bec7ec5a53f0febbf56303f199be2f26b2d564cfee2adc65483b84192354f2865c2f4c035fa16252ae55", + "0x8f64dbed62e638563967ec1605a83216aed17eb99aa618c0543d74771ea8f60bbb850c88608d4f8584f922e30a8a0a72", + "0xb31b9e1ffe8d7260479c9413f8e680f3fe391ae8fcf44fcca3000d9b2473a40c1d32299f8f63865a57579a2d6c7e9f08", + "0xa5b1d136142eb23e322c6c07cb838a3f58ab6925472352ebd0bb47041a0d8729e1074ca223922f3a7a672ced7a1e562d", + "0x8d9470a5a15d833a447b5f108333d50f30aa7659e331c3f8080b1e928a99922edc650466a2f54f3d48afdb34bff42142", + "0x866368f5891564e5b2de37ad21ff0345c01129a14ea5667f9b64aad12d13ec034622872e414743af0bf20adb2041b497", + "0x88ef9c2ebf25fd0c04b7cfa35fbac2e4156d2f1043fa9f98998b2aa402c8f9a4f1039e782451a46840f3e0e4b3fa47d3", + "0x94ba04a4859273697e264a2d238dc5c9ff573ebc91e4796ea58eebe4080c1bf991255ab2ad8fb1e0301ce7b79cc6e69b", + "0x86b6bd0953309a086e526211bf1a99327269304aa74d8cdc994cee63c3a2d4b883e832b0635888dff2a13f1b02eb8df4", + "0x843ea6ea5f2c7a1fd50be56a5765dcce3ea61c99b77c1a729ee0cd8ec706385ac7062e603479d4c8d3527f030762d049", + "0x8d3675195a3b06f2d935d45becc59f9fa8fa440c8df80c029775e47fe9c90e20f7c8e4cc9a2542dd6bfe87536c428f0d", + "0x8978580b0c9b0aa3ab2d47e3cfd92fa891d3ddee57829ee4f9780e8e651900457d8e759d1a9b3e8f6ae366e4b57f2865", + "0x890112ec81d0f24b0dfbb4d228e418eff02ae63dc691caf59c1d103e1d194e6e2550e1bec41c0bfdb74fed454f621d0c", + "0x97da00bd4b19d1e88caff7f95b8b9a7d29bc0afe85d0c6a163b4b9ef336f0e90e2c49ce6777024bb08df908cc04ea1ca", + "0xb458268d275a5211106ccaa8333ce796ef2939b1c4517e502b6462e1f904b41184a89c3954e7c4f933d68b87427a7bfd", + "0xaac9c043ba8ba9283e8428044e6459f982413380ee7005a996dc3cc468f6a21001ecaa3b845ce2e73644c2e721940033", + "0x82145013c2155a1200246a1e8720adf8a1d1436b10d0854369d5b1b6208353e484dd16ce59280c6be84a223f2d45e5e2", + "0xb301bafa041f9b203a46beab5f16160d463aa92117c77a3dc6a9261a35645991b9bafcc186c8891ca95021bd35f7f971", + "0xa531b8d2ac3de09b92080a8d8857efa48fb6a048595279110e5104fee7db1dd7f3cfb8a9c45c0ed981cbad101082e335", + "0xa22ac1d627d08a32a8abd41504b5222047c87d558ffae4232cefdeb6a3dc2a8671a4d8ddfba2ff9068a9a3ffb0fe99b1", + "0xb8d9f0e383c35afb6d69be7ff04f31e25c74dd5751f0e51290c18814fbb49ee1486649e64355c80e93a3d9278bd21229", + "0x8165babccd13033a3614c878be749dfa1087ecbeee8e95abcfffe3aa06695711122cb94477a4d55cffd2febf0c1173de", + "0xa4c1bc84ecb9d995d1d21c2804adf25621676d60334bd359dac3a2ec5dc8de567aa2831c10147034025fb3e3afb33c4b", + "0xb77307cab8e7cb21e4038493058fb6db9e2ec91dda9d7f96f25acbc90309daf7b6d8a205682143ee35d675e9800c3b08", + "0xaaf7466083cd1f325ba860efe3faf4cebe6a5eecf52c3e8375d72043a5cfc8e6cb4b40f8e48f97266e84f0d488e8badf", + "0x9264a05a3abc2a5b4958f957f3a486a5eb3ddd10ff57aa6943c9430d0cfa01d63b72695b1ade50ac1b302d312175e702", + "0xb3f9e4c589ad28b1eceed99dc9980fac832524cfcbe4a486dfeedb4b97c080e24bdb3967e9ca63d2240e77f9addfaefd", + "0xb2c1e253a78e7179e5d67204422e0debfa09c231970b1bfb70f31a8d77c7f5059a095ca79d2e9830f12c4a8f88881516", + "0x81865a8a25913d1072cb5fd9505c73e0fde45e4c781ddd20fb0a7560d8b1cd5e1f63881c6efc05360e9204dfa6c3ce16", + "0xab71c2ea7fa7853469a2236dedb344a19a6130dc96d5fd6d87d42d3fffda172557d203b7688ce0f86acd913ce362e6cd", + "0x8aa2051bc3926c7bd63565f3782e6f77da824cb3b22bb056aa1c5bccfa274c0d9e49a91df62d0e88876e2bd7776e44b9", + "0xb94e7074167745323d1d353efe7cfb71f40a390e0232354d5dfd041ef523ac8f118fb6dcc42bf16c796e3f61258f36f8", + "0x8210fcf01267300cb1ccf650679cf6e1ee46df24ae4be5364c5ff715332746c113d680c9a8be3f17cacaeb3a7ba226ce", + "0x905ac223568eedc5acd8b54e892be05a21abbb4083c5dbec919129f9d9ffa2c4661d78d43bf5656d8d7aafa06f89d647", + "0xa6e93da7e0c998e6ce2592d1aa87d12bf44e71bec12b825139d56682cdce8f0ba6dbfe9441a9989e10578479351a3d9d", + "0xacde928a5e2df0d65de595288f2b81838155d5673013100a49b0cb0eb3d633237af1378148539e33ccd1b9a897f0fec3", + "0xa6e1a47e77f0114be6ae7acd2a51e6a9e38415cce7726373988153cdd5d4f86ef58f3309adc5681af4a159300ed4e5b5", + "0xad2b6a0d72f454054cb0c2ebc42cd59ff2da7990526bd4c9886003ba63b1302a8343628b8fe3295d3a15aa85150e0969", + "0xb0bc3aea89428d7918c2ee0cc57f159fba134dad224d0e72d21a359ca75b08fbb4373542f57a6408352033e1769f72c6", + "0xaad0497525163b572f135fad23fdd8763631f11deeaf61dea5c423f784fe1449c866040f303555920dc25e39cdb2e9b4", + "0x8ce5d8310d2e17342bf881d517c9afc484d12e1f4b4b08ad026b023d98cba410cd9a7cc8e2c3c63456652a19278b6960", + "0x8d9d57dbb24d68b6152337872bd5d422198da773174ade94b633f7c7f27670ff91969579583532ae7d8fe662c6d8a3b0", + "0x855a1c2d83becb3f02a8f9a83519d1cb112102b61d4cdd396844b5206e606b3fefdbcc5aa8751da2b256d987d74d9506", + "0x90eb7e6f938651f733cf81fcd2e7e8f611b627f8d94d4ac17ac00de6c2b841e4f80cada07f4063a13ae87b4a7736ca28", + "0x8161459a21d55e7f5f1cecfc1595c7f468406a82080bfa46d7fb1af4b5ec0cd2064c2c851949483db2aa376e9df418e6", + "0x8344ccd322b2072479f8db2ab3e46df89f536408cba0596f1e4ec6c1957ff0c73f3840990f9028ae0f21c1e9a729d7df", + "0x929be2190ddd54a5afe98c3b77591d1eae0ab2c9816dc6fe47508d9863d58f1ea029d503938c8d9e387c5e80047d6f1e", + "0x856e3d1f701688c650c258fecd78139ce68e19de5198cf1cd7bb11eba9d0f1c5af958884f58df10e3f9a08d8843f3406", + "0x8490ae5221e27a45a37ca97d99a19a8867bcc026a94f08bdccfbb4b6fa09b83c96b37ec7e0fd6ee05f4ae6141b6b64a8", + "0xb02dbd4d647a05ac248fda13708bba0d6a9cd00cae5634c1938b4c0abbb3a1e4f00f47aa416dcd00ffcdf166330bff9a", + "0x9076164bb99ca7b1a98d1e11cb2f965f5c22866658e8259445589b80e3cb3119c8710ede18f396ba902696785619079c", + "0xaacf016920936dae63778ad171386f996f65fe98e83cfcdd75e23774f189303e65cc8ad334a7a62f9230ed2c6b7f6fa4", + "0xa8031d46c7f2474789123469ef42e81c9c35eb245d38d8f4796bba406c02b57053f5ec554d45373ab437869a0b1af3f0", + "0xa4b76cd82dc1f305a0ee053e9a4212b67f5acc5e69962a8640d190a176b73fbc2b0644f896ff3927cd708d524668ed09", + "0xb00b029c74e6fdf7fb94df95ef1ccad025c452c19cddb5dccfb91efdcb8a9a1c17847cfa4486eae4f510e8a6c1f0791a", + "0x9455e5235f29a73e9f1a707a97ddb104c55b9d6a92cc9952600d49f0447d38ea073ee5cf0d13f7f55f12b4a5132f4b10", + "0xae118847542ed1084d269e8f3b503d0b6571a2c077def116ad685dcca2fca3dcb3f86e3f244284bdcd5ae7ac968d08a5", + "0x8dcb4965cd57e8b89cd71d6fc700d66caa805bfd29ab71357961527a7894e082d49145c2614b670dcb231ab9050d0663", + "0xadd6ed14f3183f4acc73feea19b22c9a330e431c674e5034924da31b69e8c02d79b570d12ef771a04215c4809e0f8a80", + "0x96ae7e110412ee87d0478fdbdbaab290eb0b6edd741bb864961845e87fd44bcbe630371060b8104d8bf17c41f2e3fca0", + "0xa20db17f384e9573ca0928af61affab6ff9dd244296b69b026d737f0c6cd28568846eca8dadf903ee0eecbb47368351d", + "0x937bfdf5feb0797863bc7c1be4dcc4f2423787952a3c77dfa3bfe7356f5dbcc4daebde976b84fc6bd97d5124fb8f85c9", + "0xa7050cc780445c124e46bba1acc0347ddcfa09a85b35a52cc5808bf412c859c0c680c0a82218f15a6daeefe73f0d0309", + "0xa9d9b93450e7630f1c018ea4e6a5ca4c19baa4b662eadfbe5c798fe798d8a3775ed1eb12bd96a458806b37ab82bdc10a", + "0xa52a4d5639e718380915daaefad7de60764d2d795443a3db7aeab5e16a1b8faa9441a4ccc6e809d8f78b0ac13eef3409", + "0x8e6f72b6664a8433b032849b03af68f9376b3c16c0bc86842c43fc7bf31e40bc9fc105952d5c5780c4afa19d7b802caa", + "0xa107ae72f037000c6ee14093de8e9f2c92aa5f89a0a20007f4126419e5cb982469c32187e51a820f94805c9fccd51365", + "0x9708218f9a984fe03abc4e699a4f3378a06530414a2e95e12ca657f031ef2e839c23fd83f96a4ba72f8203d54a1a1e82", + "0xb9129770f4c5fcac999e98c171d67e148abd145e0bf2a36848eb18783bb98dff2c5cef8b7407f2af188de1fae9571b1c", + "0x88cc9db8ff27eb583871eeeb517db83039b85404d735517c0c850bdfa99ae1b57fd24cf661ab60b4726878c17e047f37", + "0xa358c9aadc705a11722df49f90b17a2a6ba057b2e652246dc6131aaf23af66c1ca4ac0d5f11073a304f1a1b006bc0aa5", + "0xac79f25af6364a013ba9b82175ccee143309832df8f9c3f62c193660253679284624e38196733fb2af733488ab1a556e", + "0x82338e3ed162274d41a1783f44ae53329610134e6c62565353fbcc81131e88ce9f8a729d01e59e6d73695a378315111b", + "0xaa5ddcabf580fd43b6b0c3c8be45ffd26c9de8fa8d4546bb92d34f05469642b92a237d0806a1ad354f3046a4fcf14a92", + "0xb308d2c292052a8e17862c52710140ffafa0b3dbedd6a1b6334934b059fe03e49883529d6baf8b361c6e67b3fbf70100", + "0x96d870a15c833dddd8545b695139733d4a4c07d6206771a1524500c12607048731c49ec4ac26f5acc92dd9b974b2172c", + "0x8e99ee9ed51956d05faaf5038bffd48a2957917a76d9974a78df6c1ff3c5423c5d346778f55de07098b578ad623a390e", + "0xa19052d0b4b89b26172c292bbf6fd73e7486e7fd3a63c7a501bbd5cf7244e8e8ce3c1113624086b7cdf1a7693fdad8b5", + "0x958957caf99dc4bb6d3c0bc4821be10e3a816bd0ba18094603b56d9d2d1383ccc3ee8bc36d2d0aea90c8a119d4457eb4", + "0x8482589af6c3fc4aa0a07db201d8c0d750dd21ae5446ff7a2f44decf5bff50965fd6338745d179c67ea54095ecd3add4", + "0x8a088cc12cf618761eaa93da12c9158b050c86f10cd9f865b451c69e076c7e5b5a023e2f91c2e1eed2b40746ca06a643", + "0x85e81101590597d7671f606bd1d7d6220c80d3c62e9f20423e734482c94547714a6ac0307e86847cce91de46503c6a8a", + "0xb1bd39b481fc452d9abf0fcb73b48c501aaae1414c1c073499e079f719c4e034da1118da4ff5e0ce1c5a71d8af3f4279", + "0x942ae5f64ac7a5353e1deb2213f68aa39daa16bff63eb5c69fc8d9260e59178c0452227b982005f720a3c858542246c8", + "0x99fea18230e39df925f98e26ff03ab959cae7044d773de84647d105dfa75fd602b4f519c8e9d9f226ec0e0de0140e168", + "0x97b9841af4efd2bfd56b9e7cd2275bc1b4ff5606728f1f2b6e24630dbe44bc96f4f2132f7103bca6c37057fc792aeaab", + "0x94cdad044a6ab29e646ed30022c6f9a30d259f38043afcea0feceef0edc5f45297770a30718cbfec5ae7d6137f55fe08", + "0xa533a5efa74e67e429b736bb60f2ccab74d3919214351fe01f40a191e3ec321c61f54dd236f2d606c623ad556d9a8b63", + "0xb7bd0bb72cd537660e081f420545f50a6751bb4dd25fde25e8218cab2885dd81ffe3b888d608a396dfcb78d75ba03f3f", + "0xb1479e7aa34594ec8a45a97611d377206597149ece991a8cef1399738e99c3fa124a40396a356ab2ea135550a9f6a89f", + "0xb75570fc94b491aef11f70ef82aeb00b351c17d216770f9f3bd87f3b5ac90893d70f319b8e0d2450dc8e21b57e26df94", + "0xa5e3f3ab112530fe5c3b41167f7db5708e65479b765b941ce137d647adb4f03781f7821bb4de80c5dc282c6d2680a13d", + "0xb9b9c81b4cac7aca7e7c7baac2369d763dd9846c9821536d7467b1a7ec2e2a87b22637ab8bbeddb61879a64d111aa345", + "0xb1e3ee2c4dd03a60b2991d116c372de18f18fe279f712829b61c904103a2bd66202083925bc816d07884982e52a03212", + "0xa13f0593791dbbd360b4f34af42d5cc275816a8db4b82503fe7c2ff6acc22ae4bd9581a1c8c236f682d5c4c02cc274cc", + "0x86ba8238d3ed490abcc3f9ecc541305876315fb71bca8aaf87538012daab019992753bf1e10f8670e33bff0d36db0bf0", + "0xb65fbb89fafb0e2a66fe547a60246d00b98fe2cb65db4922d9cef6668de7b2f4bb6c25970f1e112df06b4d1d953d3f34", + "0xabb2d413e6f9e3c5f582e6020f879104473a829380b96a28123eb2bdd41a7a195f769b6ac70b35ba52a9fee9d6a289c3", + "0x88ec764573e501c9d69098a11ea1ad20cdc171362f76eb215129cfcca43460140741ea06cee65a1f21b708afb6f9d5b0", + "0xa7aaec27246a3337911b0201f4c5b746e45780598004dac15d9d15e5682b4c688158adffdef7179abb654f686e4c6adc", + "0xa1128589258f1fbfa33341604c3cb07f2a30c651086f90dce63ae48b4f01782e27c3829de5102f847cde140374567c58", + "0xaaf2b149c1ca9352c94cc201125452b1ed7ca7c361ed022d626899426cb2d4cc915d76c58fa58b3ad4a6284a9ae1bc45", + "0xaaf5c71b18b27cd8fe1a9028027f2293f0753d400481655c0d88b081f150d0292fb9bd3e6acabb343a6afb4afdb103b5", + "0x947c0257d1fb29ecc26c4dc5eab977ebb47d698b48f9357ce8ff2d2ed461c5725228cc354a285d2331a60d20de09ff67", + "0xb73e996fa30f581699052ed06054c474ebdf3ae662c4dc6f889e827b8b6263df67aeff7f2c7f2919df319a99bdfdceb1", + "0xb696355d3f742dd1bf5f6fbb8eee234e74653131278861bf5a76db85768f0988a73084e1ae03c2100644a1fa86a49688", + "0xb0abca296a8898ac5897f61c50402bd96b59a7932de61b6e3c073d880d39fc8e109998c9dba666b774415edddcff1997", + "0xb7abe07643a82a7cb409ee4177616e4f91ec1cf733699bf24dec90da0617fe3b52622edec6e12f54897c4b288278e4f3", + "0x8a3fae76993edbc81d7b47f049279f4dd5c408133436605d934dee0eadde187d03e6483409713db122a2a412cd631647", + "0x82eb8e48becfdf06b2d1b93bf072c35df210cf64ed6086267033ad219bf130c55ee60718f28a0e1cad7bc0a39d940260", + "0xa88f783e32944a82ea1ea4206e52c4bcf9962b4232e3c3b45bd72932ee1082527bf80864ce82497e5a8e40f2a60962d0", + "0x830cf6b1e99430ae93a3f26fbfb92c741c895b017924dcd9e418c3dc4a5b21105850a8dd2536fa052667e508b90738f2", + "0x990dce4c2c6f44bb6870328fba6aa2a26b0b8b2d57bfb24acf398b1edc0f3790665275f650884bd438d5403973469fa2", + "0xa2e5b6232d81c94bcb7fed782e2d00ff70fc86a3abddbe4332cb0544b4e109ae9639a180ae4c1f416752ed668d918420", + "0xb4cdf7c2b3753c8d96d92eb3d5fa984fef5d346a76dc5016552069e3f110356b82e9585b9c2f5313c76ffaecef3d6fd8", + "0x83b23b87f91d8d602bff3a4aa1ead39fcc04b26cf113a9da6d2bd08ba7ea827f10b69a699c16911605b0126a9132140f", + "0x8aae7a2d9daa8a2b14f9168fe82933b35587a3e9ebf0f9c37bf1f8aa015f18fb116b7fba85a25c0b5e9f4b91ba1d350b", + "0x80d1163675145cc1fab9203d5581e4cd2bed26ad49f077a7927dec88814e0bed7912e6bbe6507613b8e393d5ee3be9be", + "0x93ddeb77b6a4c62f69b11cf36646ed089dcaa491590450456a525faf5659d810323b3effa0b908000887c20ac6b12c80", + "0x9406360a2b105c44c45ba440055e40da5c41f64057e6b35a3786526869b853472e615e6beb957b62698a2e8a93608e13", + "0x93bfc435ab9183d11e9ad17dac977a5b7e518db720e79a99072ce7e1b8fcb13a738806f414df5a3caa3e0b8a6ce38625", + "0x8a12402c2509053500e8456d8b77470f1bbb9785dd7995ebbbe32fd7171406c7ce7bd89a96d0f41dbc6194e8f7442f42", + "0xaab901e35bf17e6422722c52a9da8b7062d065169bf446ef0cbf8d68167a8b92dab57320c1470fee1f4fc6100269c6e2", + "0x8cad277d9e2ba086378190d33f1116ba40071d2cb78d41012ec605c23f13009e187d094d785012b9c55038ec96324001", + "0x85511c72e2894e75075436a163418279f660c417e1d7792edce5f95f2a52024d1b5677e2e150bf4339ad064f70420c60", + "0x85549ca8dcbe49d16d4b3e2b8a30495f16c0de35711978ada1e2d88ad28e80872fca3fb02deb951b8bcb01b6555492e4", + "0x8d379ab35194fe5edf98045a088db240a643509ddc2794c9900aa6b50535476daa92fd2b0a3d3d638c2069e535cd783b", + "0xb45cfebe529556b110392cb64059f4eb4d88aaf10f1000fdd986f7f140fdd878ce529c3c69dfd2c9d06f7b1e426e38f3", + "0xac009efd11f0c4cdd07dd4283a8181420a2ba6a4155b32c2fed6b9f913d98e057d0f5f85e6af82efc19eb4e2a97a82df", + "0xb2c2cdffa82f614e9cb5769b7c33c7d555e264e604e9b6138e19bcfc49284721180b0781ecbf321d7e60259174da9c3c", + "0x95789960f848797abbe1c66ef05d01d920228ca1f698130c7b1e6ca73bfda82cee672d30a9787688620554e8886554ee", + "0x98444018fa01b7273d3370eeb01adc8db902d5a69b9afc0aa9eadfeb43c4356863f19078d3c0d74e80f06ecf5a5223f4", + "0x87d20b058050542f497c6645de59b8310f6eeec53acbc084e38b85414c3ea3016da3da690853498bde1c14de1db6f391", + "0xa5c12b3a40e54bee82a315c503c1ce431309a862458030dde02376745ec1d6b9c1dbeea481ae6883425e9dae608e444e", + "0xb9daa3bf33f0a2979785067dcece83250e7bf6deb75bb1dbbab4af9e95ddfb3d38c288cbef3f80519a8916a77a43b56c", + "0xb682ec3118f71bde6c08f06ea53378ea404f8a1c4c273dd08989f2df39d6634f6463be1d172ac0e06f0fa19ac4a62366", + "0xa4f94fd51ecf9d2065177593970854d3dce745eebb2a6d49c573cbf64a586ae949ddfa60466aaef0c0afb22bd92e0b57", + "0x86cd5609efd570c51adbc606c1c63759c5f4f025fcbefab6bc3045b6ad2423628c68f5931ff56fdda985168ce993cc24", + "0x981192e31e62e45572f933e86cdd5b1d28b1790b255c491c79bd9bb4964359b0e5f94f2ae0e00ef7fe7891b5c3904932", + "0x9898f52b57472ebc7053f7bf7ab6695ce8df6213fc7f2d6f6ea68b5baad86ec1371a29304cae1baadf15083296958d27", + "0xb676c4a8a791ae00a2405a0c88b9544878749a7235d3a5a9f53a3f822e0c5c1b147a7f3f0fc228049dc46e87aa6b6368", + "0x9976e10beff544e5c1645c81a807739eff90449df58ffdd8d1aa45dd50b4c62f9370538b9855a00dd596480f38ebe7a5", + "0xa0e91404894187ec23c16d39d647ada912a2c4febfd050a1ea433c4bfdc1568b4e97a78a89ba643aca3e2782033c3c58", + "0x91a6ea9a80476ed137eb81558ff1d55b8581663cccd41db4fc286876226b6515fd38661557419e1e46b6a3bc9cda3741", + "0xb9e8a1e23c60335a37a16f8085f80178a17d5e055d87ffe8cf63c532af923e5a5a2d76cf078164fb577996683796caa6", + "0xad8e151d87a37e8df438d0a6a7c02c3f511143efb93fde8aef334d218cb25932baf9e97c2f36c633620a024a5626af3d", + "0x978f942f210e8a482015e6fdc35a4c967c67b66e6e2a17a05cc7a0f2163aed227b775d4352b0c3cca6cbf4bd5bafaf75", + "0xb5e2e3d8b2e871c07f5899e108e133f87479959b80cb8a103fbecde00ccdbfbd997540eef33079c5cc14b1c00c009fd1", + "0x88a164b3fefd36857f429ab10002243b053f5d386466dbb9e5135ed3c72dd369a5a25e5e2aaa11f25488535e044e2f12", + "0xa66091c0db4e7cf05a089ec2b9ff74744354d0196968201f5e201699144b52bb13b4e68e12502727163e6db96e3565f2", + "0x8e65aff8e37240461b7374c20bfd1d58b73a525c28994a98f723daed9486130b3189f8efe5c5efcd7f5390cc366038da", + "0x8b37c21dd7304c3aa366959ba8c77ea8b22164a67e136808b6f8e48604297f7429a6c6ecf67b1d09b8b7ec083eacd7e0", + "0xb689b1277ad050f53da91a702516a06d7406ff33a4714ea859b3b2b69f8d0aa8f983c7e039b19c0759a3815d841fa409", + "0xb17f7a0a182ed4937f88489e4c4e6163dcf49fd2ea4d9efbba8126c743bea951cd769752acd02e921774dc8ebcfae33b", + "0x8b7fab4f90be825ac5d782a438e55c0a86be1c314a5dbc3cc6ed60760a8a94ef296391f1f6363652200cce4c188dae67", + "0xab8410c4eaa2bb43b0dd271aa2836061bc95cb600b0be331dada76ddb46711ff7a4ad8c466cc1078b9f9131f0dc9d879", + "0x9194bd7b3cc218624459d51c4d6dbc13da5d3de313448f8175650fa4cfab7cc4afcda5427b6676c3c13897dc638b401e", + "0x980f61a0f01349acd8fc9fdc88fc2c5813610c07eecb6ab14af0845a980792a60dadf13bb4437b0169ae3eff8f5984ce", + "0xb783bee24acea9c99d16434195c6940cf01fc2db135e21f16acae45a509eca3af6b9232a8aa3a86f9715c5f6a85cb1c3", + "0xa3079931c4b90966d1faa948db847741878b5828bc60325f5ebe554dcab4adcc19ee8bce645e48a8f4a9413bb3c6a093", + "0x801f61ac9318f6e033a99071a46ae06ed249394638c19720831fff850226363a4ae8486dd00967746298ee9f1d65462f", + "0xb34dbbed4f3bb91f28285c40f64ce60c691737cc2b2d2be5c7d0210611cd58341bb5bda51bb642d3ee2d80882e642a13", + "0x8750af19abfb915e63c81542b13d84526a0c809179bbcc1cd8a52b29f3aba3ae0f7cf6f4f01790bf64ef7db01d8ee887", + "0xa6ea10000eb2dd4efc242ac95bc3b3873cdd882fbeb7c9538c87e3143a263ca3a2e192b2159316a625cfb5fb0b6cdcb3", + "0xaa40ca54bc758a6c64cb932924917581062e088b3ad43976b28f2e11d8a7dea73f1fb50aeaa0e70182bb2dc07d805bb9", + "0xa4779dfd25b5ec9d75dfb54a4bb030364899a5e75c1492403acb19f2adc782c7ac4daeb66d2f5aeb74135afe9f318e3f", + "0xb4551e2805d63ca453f4f38b1921ac87ff687e1d70575ad38f3469d6f0608ef76b7b1b98ae1e6b1e7d928773aaab6e3b", + "0x99490ee722f96aad2743b08dd37bfeb75a8c59efaee4c9b694eaa05eb8a6bb23861a4480544c7617d04d23fd5e2543b4", + "0x8a7050d964d295fff98ae30d77ce730a055719313457e773fcce94c4d71a9b7cf63db67e54a8aab20fb1335b0130b5d5", + "0x903144e6bbee0a4fec17ff80fef0d2103981140c3d41776cfb184ced17f480a687dd093f6b538584327e6142812e3cd5", + "0xa5b30f7c6939bdc24a84ae784add927fec798b5a5ee3dd156c652df020728dd6d43898be364cf5ee181725fbcffc0964", + "0xb43d97ec2bc66af92d921a5c5c20a03ef2be2bc2c9b345f46d8287409fcbfd88ebc49d4509d64468222cd1d2021bf236", + "0x82dc23c7f5086c9ac6b4566359bfb830d203544b0d8332a210775670f899cd9ff48b94bfeba40040c25664ebdd5cfad8", + "0x9294cd017fea581dabb73dcc8c619904d7e022b664b0a8502c9d30f3807668af279948e7e41030ae296d492225297e95", + "0x8d6c9dc636c8e884f9a4299e5cff06d044ebc94ad783a4b71788347ea4a336d4d048b8a9ecabae789e8fcdc459723dfb", + "0x801a80bc49e882ec81b04e37407713f033f7bdac79252dfa3dc8c5bd0229fcbd4019890e402cf843b9378df08f72ab84", + "0xb4313ca32569d973900f6196363c0b280ddfa1b47c88d019e5f399b805b444a777950fc21ae198fc23ece52674b94abf", + "0x96f06056fd255fdabf78986e315e7c4fdf5495cf850536b7976baa97a994cc6a99c34609c33a0f2facba5e6f1026dce6", + "0x983ed80220a5545ffd70ef5e6ac10217d82ec9cd8f9a27ee77a5ff4074092308c0e6396fc4e9932a77ddd474e61f8b55", + "0x872a059aa630af73c4abbd076e8b333a973ffc5bdecf5dcc0600b00162184213cb19d4f601795030033beb808d5810ce", + "0xb040f318d9d3b8833da854014a44296dbd6762dd17cab13f91987256c54353b7f0800547cb645a7cc231997454209fdd", + "0xa8c4731a555308e8ce0b8325eb7a4cbf6113d07e9f41932df04480b72628d313b941c7055f1cc2ac45c7353b56e96ca9", + "0x8c24031440b77637e045a52e5ea3f488926ab0b426148975edf066c40a4581beecc1bfb18fc4cf5f9f96dc6681b4bd28", + "0xb39254b475abf342f301298feaa17a4b3051f30ea23a18acf59e003e2704ac96fe40691f1da387913bdf7aee6389f9a8", + "0xa1dbf938b604ccc6d60881cc71f38df568aa02752aa44d123514154017503f6c1c335ae43e359f1487bc8934073cd9c1", + "0x8d52aa1be9f429ece0580498d8fe9fef46d4a11f49436a82b8927f9503dacc41245907f126594c1cd30701286f8c092c", + "0xb826f396486942c0326d16f30a01b00a682c30a75553dc6ac34fd5b3e96b13c33b94738f522eebaffb59ff8c571c76e9", + "0xaa89f51cbf6e6c3e2aa2806187b69ab3361c84e89f393f3ed284fe84db46fc3944aa44f8928e3964f9c1a1ec27048f68", + "0xa254df0efa4203fb92b42a1cd81ca955922e14bf408262c8f7cb7dc703da0ca2c71556bd2d05b22ce9a90ad77309833d", + "0x93263c507e4d5f4e5df88e85b3d85c46ea729fb542a718b196333e2d9fb8a2e62dc1347cf146466a54ba12d200ef09d9", + "0x922e3c4a84246d89a07aa3e90f02e04b2cea9bebc0e68b742156f702aed31b28c6dfa7ac936ea2fc2e029adf68361f98", + "0x9a00628eeeda4ccbed3ef7834149aec4c77aac1a14bc2491ba5d1a4a2c5d29afb82ceaa5aac1c5ce1e42cdcaf53e30ba", + "0xab3a88df36d703920f6648a295a70ffa5316c96044f39ff132937bfda768937cb6a479e9ba4a4e66b377f3a9996a88c4", + "0x966b11526ab099d550ab33c6a9667e5cfdedf255da17a80a519d09acd78d2ea24ec18bd1ea7d8d63cf0a408f1c1fe0b3", + "0xb5c21b9817dc32f3df9d9988aa3560e1e840d586d01cd596bc0f850ab416b6013cbf7dbfd05ac981f26014c74bd2d2b2", + "0x9040abef5e2523e7f139c9f744a64b98fea3a57952059ffe4d5ed77fa87068203c090ef4e7f52c88fb82ea8a6fdca33e", + "0xa0dcdaeb7d3f5d30d49c004c5f478818c470187f4b0b4856812dcd1b3a86de58a99acb8ceb44c6b80c3060cf967c43a4", + "0xb5f4be9a69e4a6719ea91104820df8623b6d1073e8ee4168de10a7e49c8babea772bcbc6b0908185e98d607e49cd3609", + "0x8634020a5a78650015763c06121c606d2dd7b324aa17387910513dd6480fb797df541fc15b70d269b2794ad190595084", + "0x9504d1d0fb31ff1926c89040c04d51fd1f5cddf9d7ca3d036e7fd17e7a0f767ef33cee1d8bf7e17e2bc40949e7630417", + "0x812c72846ef6d692cf11d8f8c3de8fa78cc287303315114492667b19c702cd24d462020f1276895df26e937c38f361f8", + "0x8c97aa5e9ef2aa9a1435ef9ddfe62e850f0360864ed5fb82bf9fef4ef04d8fb4f827dc078bc911ee275e4501edd6617c", + "0xac5f7af5e23c8e429aaa6b6825129922b59d25b4608f07b65f21388a9ac3aa89096712f320afe6d56e44e1f0d51a4eb9", + "0xa8c84d9a8593a0cb5be1e450960f59878a4e6b70da54a7613dfc25911b7cc9e6d789d39401b0a0d6471ab9dcdc707976", + "0x8c9d5fd89611392c0f085ffa4fa642a181f0b9b23593deb5e10fdd1642722ca75ef34a037e88a8d03f2888fe7461f27c", + "0x8c74b05f91fb95c85e7bd41f6d9a1e41e667e68f3d19b325c1f25df1767019919edab89b92af237896cbc4e6d6dc1854", + "0xa3caecb91640821f0b2c4981b23f2069df8d2b98ce026c1538bc096b292f5f956a5d52c1c8d6a8165a1608083ba6494b", + "0x8ae8e0c36f8b79a69176ff29855df45d0fcd9e4d1dbaed8899f8fcdece676e418ec034a6c161e2a894f0c834aaecbfd1", + "0xb88d18c67dc3b1b6ed60ee437c441c1ed14ecddebccf43683605716f30058b1aa4ba05ff10cd8171ee97d8f58d70c094", + "0x94f43d84dcdfd9cd19115c7d8e9c1e856828eafbfdec93b876cf0007e317e30b2ad951dbabc186aa6ef90fdee4d91990", + "0xb44e4723f41fc1d5b0057f371e3381ae02566590b3f964b6eb07b2104f66ff78410c407235fa98d04f635694f3baca09", + "0xaddd8390173d29ca0811534d389253831fed75fed135398617836b6e70767269eacb1560b39a58f02042ca3b97fe59c4", + "0x80bdbdacc0c358c7ea52aeacdc5f9ceb6928bcf6e7dee7c17d8ae3bf7c2372aa7a0372363888968fc0921aaf4776d5d0", + "0xa486e2b6f04f403f9e609d69dfb3cfb992af56ecad1683271df3e3faa3b86638b81e73b39978fb829ee7133d72901f2d", + "0xa19472da57457e10c6a6307895393ddaec8f523760d66937fe26a025817319e234eaf69756ffdf1b84c81733424a96d7", + "0xad6a195397cbc2d75171f5e82090441eed60bd1ba42c39ef565b8b5a8281b04400678625b1dc46d617f694a7652a8e5d", + "0x8f98e721c06cec432e2221f2e1b06bb1469d916a8d88d6973acf68d1e003441d00390dafcead8ecdbf9eae4509baf5aa", + "0x91d62a0f9d13c59adfe1376ed6d057eae244d13c6b3d99be49a49e0075cf20f4085cf127774644ac93615be9ac9e5db6", + "0xaf45dec199245e2b326a0d79c4899ed44b1c0219db42602a4a6184ace0ff831a3276297af28f92e8b008ba412318e33e", + "0x8754bde54e8d2d169e6a7d6f0eae6097bc0461c395192bd00dd6f105677ea56ab384c02553ea5eeac0a65adcb0df77ee", + "0xb676afd2f5afc37a314c943d496e31b4885efcbcc2061036e370a74cfde5642bb035622d78d693bfc3136fc036c7edb4", + "0xaab6ffe6cc234397cf1822e02912bc282dfb314e92fb5a9e10d0c34ee9b5856d4b76e166bc2bb6fcdd66aabea35ec4ef", + "0xada6e62f90ee6b852ec4b72b22367acac2896f0df2c105beda27096583ddbedddc710d171330569f111c6e44a5b57ae7", + "0x802139dd15241a6de663d9b810121bdd9cf11f7f8c8ca6de63f4f8e731409e40d1fd3558b4f619ed42ee54929dff1c7e", + "0xad8e70531cec21b4e6f55be1751c2d025bd2d7d8158269b054cfe57fa29252d052ce4478ec7db6ec705789e2118d63b3", + "0xa8e4a4271769480e1b33a28c87a150ecc0b48bfe8a15ae04152197881de4ce4b03453aefe574842424edbbe4173e1a3a", + "0xb98c65726296610cef16c5b58da5491acd33bd5c5c5af4d934a9840649ef85730fbce8018dee09ded14e278009ed094a", + "0x8e213a7861223287b860f040e5caaa563daa0b681e4e09ec79ad00cc459238e70bbeaf7486bbe182fc12650700034ec5", + "0xa2879f9e1a556cf89b9b5b3bd8646a8cce6b60bcbc8095df44637f66a2da5858eee2dc9091475a8f64bb5aff849389cd", + "0x8a17cdb4077b9b0bcf28b93294ac5ae4c8bba8839fce0f1012b53187ac008f9858b02925fbfc421f1123afcdbd8b7753", + "0x86fd9c11528aa43946e4415ff64a3ca6409ee6f807368c68997b18605da65e415ccd85ad913820d450cb386593de666d", + "0x8ed55923b963c3d85a91aca11c40ff9c6c7f1e2b9bc199d1a270e5fb16aa62dec0136e97866145ae9d58a493e8b1cbbb", + "0xae32af5b5d418668ae123c639b149e5eed602404e8516da4a61db944b537a3620545e8e3d38cf10cdaea980ab2f80973", + "0x95cb8d9e9d6762d78dde0ad73869ffaca904a7d763a378b8cc11a7933d3e7d1c8aec4271a079b1b00f8887ee5b1ea21f", + "0xb5ea20b42a3ca247f00ab5328c05f0cf194973d5f7271c66c41c5055b1ffdca136be179709e0c1de209fbe07b9820bf3", + "0x98682f7cce471c92a8d6d15fee4ddf4d43dd97c3e3811d2913618ecacc6440b737717c07736ae4558c910e11ee98104e", + "0xa67da2c7cbba48e929ca4e4b9a6299fe01ef79eff8cc5cd3fdbdc0721a68130e4079f30ae151a573a7dcca8ecf2e684e", + "0xa9981c9f9dcbb3b0f6996f664fb2acd7573189f203be37b2b714662aa273551396abfb1f612ccde4e4c8127a050dbe4b", + "0x92d55eff8da600f886da9bf68e8eecf482faa4b268f3f286b3b3e5cc91b19604081498d4905b201bb4ec68e32b5591d9", + "0x963e3f1728de9d719c86d390f3eb9c3f99d1928347fab0abf10dbb37d76b59ddb64d4734c977863a6cd03ffece5ca895", + "0x93480e2de83c921056b6d8628ac37cd5ef7555ba43b0308fc13386cb0515d42c12ecd06057137aa71a7931beaf90b9ce", + "0x8feae57ff0e6a162cc81c99f45c6187d268fc0bee8c2bffc92142ef76c253d201f0e932943cf2fa312982b281ce1066b", + "0x8f8f4bd4200fb87afcd743274480220d77571928000d4197410dbb75439d368df6a06d941a6152206371d2ca9cac99e4", + "0x8ee7f11e79af4478e0a70eb424fe8078237ad99ba6d7e6bf1a8d5e44e40abd22d404bd39b718ad6fdf4c6601f2a47665", + "0xa98acfcec612b574943195b9ba95bebcc9c0b945c9f6b3e8760b2a4635909246a9d73b0b095c27b4ecb3339704e389b7", + "0xb520efd19f65e81dc285031ea3593f8c5dad793e4426beb9196ab46e45346f265fd71e50adb0da657977c60ed5724128", + "0xa3d9d0b7415280ce4dfa2429d47b2b8e37604a5157280a72cc81d541ffe44612dbb3ef7d03693fc42a569169d5842dc3", + "0x8c29e2d0b33801f6d9a9c065a76c5cad1fb0a001506b970307e21765ee97c732a4cbf1d7c1b72d95e0ad340b3b075224", + "0x839e21f292892a6eb596b9b1e9c4bd7c22a6fe71d3d04487c77840028d48392c5cbe73140a4e742338e0c8475cd0c1ad", + "0x8bea5c68e7743998619185bb662e958f1b4d3ca81019d84ac43c88911aab3abe4ee9bcc73cb95aa3ae87c0138801bde3", + "0xb8f262d21a94604049e008ce03dc857848168e1efca4522acb0ccc827ffb37f545e1947843a356563a76bc6489605b66", + "0xa7bd0842b0bb38d9943b82aa883f36f4eb8a6e8a7790d4f87faf306608f51d250a19b73984f1156cef5dd2581664614b", + "0xa993e649bd953627a88a2539dac3a12ec7f37a4c65b01425d9d34edf7ee10a71aa98f65c9e013107f824faf8aee041a9", + "0x8e07eced75c67cb4d2ec01857f6ac1408482e6b31cb2faa249e8cf99f180575587df530c7782a7539b5221121ef48aa0", + "0xb2f4578f26c05ecb9e2669ca744eb19d4f737321ac7d04fafd18beb7866e0fec9dd063953ae1f077b44b9c6f54db1279", + "0xb6b3788a6c7bcaf467d19daf6ab884d549aa866970c05a9181f544ff190d043192c84fe437a75a30b78b425461cca062", + "0xa270684903c61544b85a7041e81f65e787e1c1e23e57538fa8a69836bed0ca1673861dd29f743a1280f2f38eddd3aa83", + "0xa9c2397c4773dcad2821266dadfd2401d013d9f35de6744f2ec201f3507700adb1e6ec4f5a453be4764da8bf68543f26", + "0x83a3025ed6fd5df9d98be32a74e10a0d9728b560942d33ba028536fb148fc34ae87e92be2df3e420a8dfec08da495982", + "0x90dc70c183a90bab988b4a85b7b921c8070af0e5f220364fe11afa0722990b2c971e1e98eef62d3287fedfd9411f1df7", + "0x82d940937a6c636224d04f8e2536f93dcf20dc97a5f188875ad76c21b804aef9af10839419b61143c1f88a695959a6b4", + "0x8017f9473ce49d498d6f168137e77e62fe553e5a51e75b519cf2cbd1ab9afdafad80fd5e6fd0860e640b0d78ca8ed947", + "0x80573a0ec049fe1f7b3013b2839e145cd87e07c0e43826a29ef8c92516f9a30896c2ffcf3ed77ed22a6cf3101b1789d5", + "0x953349abd2559f9824db07cec857ad54f1a05018f3076425f8dbae37f8d92a46af2c04ab7c8ec0250449541187696e98", + "0xab7bd2c4f05ee9a9f252c4e16a20993a12c535c3809d124bae24642616521a9768d3f19eceaf8524583f47ae1f527684", + "0x9883b77ee834ee0112ca2f366d2a6fc213e0cf454e061438c2901a5ba35b7378f64da8adf6a476eb1562991ef5b4a5bc", + "0x89291811db308637356dbf7ed22cf07bfce33eb977734ee346e8c15a231b35d8b4443574f3fa97a40867b3e23b0bbfa4", + "0x93d753849d7d9588d39e38217500b123a6b628a873876612d9f98b5d611f52c89c573432d2176752b5d1cc2d94899b8b", + "0xa45add3c4844db3b7a237295fc85fddc788ac1ec395a0524d2fc90a539571a247146aea4aa10eec30a95e9617c85b98d", + "0x90f94578842db7a4de672da1e483858ece5e466c73c12f725a0fc71f42ff880c9447a33fa9096839bee817536f2591e2", + "0xb2c1b6fb031bb30460f157356562b44b4de096a0a112eab4fb3cc500aad38bc770da1fc2e73caf687a0da5e8537049c0", + "0xafb15e15fd930929c0e3c66482068a5afe0c7b7f82e216a76c5eb1113625bfa0b045a52259d472284cfbaf4796c71456", + "0xad222a9a3d907713418c151b8793d5e37634354322068f8206b9d0da1a3f53b0004193713d23ec35990639a1b6c2e075", + "0xb44a128dce97e8c4b178cdbca0a5c1b3f6e164490fac0fd68dbfe0aafa89920bb4ea420a8527e06c80dd19c2f135e3ef", + "0x8596e993ef18b8d94e9c42a90cb7060affc586b8e9b526820d25124285de5590134e2e86592e9dc4dd45ccf5d578fa60", + "0xb71bb0ad138141ed506b2253e84110d2db97cc2d24a3fd0d096b0022d9f38f87aa74e2f505074632d64e90bcc491aa30", + "0x84841eafd357309de47b92ca5ec163dec094a2e5271bc65898c31932e0160bee165e4decb23af339cfe09c83e1cc5441", + "0x8a2915ee39a6fd4a240b98533d7690ef1773ce578ed1fb05ed414ebe36f7ef289fa46f41768df57190438c356331e329", + "0x90bb337165386f1990cbd8ed2e8321ef21bc18125b015b4da0c37e5fcc446b26005379ee4fad8ce9348ceb4ab49e82e2", + "0xb707b50ea2ab05c6d183671587f25fe29eef23fe569d731459a1ac111a0b83a2cd65b88242876b34aeead3b05a15d745", + "0xae1f159f79b7996315c4f9acce7e21a6ed59d4ef76331196fc86911fda3035edd5c11d568b105175a36c948d0263b382", + "0x922bc525bace05e5dff6b5cabde5469ddd2c1c601f7131abc04ecefdd35095e6ac015b1aec3c3b25c5dee8d139baf60d", + "0xa7b060405b2740f82db64683187b1bb89e5f40c8438663c7cbc8ef2513929fe5f92625667a7f2f599a72a96b1fc8f08a", + "0xb9dfe94a08651db5efefbb813269bce80d814e3089b80c0654491e438d820bf521f8a4a4477909344ba88f7683eebb43", + "0x841817a9729465743576950b6e8eea32ebf39cca99ace86c4792f9f35926e2d6830c52854a3b2eaeb61694e6845008bd", + "0x934128034bde8fc7b93b952aa56e0ed28b36cfa04cfa1f0d5b38266dd40beedff5e0bab86e4717b0fb56c56be2eae26b", + "0xaee9d64caf28596308782cd8f3cf819506daf3378f86157ff775e618596411adf94efd0e9542787ca942066f02cbd332", + "0x85871184db314411a49575fee088c52ed5dba4e916ee001ec24d90898a0154d9790a06aa8a707ca7a8b986c0293b8d89", + "0x8d3d87edcc0187a099c97b581a598d357a41ac152303bb27c849eb78e72e15cb97cf9a0468fc36f245c3e152c76bb7dd", + "0x900475d165dec18b99eb7b5f9e9ad1d2d4f632e55fdcc4c5ecd7775fed462990e6aaafe9c669f40508f9b15f00bda31f", + "0xa25b5954edd57e7811a0d18532043d975c7b44b80f65cd630935d7b16ada05f30fe2b7be7ae8a2f54c25957faf3f1950", + "0xa089019afa3a7a15f7e7874e73b6773c0a824e6d3379b4c928e173321fb165ad979a6be004d394c28d19d410b2655d3e", + "0xb28f46797dee0c538bd3de815df641a0ef718ad3e52b2764aec380d6905b38b50ad6f60d0f68e096ca39960ba7734355", + "0xb0ac155d3d05851b04104e6b459f1a68e9e155437c92421a7c0e4dd511ef89cf71dfa3cc920769492ee283a65ebf029e", + "0x813c69a810745580d43d5b5480f0ba81000fbef0071e6b655c7346bef5ed774e9214a7816d40eb1774a5bd033767a046", + "0xb176345ca75c64f10ec33daa0dcf1f282b66a862fcd3d8d66c913f9a02db4c9d283dadc02eff13aaab94bc932a42234e", + "0x92560f67e5b995db4a489bb86ee78b4aee0800143b3535ad557a53e9e08716bd0202d9f5714722c2a5e8310046e3f5b3", + "0x8adb427bad9cc15fc6c457a96a6750dda8c46d859c5f69bf0e7ab8fc0964430b33967fd47cf0675b6ba1757f91255e6e", + "0xb120f723b80389a025b2daa891b140b3d7b8d520ae2a6a313f6e3d365a217af73292dcb249dca1f414ec05e865e3cdc7", + "0xa61a5d261a8dfe5996c42ea0a5ae703a2adcfda80e86837074d868eee16f87d38da19596c48b55dbd7a7cbec1a9b4996", + "0x99dc921eacc6bb867c5825ad4c83bc4af9dd78a18b3d0e1a60ad493e3805b8fb9b7922b577da1adb3d805edfc128d51d", + "0x85455fa165a07282aaab4a5bfb88027f47b9532e4af8195c048515f88b0db7e80f42e7a385fd4944faaa7f2a6544ad17", + "0x96dff2d1c8a879d443fe576d46bcceaf5f4551d2e8aad9c1a30883637c91090de99ad5eec228eb5febf93911502d3cbb", + "0xa87eb7f439377fb26c6bfe779701f4aea78dd7980b452a386afec62905e75217a1996c5234853432a62ef8bab21c31c3", + "0xb598278293823e9ccb638232a799211173b906444376337fdf044d0227d28fcc4c5867e6ecb3200e59ca0b139e71cac9", + "0xaa6fe147edc95027654d68140f428ec53cede3552c5f49c09d18bc6f6ae8c739a63042eb7291d14d717a4e1f0778abcb", + "0xae8ee18913d328b2fba71efe65526d3ee9c81beda53cf776baec4019ea30212010758cbb5dc85ed6620ce04b189f01f2", + "0xae9fb686777e88dffdd42805fe4114aa0da1b350d92a27ff3f8a817fb25af1fcfc9a06155affe0273bf13caad16a5351", + "0x95d372ba3a2ee38371538f34aae91b4844488e273f70c02f1992370f89fc2343eff95692d52ce9f21206abbee4959958", + "0xb15260376f0a34ca2827ff53acd7eaaef94c9acc2f244b36500423069cb1cdaa57ac8dd74adb5b53d0fd4265fcbb28ea", + "0xb0ffce6a8059537ef6affdbbc300547ef86e00109289239b0c6930456c562b4ed97f2e523963af17736dd71b46c44ac7", + "0xb5499a1277d34f9892f7579731ff53f423f2ffffa9ea43a6e929df8c525e301396249a2324818a6a03daa0e71fcd47b3", + "0x98dbfb8e97a377a25605a7665d4d53e66146204d8953afda661ae506858c5cd77ff7f21f5f10232e06dbc37378638948", + "0x84177e27e6da0e900c51f17077f5991e0e61bff00ca62c1623e627c5aea1b743f86eef6d55b13219a1947515150bade6", + "0xb50407bb5c61b057ab8935df94fd43ca04870015705b4f30ceac85c1035db0eb8293babc3d40e513b6fb6792ecbc27a9", + "0x988699a16917514e37f41ab5c24f4835ed8a2ca85d99972646fcc47c7e2a83c2816011144a8968a119657c4cda78d517", + "0x920c43fdcb738239ad542cb6504ab34498bce892311c781971d7db4dec70e288676de4d8697024b108cfa8757fa74035", + "0xaaa106329aac882e8d46b523f126a86d3cee2d888035ce65c0be4eaae3e92fd862f6ac2da458a835539cccafaba9e626", + "0x96e4c1562d14b7556f3d3e8a1b34ea4addc5a8170e1df541dc344728bcb74cd1630eb7ba4c70e9c68fd23c5c5d5a729b", + "0xa616ac5016d4e68e03074273cd3df9693ee0ce3458e8758b117a5c1bc6306dd2c7fad96b1bb37219c57ac62c78ad7a3e", + "0x8db7d9b20abfb1445babd484ae9e38ff9153ac8492230d7591e14e3fca7388a5ca6ef7d92ed445c8943cf5263e4a6ad7", + "0x88464134221aa7134878eb10928f31c8bd752ab68c27c9061c1de3f145c85731a4b76acdc7e939b399b6e497f9e6c136", + "0xa5f7c794f70b7c191c835dded21d442b6514bab5e4d19b56f630b6a2f1a84a1d69102d7a0dcca256aab5882d3f30f3ca", + "0xb96b6f98b6817b5fa6b1b1044e2411bdf08bf3ffaa9f38915d59e1d2b9bed8b3d645eee322ee611102ce308be19dbc15", + "0x92c26ade2e57257f498ac4ff0672d60b7ea26dad3eb39ed9a265162ccd205c36b882dba3689758c675f29e20836b62d9", + "0x8379a0299e75774930577071d258e89e471951642b98e5e664c148af584d80df4caa4bd370174dae258848c306f44be5", + "0xa0e53beda02bd82bf3d24bd1b65b656238128e734b6c7a65e3e45d3658d934f909c86ca4c3f2d19e0ac3c7aae58b342e", + "0x8ca5ceaeaf139188afd48f9bf034d8baf77bbf9669791c7e56ebf783394d7fcdf2a25fa4bdfcddfde649aa0dc67ccccd", + "0xa8060e6448844e9db4e9fb4da1c04bcf88fda4542def5d223f62c161490cf1408a85b7c484341929c0f9ce2a1d63e84b", + "0xaf6e1a5ecf50b754bb9eb2723096c9e9a8e82c29e9dcaa8856ab70074430534c5395534e1c0ed9ce98f4b84d4082fa67", + "0x81c8dbbef98f1b561e531683d5ae0f9b27b7f45dc6b2f6d61119ca0d559bf4ceb676d320afc5aba1811eeef7547a59d8", + "0x85b46cd64d605c7090a2faf1a2aadf22403b3692b3de1d83e38b2de0108d90ac56be35b0dca92c7a41c4b179a3567268", + "0x8dd3cc3062ddbe17fd962c2452c2968c73739608f007ad81fa1788931c0e0dda65032f344a12249d743852eb1a6d52a9", + "0x8630f1707aea9c90937b915f1f3d9d7ba6bda6d7fdef7a40877a40c1ee52471fd888f84c2b2c30b125451b2834f90d3b", + "0xb4a747e0bd4e1e0357861184dacec6714b2b7e4ee52fa227724369334cf54861d2f61724a4666dae249aa967d8e3972f", + "0xa72de682e6f9490b808d58f34a0d67f25db393c6941f9342a375de9ca560e4c5825c83797d7df6ed812b71a25e582fff", + "0x8d5ea7d5c01f1f41fffe282a334262cc4c31b5dcf31f42cc31d6c8e37c9bd2f1620a45519dab71e108fe21211c275b6c", + "0x8ccdc7e3642c2894acbf9367f3e99c85963cea46dc5473d175339a2391be57dd8815feacadec766e13645971213b9eb8", + "0x858e9b5fc8c13b651ff8eb92324bdda281db4cf39f7e7bd0472908b3e50b761fa06687f3d46f4047643029dc3e0ceeaa", + "0xae20d36c70cd754128c07cbc18dcb8d58b17d7e83416e84964b71ccff9701f63d93b2b44ec3fddc13bbe42ebdd66221e", + "0x860dbf7013da7709e24b491de198cb2fa2ffd49a392a7714ad2ab69a656ca23f6eafa90d6fdc2aa04a70f2c056af2703", + "0x8f809e5119429840cb464ed0a1428762ba5e177a16c92581679d7a63f59e510fdc651c6cc84d11e3f663834fcafeafdd", + "0x8d8a8dce82c3c8ea7d1cb771865c618d1e3da2348e5d216c4cbbd0ac541107e19b8f8c826220ca631d6f0a329215a8d6", + "0x86e3115c895ae965b819e9161511540445e887815502562930cedc040b162ecb1e8bdc1b6705f74d52bf3e927bc6b057", + "0xb9833b81a14115865ca48c9c6a3855f985228e04cbc285f59bf163dca5e966d69579ea4dba530b1e53f20bd4dccdc919", + "0xa71f5801838a6dbb162aa6f0be7beea56fadac1a4bcd8113a0a74ab14fc470a03775908c76822d64eb52a79b35530c05", + "0xa77ab73ae94b6d3378884f57eee400eff4a2969aa26e76281f577a61257347de704794761ea1465dd22a6cc6304fbc4a", + "0xacd1c5df3c487c04cf27f002e81f2348a0119349b3691012526a7b0d3bf911cdd3accbc9883112ed2ba852145e57fe68", + "0x8a28515a48832ac9eaf8a3fb3ad0829c46c944b4cb28acbcdbca1d0d4c3c623a36cda53a29291b8f2e0ea8ee056b1dee", + "0x846bafca11a7f45b674237359b2966b7bf5161916a18cf69f3ec42c855792d967d3bf3f3799b72d008766206bb7a1aa3", + "0xb24b341675b1db9a72c3405bbe4a95ccdfd18fa96f876ec946ccb5108f73e8816019998218a036b005ef9a458e75aeb3", + "0xb99c267b4a09193f3448bc8c323e91ef5b97e23aeff227033fe5f00e19bab5583f6e5fcb472ec84f12b13a54d5c0e286", + "0xa088aa478dbe45973b04ecafbcbd7ee85c9a77f594046545cdb83697a0c2b01b22b1af0b97dd75d387bb889e17f17aa7", + "0xa0c6b0cdff2d69964134a014e36c3709d9e63f6463c5cd7b01b6f0be673731b202d577539d89dd57a888326da1df95af", + "0xb4e6dc4ef11b2b41794ece70a8968e56705199d183366759568b6fa845d2cae127486e926b5b27ae9118bb21d1682c1d", + "0xa007804353f174098f02540a57e96227232444d5ae0a24232c244647148b6c049848cbd2b50d0a25af3ca9164bfff8ee", + "0x873fb034cc39c9cee553ece908fbf315f62efbc412b9afdde6a1889326b7f6f813e050b0601ba9921688e958cb75942e", + "0xb5676c90f0106c40d8683299e59d564f505ec990230cb076caef3ae33f2021e6aa5c9b27bb8fead05fc076df034c28f5", + "0xb5a67fc4c5539ad1ddf946a063110f824f7f08d2e4d30762c9d437748c96c9147a88efc22260573803ab545c18b108f2", + "0x817ff2b748a949973a91b69b0ec38efbd945aeb26a176d19f0fb76e261c7526c759e6f5516f9ed34de6eb1ac7838c9cb", + "0x99b76bda3526a5d841e059010fdb14eb2fa035a7d10463373a062a98c3c1a123e2da0848421dd7546d776438fd05e304", + "0xaa0d363270f90d56bbee7ea577b0c358532bda36d9247af6c57d000044a97ba41e35bb0db438f4c94551c6350e4e0674", + "0xacdae205d05f54b9544be96c9032350511895ccf413dbbc56d1f03053185df22a6d5b7ffcc3fbe96c3e2ce898ccfa73e", + "0xb091c220a1de18d384f50dd071dca4648ca4e708162c52a60e2cedc0188e77c54639f75bce9a468a64b2549119c07ded", + "0x878676133e5c700b1d4844564fa92a9930badb5293d882aa25ee6721a9f2cfab02088c31d62cf1342ae3edaea99a1ea0", + "0x9756d0793e6aba3b4dff48100bb49a5ec08ec733f966cb438379b91caf52fc2a5930830ec3f49aa15a02c82c1914dc7a", + "0x9722f760184d3b2d67cb2cea7fa41b1ff920a63446006bd98c6347c03d224d2d8328fa20ccd057690093d284b9a80360", + "0xb5a68489de4f253715a67f0879437bfe8f4dfc4e655ca344848980e6153b1d728acde028bb66fd626fa72eedd46ff683", + "0xa8cfc900b34835d9fd3add08044636f69614eff9ae929eac616c39bd760fd275ee89bf24b0f275dd77a66e54fd6b94e5", + "0x89967479bebf70b2893cad993bf7236a9efe4042d4408022fdbb47788fabedcec27d3bba99db778fcde41e43887e45af", + "0x889235938fcec60275c2cf0f19d73a44d03877d817b60bb26f4cbce09db0afae86d42d6847b21f07b650af9b9381fa82", + "0xb7fc321fa94557d8fbdd9fff55ab5c8788764614c1300d5ef1024290b2dbb9216bce15cb125da541f47b411a2e7e3c2d", + "0xb11b0c4dc9477176b3cda6b17858dbd8c35a933ed31364801093f310af082cb5a61700f36851e94835c5d4625bf89e32", + "0x9874e54d2939ee0600f4194f183877c30da26d7515e9e268fea8d24a675dd2945d1565d9016b62b1baab875ac892f4d2", + "0x90df3a77280d6f1fa25a986309bba9d5b89c3cf13656c933069bc78e6c314058716b62eacfa7ab4aff43518b8b815698", + "0x962b08299a287d77f28d3609f39fd31bc0069f7d478de17539e61fcc517045050644b0307c917208b300ce5d32affcca", + "0xb30eedca41afb6f083442aaa00f2e4d5dc0fda58e66aaf0f44e93d4af5c4bf8ea22afec888cacbf3fae26d88e8d344cc", + "0x847747a22fab3fe3c8cd67f3f1d54440f0b34ce7b513225dc8eb4fa789d7d9f3577631c0890a3d251e782a78418fecfa", + "0x8d1ef3cb5836e4039b34ee4e1b4820128eb1e8540e350309e4b8fea80f3ae803d1f25f4b9c115482b324adf7c8178bc7", + "0x8f8a2b0b0f24f09920b58c76f7d99ec2eb2e780b5a66f2f30a9ed267dcaea0ec63b472282076c7bf8548211376c72f6e", + "0x831ee6dc8889bbf4d345eaeb2f425959c112d2190764abbbe33bc44e1d9698af87ff5a54d01fac00cfee5878dee7c0f6", + "0xa7eb2479ac80d0ee23f2648fd46c5e819ad3a1f4752b613607ae712961b300e37f98704880ac0a75f700f87d67853c7a", + "0xaa4d1b9cec62db549833000d51e83b930db21af1d37c250fdc15d97bc98de7a5af60dbf7268c8ec9c194d5d5ccda3c1d", + "0x87396fd7e78c4bcf270369c23bc533b7fb363ca50d67262937dab40c7f15bd8448a8ba42e93cf35fb8b22af76740d5e1", + "0xa958b2a9ffccbca13c0c408f41afcfc14d3c7a4d30ea496ce786927399baaf3514ff70970ef4b2a72740105b8a304509", + "0xa5963a9dd3fe5507e3453b3b8ed4b593a4d2ced75293aee21bfed7280283348d9e08bf8244c1fce459aa2470211d41ea", + "0x8b06ddc3359827558b2bb57caf78b3e5a319504f8047735fcc8ec0becf099c0104a60d4d86773e7b841eb5b6b3c0cc03", + "0x9437e7278283f6d4d1a53d976c3c2c85c5fe9b5aec7e29d54a5423e425b4be15400ed314f72e22e7c44ee4bacf0e681c", + "0xb56067ee26a485ed532c16ec622bb09135a36c29b0451949aa36fee0b0954d4bf012e30d7e3fc56e9f153616b19349bc", + "0xa5c72f7f5d9f5b35e789830a064a59c10175093a0ce17654da7048827d0b9709b443a947346b0e5d96b5ea89b8d7c575", + "0xa8318d01182d4c9af2847a29a6b947feef5795fc12e487a30001cc1ec482b48450c77af4837edfa1aedf69f0642c7e5e", + "0x82ea421c091552d3dafa7da161420cb5601b819e861dd2ba1a788c3d1b5e8fa75cc3f2b0db125dde8742eb45b335efa2", + "0x8679fd1c7771ea3b12006d4a972f4f2892e61f108107d4586f58ee7f2533d95d89b9695d369cdace665f19c6bc3bc85e", + "0xb5ab3e8adee4c950fce4d33a0e2f85d3d886e60a6e2f4454b57bc68725f0cf246372d863167482cce1ea10a7c67c3af2", + "0xa85696927075ec188979180326c689016a0dc7a2f14ae02ea27c39ef91418cd44177d3fca5752cf6b298fd75fa012e26", + "0xa44f87b7232f102cd092f86c952a88afb635484a984da90a41a57a3d883c9469064bf105b9026024090486b6c6baa939", + "0x866ac91a437db945bbfdc11fcee583f3669fa0a78a7cecf50fbfa6ed1026d63ad6125deba8291452bf0c04f2a50e5981", + "0xb780d5a1e278fd4eef6139982e093ceafea16cb71d930768dea07c9689369ff589d0c7f47d5821d75fe93b28c5f41575", + "0xb025d0046e643506e66642c2c6a5397a8117bbfe086cee4175ff8b7120e4f1e6794e1e3f6ec11390993cca26d207ae43", + "0xa04a22b6e28c959ab265c7f48cde42bb6a00832c6beb2595b5df2879080a9424890960417d7d7ceb013d697d0ebf7267", + "0x81de9c656ac27f54d60d0252e33aff4e9e9e9c3363a50740baf15a2b9061f730a51ae1704e8c4a626153cf66d47f19b1", + "0xa15fab90599df889df11fa60c752948b68fba54005491180dafb66c5775547976d0eef33945e55d4818653e0818c6f92", + "0xb06f9be44ddb103a72fa4ebc242c8ee1975fe9bf9ef7124afeda9967ff3db644dbf31440151b824869406851a90984a2", + "0x99abdfe6806ae5efa2d11577da17bd874d847c5f810460148bc045bcf38c4fd564917eacb6ed61bb9164ed58055cd684", + "0xac53231077f83f0ae5f25e52b70bb6105d561c0ba178040c11c3df8450c508ed5df34f067fdaacf716f90b4926f36df5", + "0x99e3f509af44fc8d4ebc693d3682db45fd282971659f142c1b9c61592573a008fc00502c6af296c59c2e3e43ed31ec7a", + "0x98f2f5819670aff9a344e1c401f9faf5db83f5c0953d3244cfa760762560e1c3a3c7692bb7107ea6eaf5247ac6fd7cc8", + "0xb5b9f90391cec935db8d2b142571650fcbb6f6eb65b89c9329e84b10bfa1c656026674d70280ade4ba87eeaf9333714d", + "0xb0696b77ca8a0cdbe86cad12f358880926906fb50e14f55b1afc1e08478ae6376215cbb79bc9035de2808c7cd2b13b85", + "0xa51d746833062a65fd458a48a390631d5d59e98e2230b80d8f852cfc57d77f05eefcfd3c395ade1e86d4a39c2141365c", + "0x812d67654319f4ef3c9e4a2d4f027a4cb7768f1ea3f5fdde8d1b79187a4b874ff9a5c70f15b7efa079c2dc69d1b9b1fe", + "0x968978b653c6416bf810f6c2ffa3d1abbefbd06f66b6686e9a4fdce3f869e0ab1e43cce14dc83786596761c100ae17e1", + "0x98e1e6ab562ca7743783b802faeb0a24f1341abfb9655f106920aef08964a3c0e8083e1acda7ae28fed7cdd5478decb6", + "0xa91c0b982a0a7085a103600edf99e9d0bee4c4e7db6d9f8f376c215c7d42476218462a3765f2928e12c3dd49d688e4fd", + "0x8a43395b3124fab9e2438635bf88952e8e3084dad7ecb3a9927f9af0e0887bce4707084043671fc98ad03621e40a149e", + "0xb0b37626143d4a8c6f5693d5f1fe871525b4dd946c4239cde032b91f60a4d7a930d7ba28959737550d71c4a870a3a3be", + "0xb01c74acae1715c19df08d5f4a10e0c19d1356264eb17938d97127bf57e09ced05ba30d0fc1a9f32d6cff8b0d5f91c9a", + "0xb4c2328eb8a5a673406faed8f0aebb8540d2791646e37ce46e0e382506570ca276eb6f8e166dbbf9e0a84064873473b9", + "0x85cb9f769a185e3538e4a4beda9a008694e1bf8dfeea9dc07c5c40a9ceb1d31fcb13cacfaa52849ba1894b5027cb8c30", + "0x8742f91cddc9a115ddc73982f980f750d82d3760f2d46ee4490d5b17c6c3bb57c7d4c7b8d6311b7b41e59464c009b6a5", + "0x948ef86d17128a061e1bdd3ea7fcc7348e3ec87ec35dc20a58dd757d5d18037fe5e052bb359e27ab4c2320d9a52a6a0b", + "0xa70f6a214097c271e0d2d95e30fce72d38c30a2f186271fdff0e38e005aff5baed53739b8c4f9501aa7f529c5cb2da59", + "0x892a7574cf6704ad75b346c95ae6f2668904f1218c35b89b07a0c2dbf3c62173c348f6fd9473926eef56a37c0f635c04", + "0x837e85a41f39b4ded1420aa8fc3be46a7adb99305e0928c6d7643b7c44434b72984cea08eb68f5f803661df0db78c87d", + "0x94e495329f2aab3eeb68f347961d1006e69d990095877a4dcc376546233adf29a14bf6b16a0c39aa477e15368e87014c", + "0x851860a8fdf76a97048396553262637dade27f1f63f926997e74c7c72b14b10293eae7824e8dedffad1aead57c124f79", + "0x90481017a250972055ab1cf45ff17d2469517f10f18c9d4ef79a9bdc97a49093289bbacfefa8a1e491bbb75388b34ac0", + "0x983db15f7463df28091c691608ca9c51095530fa6b1b7b5b099c612e673d29e16787cc9ae1c64370ba6560582ce623c0", + "0xa477dab41014c778a1b78a7ce5936b7b842124509424e3bfc02cc58878c841c45f9e04ccc58b4f2ff8231488fff0b627", + "0x868ebba1c85d1f2a3bf34c0ab18721ea725378b24f6b6785637ee4019e65d4850e051c8408fe94a995cc918c7b193089", + "0x93cbf4238a37ccd4c8654f01a96af809a7d5b81b9e1eab04be2f861d9d2470996fb67367e5bf9dcd602dc11a3e4cf185", + "0x83113f4e696030cca9fdc2efc96ba179cf26887c677f76cde13820940ad6891cb106bb5b436d6b0f8867f2fd03933f7d", + "0x90c709f4e3359a6d215d03f45ad5cf8067aedd4aab03512dd62229696485a41dcd64e2acce327fda390e0352152fce13", + "0x9945cfced107a36f3cf028ba04c653360afc5013858b9a12fac48802efcbc198c9baf3a7f9b23dfdd5036e88bc7274c8", + "0x832ae60192b47fc735a8ddeaf68314b16256c90ab68099f58e43073e249c6939895c544a02fa34e40805bc6b5db33461", + "0x8b12c335818b643c1d22cbc2869606cf64e7ae54a7713617fc4dd3b2f052ebd6b920ca59ba2e9c7aa8cf71bb4f40f9e8", + "0xa2033eb7a373931c65d66989644aa0892ac3778b9a811b2f413d8bf534e282c339717979f9aa742162abb3468c195f87", + "0xaba2b4c37dea36bed6d39323e5f628ab607699c66767f9bf24ef5df1bfcad00c2664123c0d8d5bd782f1e14a06f4c769", + "0xb71963777535b4d407286d08f6f55da8f50418486392a0018ee10f9ae007a377b8b8336f33386b0eb01c45695c3ed2da", + "0x88dc87826941340913b564a4f9b74985a311371c8e7b47881235d81c081f1682bef313c2f86561a038757fb7d6a1a8dc", + "0x869e13e3fcf91396750150f9dc9307460494c1d365f57893fd06fb8acf87ac7dddc24e4320d9cad0414119013ea739b8", + "0x92194e292303d32b91ae9cecb8d6367c8799c2d928b2e2846dab1b901371a4e522fc4089aad8f4ee676f0614ff8b19d7", + "0xaa589a3e512cb4f8589bc61e826a06d9f9cb9fdfd57cf5c8a5a63841435b0548e30a424ca3d9ef52bf82cc83c6cb1134", + "0x81802e0194bc351b9a5e7a0a47911d3a0a331b280cf1936c6cf86b839d3a4ab64e800a3fe80ea6c72c3751356005a38b", + "0x88e5e9e3c802314ddd21cb86f2014948b7618502a70321c1caf72401654e361aac6990a674239afa1f46698545614c93", + "0xabac1e0f85d5c3ff6d54ed94930c81716d0ac92be49e3d393bed858833f4796c2b80bf7c943e7110de7b2d148463bfbf", + "0xb7eb416004febd574aef281745464f93ef835fd65b77d460b6ad5d5a85a24b536b4dec800cfe80ae98489e54447e8bb6", + "0xb3fd8ed1c30e7c15b0bc0baf0d9d1ecad266bafb281cd4e37c55edc76c202fb1e4ea315a91a2848f40f481793ae35058", + "0x86ef674ddf4b7d303c68bbfb53db00b925ccbf11d7d775ca09e458f4ecd868ca828103e8e7cd9d99672a193e81b83923", + "0x95ef414e9f7e93f0aaaeb63cd84eb37fc059eb8b6eced2f01b24835b043b1afb3458069c45218da790c44de7246860c9", + "0x93ec8f84c20b7752bfc84bb88c11d5f76456136377272b9ac95d46c34fce6dcfc54c0e4f45186dd8df6e2f924f7726ab", + "0x95df5f3f677c03a238a76582d7cb22ed998b9f89aecf701475467616335c18e435283764fb733fb7099810fec35932ae", + "0x8cda640695c6bc1497d19b9edc5ff4ea94c1c135d86f573d744358758f6066c1458901f9367190dcd24432ae41684cf0", + "0xb19aedf5569435ff62019d71baa5e0a970c6d95fe4758081604f16b8e6120e6b557209cdea0ccd2efec6ff9e902d6ce6", + "0xb3041f21f07d52e6bd723068df610aa894dfdde88094897593e50c5694c23025e412ef87a9d16cadd1adbb1c6e89ced4", + "0xa7f8d6ab0a7beb4f8d1cfef6960ebdaa364239eca949b535607dee5caeff8e5dfc2a9cfb880cc4466780c696cff2c3a6", + "0x99a565b4796e2b990bfcb234772d93c5ffdbe10453b5aa94662272009a606ba6ea30cc0c3c26aa22982c1e90738418a5", + "0x90c54b55ff19157c1e679d8d4f7f0687a70a27d88f123179a973c62565adfcc9347cfe31f54539038cf2f34556c86870", + "0x8612f34bcd018d742202d77d7ce26cf9bc4e0d78e50ddf75250b9944583b2c6648f992b635ea13fdaae119764e7c28d5", + "0xa04fb38e5529bf9c76ec2b5e3a1ef3c6f9effb6246c7f67301cfed707356ba1bf774f2867c77a5805933f0c8ad0ec644", + "0xb4800e7b503da0164885d253135c3b989690794d145182572181995e6fa1989f3d0324993e871bbd5f48fadd869d8a18", + "0x9981cd4f28ae7b7dadf454fb3aec29746dc2e0ca3bd371b2a57cd2135a7d93559e02132528ccd2d305b639d7ac51613d", + "0xa3ceec012dd1fbad3ef9f9f1d6fe7618e13d4d59e3f50540d2a57010d651092979c75442ec8b38a1ab678505e30b710d", + "0x8b97b8654d067fb4319a6e4ee439fb8de0f22fd9db5569ba0935a02235cb4edd40a4740836c303ec2394c59a0b96308b", + "0xb3d1bf4410fec669a269622c3ce63282c9ac864620d7b46c9dfcec52d8e79b90c4c90a69c32763136a7f2d148493524e", + "0x93174eba1e03f879e44921084aa0ee3562e48c2be49085de96ed7621c768ff52324d14c8cc81f17d7ed50c38ffb2c964", + "0xaa2194cd0fb7aec3dac9a1bd8ea08be785926ed6812538be6d3c54218ea4b563646af1f5c5f95cb914f37edfae55137d", + "0x93f2c0dd59364f6061d3da189e04d6c64389a3563b062e8f969a982cd68cc55b4f38b21546c8a67c8df466ff4f61f9c5", + "0xaa7dd497cc949c10209c7010ba4ce8a1efd3cd806a849971e3e01716ea06a62e9d5e122ad1d2b8e5a535fae0a01a7761", + "0xad402424b2a32bca775a66aa087580d7a81f0867f293f1c35580b9e87ccc5a2bab00c29a50fd0d7bd711085ae2248965", + "0x96237843d8e29ac77fc6ebf4acc12946ad11697de8e5f152fe5776f2475b790226a7d156ac48968dd68b89512dc55943", + "0xa45c25cdbb9fc327cc49a1666988af9ab4c5f79cea751437d576793a01c3eeea4c962c05c0947852fe0e4c63e1c84771", + "0x93dcf834a614a6f5484cc4ba059e733ab5dcc54253229df65ff5ad57b447353ebbc930736a4c96322e264e65736948dc", + "0xb9a94f82a82c0c5a26f2c1d5381afec3645e8ee04c947dc3b7ad59a73018db1e9965ab3642f2bbf60f32c430b074fb22", + "0x94eab29b3524ccbe0c4b928e5fa5dd8f684074b332fcf301c634d11083653ffee4f7e92ddbcb87ed038024954ad1747b", + "0xb8dca5f679931d6abef0674bad0639aefad64c2b80572d646aaab17adf5ca1ab2ebeecd5a526cadc230bec92ed933fc2", + "0x944d394958e539251b475c4304f103a09f62448b7d8a8eaef2f58e7de4f6e2e657d58d5b38e8513474115f323f6ec601", + "0x8a5ae1f13d433962d05df79d049b28e63fe72688fc3e6660aa28e0876a860c3dbc5fc889d79f5c4dec4b3a34cdf89277", + "0xafa5278724998eced338bb5932ecf1043d2be5dd93f4d231d05d2ea05b4455f2ffdc0eadcb335dcace96dd8b2b4926fb", + "0xb91153a2f4647ae82fc4ee7396d2ca23270ec7f8884ce9eead7e9376270678edd42dd3d4d6c003dfc2dde9fd88cc6e7c", + "0xadc932f1c679bf7889cb1ff4a2d2897d7973483fa283979a0ea3640c80ed106ea0934c1961dd42d74b22504be49851f2", + "0xa82e90761fae684d1415cee0649bb031bcb325ae0b28f128ab8e3650bccedd302a70de1a341ca8decfdda76f3349cad0", + "0x8ae353188b4b98835f4ef0333cccb9e29e1ac3ec11d554bc96f5880c101cb3c84b8eefe72f2287b0812735339fe66cfa", + "0xb8b41135bb1a1ffb64afbd83e2189e755f2c350e1273cf47c38ae9b8c4800d831436a69458b8ef9fa8b95a148d8ec9fd", + "0x96f75a04d8752fa93dc1eaf85ad333cff4eeec902a345576139e16de3a88eeb71b6726224349bb9844065cc454d959e9", + "0xab82b05e3923ad4c26f5727c60dc0d23063c03f5a4fd8077da66aa87042cad1bd99586d4ab35aa5e4ce6f4da6fecf3c1", + "0xa50c83db91c26ef7bf1720d8815b41bd056b49fd99710943679a162ccf46097a7a24585750ece886e38eb4fdb866fa37", + "0xa719f667914a84f62350dcc6f4f30b9ab428eac6837b70318c3ac491c1e69d48af5e1656c021818f377d911fe947c113", + "0xa148807aafddfa0a5624c7cb9e42468219e4bdb9994ec36bc19b6e6d7c4a54d3a0763d13ca80624af48bbd96d73afca5", + "0xaa012f205daf22a03e9fb13a63783dda7666f788a237232598d02a4d4becec7a699ab493f78d722ce68519262924c708", + "0x97fc15fab5952c5a2d698fd6f7ad48aff1c8aa589f7d3b14285fea5e858c471cf72f09a892e814104fa2b27eb9771e73", + "0x8da8840236812667c4c51c8fc8ab96d20dae8e2025290b9cde0147570a03384370b0fcbe20339c6aff09cca5d63e726f", + "0xb477d85359a8e423fed73409f61417a806cb89c9a401967622aba32bf85b569e82bca1b3394c79e180114a0d60b97316", + "0xb3d6ee2ed1e4c5cf8ba2c3a4f329832e41c7fdcbcda8a3fcbe8f60967fdb1717665610b7c1ac65582534d269d762aa09", + "0xa0b3b30b1b830b8331ee19f96b4a4321a6b93a3395b95d3a895682c65ec6ea64774b878b93514eaf353f2e4be28617b8", + "0xa2b88e9617f4d30ef4e686d1932ad43cd555fadcb5102e51bea19e6fca649284ccf4debb37b5cb2090ef386fa5bf5327", + "0x8a4446f7e8463ea977a68d6217a9046ad4356d6fc1c18d46c5d2ab681ea977b8faff136d65abea6bbf8936369cb33117", + "0x91e7464bc56e03f436228104939ddd50caace5a38f68817bb2991e193b57adf6835152bbf3dbcdebf0382ac9823f60c9", + "0x961a441e6cdf8106c4f45e5b47190d35644faec701c9cfc41ced40cfdd1fa83752fd56c1ac49131a47f1970a8f825904", + "0x94b7b165cc71c2ae82976b8f03c035fb70e90028992b853aa902c0467b384c7bcf01d56166bec5def4453e4d0c907e52", + "0xa5d32cffabbf547f900026b34ef46f08075b7a244565f615370d2f04edf50b094c95088a4a139ce07caf55bcd99afa07", + "0xb4e06e73660745f75ab2f34d9f6d2675b58f80f911ab6dd4c5a6ce1095f9a2b50d86f6ff9a05394190bdf96af0827920", + "0xad3fd8f83c0103b29d41319209dffca201d2b98094362da08da3fd6ff0ba96796b49d6bed525c9adb96c2954858e7f48", + "0xb0c27430695f0fd20ae31e1ec621da090094f2203e17411db9384695ffcf5c7c6badf461ba49ba70164aacebd6f278ee", + "0xb9bc6e972fc3b532fd2b1eeafc4bceb77604885f32132af6a9a842fa2440df452f49ec0cd9d86da1180e8deb0723b260", + "0x9729e22d6104b0174c136a854920f542b384d375040adcebe36acc253bdb55845eb43e34dc5a7cc27d22c417973c24d0", + "0xa8b420b36d48786c9231d454468a6e855dd7f71dcfd095efc9855ee70dbece0f06ad277f7829c5813fc30524c3e40308", + "0x8757dff5499668c93fc5d9cea0a8db61817b8ed407200d623030b5849a913d12f8371b667cfde8d8082026eda7407e8c", + "0xb859ad747ca5af661fbd03a1a282df6e84c224ecea645bc2d4ba5e35fa06cbf047387319fca0cbc76b712398c0798968", + "0x8e3173c27875f1460297af0fa736c945dc842ec3e476a973d3d5f790bf183ad3ffe96ac13868c5101d8e299890791864", + "0xa9d725e2b92c878be42b5eecc2c3081c63c7231ccc7e2dee17ca6a4caaeae22788fab1f1465fcbd7fc236613fc2bae4c", + "0x86f6c4f04a354cb2470ef91914816fd740f8d5795ce7ff981f55a2634695fde5951bbae7a4bbc4c63747040f8644170a", + "0x851773cb26f320f0c3f252d95ea7e058ffcc795dd0dc35e459aa1b6b448238909230d809e82022e64b7fca5d40b8324c", + "0x8962641e0306220d9892fe2d452caa286301a3c465185757be7bce2d9b2c9beb3040280099606cc86773e43941fd3439", + "0x8beb6e08c440b0de5fb85251d39d9e72db4e556a2dfe3dae59efd8b359d08492064cebd8d8993254b43bde8bd67d969a", + "0xa7e047894466ffe3dec4ab8d5462f2b1d8ac0df006b1d2dd26caf499ea857d93a811cf42233f9e948c9cb903beec004c", + "0x92eedd95557a91691a5e2835170390ce2401e223da43b78615a804c49566f9d31cbb7f10c8a8390c4bdcf691544fdba9", + "0xa5e5b5d8fa65824e958bbae98d146b4b332f97ed50e0bc2c58851dc2c174ab71bcbb1ae015cd2955c26b368487dd862f", + "0x853a494eafb308175629d581ed04bed71bbc3af9ca4c0dc483d03d27c993a2bbd88cea47c2085a6928d166fe6938fb77", + "0x83f06b88d29afbfbe8f61811690322ac4fdd6abb9a23612162e7a2dd6bcbb5f14cee298ebebc1a382484f7346dc51e60", + "0x8c9cf05735ea5a0e563490bdc7ed29a4426643711c651e35c8551ca6f855c8458ae8f0933a022d0bb9a952edfed411f6", + "0xb906b48d807748a26cc2a8848455a76ce502261afe31f61777b71917bdf7de2fece419db636439478c7582058f626c29", + "0x97efe1fa7c9b25d8bea79d74b6cdcf88f63f1e865f54b58512a2e60428630b0b40b8b6af1b5f71df47520507548c3cad", + "0x8ef5ca6e753818906bb3fc71405928d8e4108854ef0ef01c1009071b353bc2852e771fcb619d5fea45590e8f61003d7f", + "0x8e4d901661e2913740d70ba4d0745df5e8c9c0a260149d9362beadc7e669630ba909ff0e8a6cc85c54d6b7435d0d351e", + "0xb7c6ba3bebbd9592967954e3a480ee8df1d9f5965f04e7d78a5415b645128deae7ddaf6ed507c8877bfca91ce078e529", + "0x840bedb0ad4e25acf6cd25dee4f98fea495b2312dc5cb7a8388c5ab00b2acb9cd25da08e9fbead145a3107972b1ccd5d", + "0xa8d4578dbafdb27f3911af59962d89e75dea74db55346720357790da677312c203107d9c7911535aa563446fde7d4c47", + "0x86d3b77f231bfa09251b7fd2ce09c27ac520ec35d783e912476f9a4863f83d269eb175790d6e735da9260293d707f8ee", + "0xb34909f1cc033232652da0c34051a769dc76adb1aee00674a59dc1b860f6e610974c3b4bb69a69ccc73e01f042431242", + "0x90799854d0cf34e1d91ff8e101bc7c5007423d34d2f3bd9adea2ecac57e83f3a65a506bb93d4caea49b29f6d18149957", + "0x8ef94cde29b037e19a1ce7bf4418ad3c95cd9457412796ea385750c19a6690f13a3bb5bb6a9ee81e7a40face1e0a8bca", + "0x97053d21ae8d75972fb37f6fe516c38c32ab162fb56b9f510f954858f4e3ef6ac8c3a9557ed3f41b7b6aef05fe97f931", + "0x90a9f9f0f40991f3bddc58b92d40382147db22cce50d092d4a05aad251b46b94e71ec9f7107a180243288059fcc5ce29", + "0xa14265b1344ac2921b0f890d13bcfc432e4f648ce403e261fce4d3bb32ffee9e2794c02830346054f998e82784c77040", + "0x91928402ae121e56a3e64cd6f390127e6e92fbfb1967ec6efa4f52f3e8058f1f41a0f4fe96b5bcc11641c1139e790b2b", + "0x921c8c92b6d40da6c5a7b592acc74fc0f577d93767b9aa4a1cd302a72dbf503a1ea5b2c29fa0d0359bff3b8f252246d1", + "0x93ae0ebe0e8e133fd80cf67a499047e30ec4c4660ccec9d49098717ef57721a030f423e00c5e74af4ff4acf014a10497", + "0x82c865e21905aebfe0496af1c6ac7e342b5f446a9edb4f7da0f2fb0340abfd8e6fc545da874459d9aabe6bce0dd9bfcb", + "0xaee3961d8d2687c0f134b9c28b920bdc4021d925fbe14323c84224a9fe161248789249fb85436a5891d0bbff42c2a3e9", + "0x91aee420b98b6949482b8ff4be996b97245b4e8f583a6e085226539074f42aa89818395efd1a6699735a569bfe19d623", + "0xa48eec22c192e495b01722d0016a54acc45ff837e2a95c4294ce81d5a4e43e0053a6f0ead8a4fb3ddd35faf6607275b0", + "0xa26e15937c11faa30ffa64817f035e294cab0e839f73d29de8a244ad039be4e221eb47ea08d9a4658b0152fc3caf6110", + "0xb84450f948aa7c8682fccb9cae84d8e3558adf2d0ca5fb81eb200415291158720f8f3470542ab5b88c6873ad08e7fa9a", + "0xa8e8ec27d0608d020169a85d6ecdb40eb402f006a3b97afe32cc01987721b3a68a92ec693aeb4d357e189e05fadf699e", + "0xac87cd535ef5699312cc26f86adb71baa0be42e858bd5a2d94ac05737dac63430691e29b9a30d2559ad581a172519b2c", + "0xa4481e67b524f8cddf2046625efd3d75efee6aab87ddd2c1b22835647e918157e5e924ac760db2195c86d326f3db1615", + "0x891f29ded231486ee826840c8895cb325f7e84a5a6d2eac246cb3573612cde274720233b1978318a57ed337a046330a6", + "0x906b6e750e6178289012769807d2598925d7e51c260c14497d8af978b1695990e3352e6e809a752f376597a68083870c", + "0xb7a056898ee1e46f7f29702fb39232f678ec173eccd170303b3b0a30c8d8cf1a5321384e3513e3b03bb742c238deaa54", + "0x8f2f035fd96c3a336354c89ec9b8222803bf42e95fb2412c28d4e75eec99c1d4d402501ccae17357b757db8bdb0bfeab", + "0x81228625ffcedf977fba9cfa13f6edead3985e2651d5974789c394a69401cd7face9e20ae6694be4c0d4bab5e99c61a8", + "0x885a83eae25e61439ad809567a2ab148583402e01cfdd77b0e37ab4038935425c64b4e0886949bf06438c35e80aa13f4", + "0x8926387f48752f6933899c48e038cf14e7941ec6a58bcc0a436614b396296a17aa53e6873803dd3041dae470bd493fcb", + "0x95d0d3fa061f4d856eca78a569aa132db14cede7646f97e2aceb6da0c8ea53195d3b7a566fe5ec8c41b95ecdd89a1c6b", + "0xa3c817f4062ed6aa94064ea695d76c1825f3bf77b310fe1db28b8bedc9aaacbf1019dbd128adfd53042fb943d863a2b7", + "0xaf1208417aa584052da309169854149ede38a3ad63c76cad6e43afb6f1a7b854edf8310a0b00088c039259cedf0f859b", + "0x8b713fc3196bad35dbf364089049ada5477e540d78d76a5f0a9df98f7ba4a0e65dd0644509c149f9b07887298bf74b04", + "0x89c09c43c5b733c4a417cd9ebc0795cc3348b72778d31828a9171427779a82ef023c1a4fcfcdc919ae25056f9c826fde", + "0xa0759c850ed320c8c874435e90ace6edfb8e7b3f2a09d942b8ad8339c508044ee2ee26c70f1b626ec49a77971433b6a8", + "0xb85cbc58d4fd52286e714ac4eaaa0b2743a1de06fa03ddf8f6668ec6f1d204acccce93b10620272afb8c0b49bc4b0a43", + "0x814e0a87384e159892a8d23036985fa3f489c53bce192e107bd2d64f57b1bf5ea0acc1ef46c7a42bbc5cd0924d92b4a0", + "0xaa6821da96ad89d7881b878e141076522f104ea9a5bbdd1fce9f641898f7d6232c518a87a0f666871d7e3165c26081e4", + "0xa9041d714bfc067b5427252186fa3557bad598fc0067dc8521aa9bc1ae298f6e96113db5ac9f6bade9a85d5a950c9755", + "0xb8669340f3064692625e1bf682d34fbe69a61689e3aa6d6a3e822c781d406b0300dba9c3f7b8152a8c2513f1310d4291", + "0xa78c53316ce768a1dc5968030bf4fc885f4029b1ddb6a5d84a61c85af686c73727f62823891edfcb6ccf4545de366cff", + "0xad1d3aa29ea28292ddd438c865e2b5d93f32cdf009e6d5f5dc726de996583925727e6348bf1c28c22dec0bd86aaf867f", + "0xae1447a2062e9e28af5f38aecc60fe150cd10c2edeaf2110034aa144f6235ed7fbce432a58805d4fe1f6b12652d6e1cd", + "0xa32146634332d3303934550705353c6d4fae5fa5985105bba35041e74cd71e2aad67b45da171221f6ed80f36bf6dffa3", + "0xa232e8286184196ea77427b53d8b52c44d758ecc42d22556529db3136379b4989dec61cff610cc6cf6700a450a847a94", + "0x8a72c7255125a736da52dff5f77e44c3de29f88fc05f5ff9227c69df296930caaa11446595e6bea3bd946baac5ef957c", + "0x9688a981a9457678067f629f8efa6b522e7318b529f88d37ef56c5bf8f1c34fb9bb3a918ab73caab82bf5abb0c03518b", + "0x88286f3eabd71115fc3b17a6bf6981340a81cf7e5f96b0a1a016d4ec8c18fb486d46c70919123d0c189a6f5d6ff29a1e", + "0xb535e701b40d793c02ac0d625ca91620d3f4a512aa9741f71389e58381008b2f93d597586d06213c4e103d67d0ddf6c5", + "0x80d0c9dd941e8d8d3700cc51a434a5aaa3308cf8ebfd14128ccfd258f826b27cc3cf5c3ad7851340393abb1eeab3a157", + "0x87049225fa2380d93f18d3d90cb0697a56b373b66d7f24ab209966aed8b55a2790194d5885399db29dd5b1f189eda64f", + "0xa52df158ce8670e0290551e8878d63dd33b4759d6f50e448e63fc7fe6ea99dddb6f180be5fc0fc3918ce54c05f80b356", + "0x8b2a728b39c465fb0f60b0c486e5dc8d5845ccec03d3dd93b393cedeeb3fe1b44518359f1ed55fc770a8f74bfeb9923d", + "0x91fc05419dba718fa4a910dcf256ebea356bbea00522d8d5ec3e7ba4271a26035aac15e8d9f707969df1d655d92dac55", + "0x97c8779ae80c24c1f82d5a714762d6ee81069224e39515e41d8a71c9310dc5d1c55cc92bc5c6a4bd391ae4c321d1d4d2", + "0xb5e5aedba378c4484e3a7a4ed41b75b0844f674261c2501497de6f91f7274b5a4c1be0e055f2e0c0cab843d891169fbf", + "0x8a26212f27211b295beea500abc8e9d430a8500d3a350cc62f895d39e8b4668aa638c17633804ba353010000165637ae", + "0x864a95118e5d394e00e99efebd505df0125525c9ebe165764c453b80ad3edc730feebde3d93850745dfd88a27bb8f20b", + "0xa092e0b78290e826cc1ae56afffdd08f7c10954f549a3ea6666f3db1b6cdaeb7df53db28dd2a92446342930fe60a27ce", + "0xa1720224c0626a081b6c637b2a6d37da85d9a82241e5efef3bc15699b02a69f6304e43d8ff3144d60c16e00225d6b39e", + "0xa7b3d098cebea9cf32e19c5195608182b6afe9d4af6b9df532c047eb7a941a971279b2ae6a4b80f2f9d9313a6d788ce3", + "0xa3d2451e6788944802c5077a778d7b7299dbb9d1612676bb6baae78f39976e0fd879493cc4a4d737b8174b472a456850", + "0x930121b73da844571b1411d56760e80923a4ee09917b3e9cff4d3dcb0bc27026ff2c4e2c44e7aca7d3f8383f129c7f9b", + "0xb4b0119d163ee00a2b74bdf188a5cdcf054daaa48c483b94bbb4d09ff615afb4a91347db6363bc7535e2af9054ec2214", + "0xa5846decee706780201095a8cdd48fbf3d3a2eac8d089a818e5e22c29457494bbfb4399323b067f3d2be2197c33dbd98", + "0x96ba600df10ee7af5a9df29c0ca31dbed275d647faf9c66c7342de927ceb25b5bdd852dd7aae0228b27897f90fdd5d62", + "0xb6ac51ddc98edd9fb9f54ef84bf372a041d58dfdf0dfdbdc4b08ddc1a7ba93ddbb1413dda3c1545a3fd7386c6b85975c", + "0xb35f3efd91a0723e0d486188ea9675a3462106470455118392d7610470b623caca2fa33829721c05fbeb0fabcf570bfc", + "0x87f49e85df5f8055714a8ce7adf37f6a278e64e76ed74c60abe3edfc3611ef5b0426d4c6da45e5f3b74d30be1dc6f539", + "0x8ff8bb06902a71b1e9177a77367318b2e3e0a88f5d74d6907ca9943f4f9f1ceb5f297132c2a025259d17a67e880d1bad", + "0x85eb6de6c70fe5c53ab0ab27aa0fec439f136c979c557d317337cafa6e6c5cb3169679c9169567dec5f6c72b3c057d83", + "0xac18715ed1080771d760cb7066c6328faf65d9b30517903f8a5cad8d66d5c6381156b521107d7cd75ebb8c30e250706c", + "0xb95b9eae4703727e4ac9ddf2ae675906487bb78905a5f9cba74a4cbfd118d96b7afb6ef3ed5edf14fd963b830d71338c", + "0xa3b47b52fda16b62b11c8aa4daa56b0b669c4d5c56a3059b7d063284d8a91f6fff9ccccab23d6ceb9650483b2d353039", + "0x96a95b3f327df94c85e92f2e406f1649ac621533c256b062738f3c3ee137059a735a3e6072247acf57b1b0d8c219bd7f", + "0xb19b33cc04570be94eae8e943d5bb17bb0c96e9de4ca84f9f41b37320a1a03d397d53747dc13275fef1b356de557214f", + "0xa1faa3dcb931dd91507f3f12a17c43f6627fa2bc5c71fbdd27548e091eaaaba262477949cd51290e81196bffb954a492", + "0xb060a16079dca1d28a1fb33cbc26f368630ee042d980ce305230005d5b9ab533a7a695281ab76e9214458303932d8bbc", + "0xb303783196a858fe45d67e0520c30576da605fd69964449c20009fbd5099cf1de52a32d326d7c3b864de07440195ef40", + "0xaa550a4c20d1003d137ffd8fbdc1196d09ad53cfa0e202302093a80fa3bbc4c9aff83f34f2151785cc1ce5f30255693b", + "0xa7f8585f45566a351058e10c6f1ff4a7ba24811f1482a47202f581525615ca770da93f2f58878788b45b92cb446ef4ec", + "0x8206f63a9a5b59bd68e64a843e68fcdf706f4c13bbfcdfa9928298e5b9251006ae0bbd80c715aa3c9957d2c0148b5059", + "0xac9490abe1241319658f1c2c645cfa01296f5d4106020c7894b7ba4a65cdd52f6c5401bd3b3cf1c9863e088cd8c9a16f", + "0x85dd6d9c80a1b58c24c4d2cb7590d33d2454f381f58e820979948e5831972360cde67bbd56e1860077ef5192fcacb904", + "0x8b0285944c676fe2519cb68da0973275fa29c0718d838d363ce46651b068d29f867cf9fe579ff8da0bb8b37d202bb23c", + "0x95147275da658d43a758b203b9ca1f1c1478853e9bf77b5218593142e2bd9c0bf46d2206ab64cef99295de6e9a268edc", + "0xb8efa187fdd3e1f46c15cd596e9567690c10e253b5beaa5be8074b6ea4e6d3d06e0f2b05323453239e419ae1e7128521", + "0x8340464f52c92e31806fd3e8e65f56e27194d1f6daa4a0f0b3831e8102aba16f88bb5a621633ddb7dd0342e1d2d12343", + "0x8615d87dcab85a78dc052f05a01e751176b756b5dc9985014347454ce5752f459dd6464e1c5aff36cb6c51b783fa2692", + "0x80c6e35c0d3defbe4d3968792724a23f0b8830dd2fac58663583a49339ea20f1812cc4140e3ee867c7e716177319bbbe", + "0xa7aa63dbfc201dde8f29bb6e23d7aa5020dd35bd18a0cc93c8a10c35d695913fe25b9e8cf9b5fd1899e9657b22bc8863", + "0x97c2a4ba80c4caba2e729a603d2faa0120915e3fe64cbb065f7ff33de5f877f1ec9461cf455e88ec9e9ded9393939dba", + "0xa54bd1419f0e2d2d87757870f37c476c7e3a13502f1ada82fd7394fd29f8a00c4986473d753034d0954a2550badbac0b", + "0x8d3e2bf900d0d2b9b46e6e2f37620f0cc90526dbbcfaad4e4a37ed53f39fdd23bd3a6f21aa7e800eaec937d9710dd6e3", + "0xa88d2b1c7802b2dc216c2b6532406c091bfb12f29121b9a82c1154470e250188413ddd3e79f7e009ea987a4c45b332e5", + "0x8c552c2101dfdc3f99c2da436115452e4d364eefe029b12946f05673c5ce1cfb48d39a579625849236dc6c8e7277dd30", + "0x8415c252d52a26a6400c3189c928a98559bf24162ecf3eef1d10e439269c31d854b0b4f6ec7a2430e3f11b5d77de78d6", + "0x8b38905bad93a8d42339dbdb5e510003c51fcaf05e04f88fd7083753353bc1c4c00a5dd4a67431cd4456d0669c7040e2", + "0xb1d0ed8862250d0f0d9ef9dcf0cd16d84313d1a795dc0c08e0b150dadf9ce73d32d735e04632b289cafa69a6ee75dc89", + "0x9434e18a5fb631b10edb02057f2d1fe16000ee55ada3c26a079c9fc3943e29d6de99e52829fe7b333e962270c712e51e", + "0xb1b9f3914007e6fca8ad3e7e848a1108988cb2318da36df24767d804e95d1272943fda948451135cc1b5052a3953b081", + "0x8c02947a76d7b6c0a700a83dfb971dc105bfe996e18c521445f036310914b349ab28e57571e36ae08d13a46fb01c2f43", + "0x893472fbc225f973a0ac6a0a0130b9cfb7ab6869dff80df71a62b1f6beb4afd069bbf35b4f327165bc31dff39e4fcaa4", + "0xa7c176c0903175f3540d62f9afee994d5d9bf37081e094644b22f017e94c515afefde7bb07f638342abef7de657f8848", + "0x860186c2b1d3b1e657729bc804275fb5f5ee89eaa60848fcabd3871289665ea9f0efc8a95792d884972bcfa2de96223b", + "0x865b38aea6386d0ac8f501a7d934e23d01dc50105324e354d4c4fa3cb1d4c29c26f4566df7b1a728e10cfaa9d24552e6", + "0xb4eea5548de6969dada658df604b5d9c49002e2258352838003e0fdf7b299d81fb025807a7f37cf5b547cebd7f2c1f93", + "0x8982de11ba68d63a649a3b296d4d56c71e3c3eec016db250d733ab7c3b9a620c09c5a5d0b64fd30d3bc03037ca4b17c9", + "0x84d8b8a10d67eda4716673167c360fc9b95717cf36ef1d5bc6f2ef5b9d2624f0e76c2a704d016adf03e775ea8e28d83a", + "0x834d03ebd51aff4d777714783e750b84c16cb6627f8311bd8ff17c3b97fc4a5bba57d6c8f6d74f195d3030bcb5f07612", + "0xaaf49e0def0c4d5f2c1e9c17b51e931d2f754b19e80070954980b6c160178349f6d3c8d4808801d362e77f41a0008918", + "0x8ef4115edec841854e89f2bbd11498dac7396bca35dda554290d3db1c459ffc17be671f4a46d29fa78cbd6064cc2da20", + "0x9641dc8a64f4acd38e343a3062787c48c312f1382f7e310ccea3e95e066ab6dc980f6ed90a633236a435e68bf6b3c625", + "0x8a84cfc2cbeb18a11dd6c2a0aebb3f6fd58a33bb4b26101e826add03748595022e816afac79a4e7c20b3805252839dca", + "0x9770782d729017659844421e1639ffcda66a2044df9e19769b90292df87dcb146b20c6b9141bb2302029d84a5310665d", + "0x98c7ec9696454868ac52799d1c098c15ec4e08b34884dda186ebfe87d32840b81fd3282295df141c91137faf4cc02da8", + "0xa3f6eb921247617292162dfc8eec5b830ddc294a0fb92f5b4828a541091ffdaff34c392c1d7168259d6204405d90ec72", + "0xb185f77a468f07a54222d968a95635234e74fc942485604909308a9028ed2753b15902b9134749f381f7cd6b89cc8c3d", + "0x867608a682d53bd691dbc92eeb460d1c300b362ca49c11a280f6768ccec217f1145f9d59fe50d994f715ce89d38a74e1", + "0xafaad630ad8827cd71aade80edf3d7aeb65a344878db12fa848759e6233f6fceca563aa437e506ea9e0f1e47b126d45b", + "0xa12afbc84e3441594aecf85d089423dd3bb8bb33a1a384ddf7cc14caa72284caaa56aa179c15e3140fd56bb532491a67", + "0x98757b0b5e5837ddc156a4a01ce78f33bb1fce51e0c1254ee9b6d3942268d0feb50b93edbf6aa88f9ea7b3c0309830d8", + "0x89573f4a4ae752e9f964e42bec77d28a41840c28e4bcdf86a98a131d0b85367b885077823a6f916972de6ac110821bd2", + "0xa17f2745052de5de9c059307308fc49f56cb5230e7a41cb7e14a61c9efa742ee14c41023ce90c7f2261adc71e31045f8", + "0x914b07c53a41c0d480083f41a61c10429ea42dafea9a0db93862d2269ff69c41db8b110b4768687b88089b5e095523cf", + "0xb380cc3e0d26370976fe891d24ea4eeb1b6be8cfce01f47fd68838a27190e644fd57b049d3aa0a9589370de20e276944", + "0x906385fdfad60feec79eb1c303e750c659ceb22d9c16a95faaae093daadd53e7aa039a45d57e20951d6e1ca0dc899ef2", + "0xb5211ceee31b194dba60b616bfd91536e71b9213a3aaaf5aaf9b2f4cbdeb05191861d78b97eec58e3c81abe4f0488c04", + "0x97878e9e38c2f69d697800e7a2f132fc4babaacf471c79c26a757f771606e55fe696ece68a3163a0ffeb2f72274cf214", + "0x959431c1f54c46500c05aaa9a2bc4230531dad97ae768fa92bb85436c0ecc6374cf20fb0ef82d122db116820a943b401", + "0xb69e5a1c6798f30d33e42cb8d124f025d2c77c993c4c7107a539aacddf44d8d4d2239e802ece32e60ee4dbfdce201bdb", + "0xa8b09e5e9f802ad273b2efa02bcbc3d4a65ac68510510b9400a08d75b47b31c6f61ffdb3704abf535a3d6d9362fc6244", + "0xa41ace7f1efa930564544af9aa7d42a9f50f8ba834badcaf64b0801aaed0f1616b295284e74ca00c29a1e10c3de68996", + "0xa8f2aa0bbbc19420a7c7cec3e8d4229129b4eb08fff814d959300cd7a017ddb6548c9a6efebad567d5a6fde679a6ac6a", + "0x9683da74490a2161252d671d0bc16eb07110f7af171a1080dc4d9e4684854336a44c022efe3074eb29958ae8a1a14ace", + "0x8ef44d78d10795050c161b36afa9ab2f2f004ccf50fdeef42fe9cdc72ebb15a09389ca72a00001cd6d9b1d7b3bb766c3", + "0xadca54f3b14fb18298098970b0267301b7312afb75894deea1b2afa3e85b7a3b4efac9971ab54c5cbecba2da9f18507e", + "0xac5d4528f06fdccfc1370d5c3d03ed982fed0861a93a3f6453aa64e99360b124926d1892faaf72d89459e663721dfa99", + "0x98aa1c801bd615b8cba728fa993021e181e0ad717ba01c0290e7355694155407083eb53cb70819c4775da39d33224db7", + "0x8b3aea4c7c2bfe1020de3261ec085d79c7bf8a7903b825d2c70ebbb84af197bcc54e3653c5373a2045c3021526b63b66", + "0xa29f3de4cb3d99afff1daf7d431b38a33a9804fedc41626618928ed059df6f6fe9f298a046b594ffee951ed4d4e1400f", + "0x803fd346be540c5242667c18ee41b26bc812456ab13ff117196ed69b90ee608c8cb6554396b64066a546ec87a71ed6a9", + "0xa9c18d81ffd029c0339c72c499bb51685392253b996b6eabd8b76f05c6191ed8444a1397d63b9923743661a319517f7e", + "0xa048d5c390d08f07161faac71c5994baf152c883b205f3bb10d3501709d6516ae54d491b486303a11b751857a31f0052", + "0x9156fb4803e40e28d8d57d928481a8de4373687288da44fe88c5676a8ae013ed1fcc09d56a31140bf74e7f767253810e", + "0x98e289c725b18e0085afdfaf2acbc674dae7b0a2ecc2537a7d0b87e20eb785404ab05973a787f0495d2adb3e5565c09b", + "0x8a7237b249325bd67cdc1f9fb278710069033c304afbf270b7ea24dbc10c8eabe559a484d3edc733c77b4384932deb41", + "0x9056f2e5b02e5c2e04a69fa1323bbf1859d143761268d18e74632e43800a2a9c76fd681e924a19bc141de0e128d3e462", + "0xb9f2bf9e4e7263014296a82b9ecbb05d3f1efa4b2e675e3b38d3eace59da06a89c859256e1b77847886d6aa15f98f649", + "0x83b22949cca19030289bbf7cd2a0d8b84e1d468e78bc85271a6753241b89122627632723bc293cf904a5eb2b5dc6c3ae", + "0xa919aaf35dd0116168d2ee845122026416bec9633df113fbd913d8db5996221e234f98470d029a8ff182825b59fda20a", + "0x91726901f49d32b41afa15219073842278f60dcee223640903d871e318a1c2b541136b7b38a7b2ab7d31e4242fc29674", + "0x942b77666545bc9a858d36cfe857ab1a787c9528f4a0b87918a06bf510793264dcafd12ae6bd3ee300179dab7f40aed0", + "0x80adc1f2f9c47a96d416e44fcba41628abc0fae1f88f6a26aea4648419ab726f7fcc2187c7d5145e3d8f5a75c03937f4", + "0x8041e0f66ba9dcee01e336dd4d16ae5e4e1618512fc147cc8230003aa2940848162dc2187d4130bf550dc1f3559849d4", + "0x999e8adc51bab54386af1c5e8822986ad1b7ecaf1f8a4c2baa5bb2fe9d10710e49545c5a8bd89ed0e61a3d73a908e5ef", + "0x89272ffd39b6e9f99fafdd58bd9dc00f66f26a1d36b38a1ac6215e3546d966739eecda7fc236335479207cef95cce484", + "0xb8e0b7532af13f15dc04a0eb4ea8abd67e58f1b1c6ad2e70c0ffa04a5c18ec2018b5d7f4be2f9f86db5e0b3986f639d9", + "0xb96bd11b0f6ead4abd5fe1e4c6e995da7583b901afd01cc05e87d04663fb997997d6d39dd9fb067c62cb1b1cbb67516f", + "0x94ab08914088b973e8dbd5685decb95f3bf9e7e4700d50a05dbf5aaac9aea4be2c10c83096c02252e9238ceea1351d05", + "0xa188de419b062af21275d976494c131ba18d2b2ead8bdbfa38a777832448e64d4d9725c6a1d530ffb6513f18d5b68d9d", + "0x8f73c8c118fa25c76a4ec5611351953c491452743056a819c8c82ba4737a37d88da0b55f837e7239a5f46d2c05a1bbba", + "0x894a44769e0be1c26648b0d89c4c9f46dbdeb3a71b90c493093bee372bb9f2d3f319850fd886d51f4f58db0de5641742", + "0x87d239923b0db024a8d9b0281111d47b0761d81c50652268b074efa3ea70d793e30f874a91ce33a4acecd0cf38c01951", + "0xb1b48b75a97f9fc2dc9530dc69f6268829dd0ddd574516e7eb1b9f5c3a90058889a7bcf3d378738e6d4b02f5fbfa44db", + "0x83e3ee9526ffcb60c6e75b75550fc017912ec0daf96d0a0d5f58c1b229cce90c684ac7c3e17fb998def8e7e2e155d750", + "0xb9b7bba579e474b0abdc7775ff5f84c9f117c6ca17788cf5a5f01b2c35a14aa39036031c8d799fec2cfb371d9f7471fd", + "0x90d7faf4891fbc368a32f575dfb69f13e37161ab4f63a7139be103285a49490c2851a907f8d36e09e7d1a190dddbc6cd", + "0x968c8b9affe18fc34a4e21f0d8c5518341c566099e6b45b8721c9912bab3693c9cc343406fe90279692a1eef2a3f7311", + "0x8735baaf4704207550f77df73fb701d9a63329993a8cb355ccc0d80daf950145f37e9b4b22be2aba29898e974f9fd552", + "0x90f52b2dccf525b9191d836b205ffe966d9a94f6c5800f8f51f51f6c822619e5abdf1257ee523597858032d2e21014ec", + "0x831209f8f5257bb3eb452d3ee643d5f063299f8e4bfea91b47fc27453ac49fd0ba3cf9d493c24f2ca10d3c06d7c51cd6", + "0xa5a4db4571f69b0f60fb3e63af37c3c2f99b2add4fc0e5baf1a22de24f456e6146c8dc66a2ecaafeb71dce970083cd68", + "0xb63da69108fad437e48bd5c4fc6f7a06c4274afc904b77e3993db4575d3275fce6cffa1246de1346c10a617074b57c07", + "0xa449448d4156b6b701b1fa6e0fe334d7d5dd758432a0f91d785b4d45fb8a78e29d42631bc22aaa4ea26f8669e531fed7", + "0xaabe43de1350b6831ef03b0eef52c49ffb0ccd6189cce6f87f97c57a510ac0440806700ce2902e2e0b7a57b851405845", + "0x91015f144fe12d5d0b0808c61fa03efe0249058e1829bb18770242f5fb3811e4c8b57ff9cb43deccfc70552e4993892f", + "0x8e9c570811ce44133ce3e0a208053acb2493ef18aade57c319276ad532578a60d939ed0bde92f98b0e6a8d8aabd60111", + "0x8b21839b5dc1c9a38515c1076b45cedec245d1c185c0faac1d3d317f71f1bfebba57c2559bcdb413d9d7f0a2b07f3563", + "0x90413bbd162be1b711e9355d83769e6aac52fdfa74802d628ff009325aa174c68f5329ddd552ef93e8fdcb9b03b34af3", + "0x8b6b02e3f9dd1031ebd3df9a30432a3c86e64306062ef00a6d1243620d0cb66dc76f8d0d412eceff877ff8768c2696ce", + "0x9894b41d9fc715f8f6addace65451f41dc5ce7b983dd8cb33757b4d7259bef12f144e0077d0b662aa847d5a45f33c563", + "0xa353a9740f6188d73aa4175a6c5f97898a05ed7aae9d2a365f15b91dfa7c28b921fdef0a32d90b6fb82718b33d3ddb8d", + "0x984eab8faed87c403c9979f2d2340fb090cc26d00cb4092aeb187c3f4ee1df3f57cb8363f7764073188790b16dfc464b", + "0xa5c5ae0ba435fb7f3ddd5ad962358da326239ff236fc3b51bd22e88296236b109951cee1b98f444302badc58d1b5bfbe", + "0x880be1006b0156f2788813432f450f613d235f41aba52a6000d2ad310408ad73d86b79f6081aef1e8c51010d404ba670", + "0x937da751aae68f865c7a33fa38d718f20e2a1c65cb18c8e08f8441f0cdc77662789d2793794dd0a427cad30cd0b33f42", + "0x9496fde66c834ff86f205897db12bbf9a9bb78d9ba8b5fb539cd0a2c927cc6b4120c017b0a652750b45edbe5f650e5dd", + "0x97a6f409ffeb593e149307a14bc47befb632412d70565c5f13d6b7d032acd2e3ed0f7b6af701b387f11d69ee4a8094d7", + "0x97ed94934263dc0260f4f7513745ed3483cdddb9adb85dc33193c3a8b4d52affaf1ded23b59c34651afbffe80d40dc36", + "0xb2b26378d44f916bcf999db218b9892e06de8075f205c7dafd6d37a252185c2d1b58e2e809c717963d25627e31f068e4", + "0xb8f9fa1fb45fb19a45223f7be06c37d3a3501dd227c3e15999d1c34b605f888123026590697d0ae24d6c421df8112520", + "0x997aa71e3b2e8c780f6855e94453c682bee1356b5ce804619ef14834475511105b1e4d01470fe4e2215dc72182d9909c", + "0xac2cb2a7cf55aaf990cfada0218453853047e813d3f51f5a623d09f4714da79de6592671358a5edf938a67f905b6cb5b", + "0x8d8340d0c3081cd30d34f3ff6191e1ff6ad7994b4ebac19e5936f1157ca84e1813228b7605ee226366d6bab1e2bf62a2", + "0x9693b17669086003cb46c75fed26ea83914a54901a145e18c799a777db1df9c9ca6b2ea3ee91e7b0ab848dc89cf77f19", + "0xa6b6b2a6cd8c4922d78c8ba379373b375d66ac6ea04b830a23d5a496cf714a9439d81c865da92d52600aa4e2e43afcf1", + "0x89cb665020abc3f5e11a03c7ba5ec9d890fa9ed2630f1443a8e45a28c32786ed980b5343ffffaea60eeff5b313bc0d66", + "0xb37b989106594221bc6cf33a1a83c3e65ecdef279e90333a9e105b8139dc28384bb2277edd4b77c9e59d15e6afe074c5", + "0x98ce5aee5918d18b2326b30c1ba41669cce20bc7a1d1b585363305fbdea66055164a7ac398ca0f0e670291a3061022eb", + "0xb57f472d5f34beb4cf430d7c0f8ac5bd1c0621a284633ed36e6f7804bc2b7847f54b469c7ea163a436510d9e3b32f97e", + "0xae673a6579dbf0504c8fd0c8fc0252d2f7ae8da615a06f4d215c2f8a8f516201f24e5cc42967630c252905e5dbbd6377", + "0x97c1501835a31091a5a83f0546e01c85ee847a0ca52fb3cc0653f6a826e13d25ddc623a5dea139108f7270a1fd7043ea", + "0x9376ee667f3834f6c0da4324fdcca5c04712e0649877ee19da79a2d23be24640c38758fce562470ce2134ca34148ffe3", + "0x818af89c40379a10074cfaba6d5968ecf667f1a68a7edaa18e8977ccb34e0829f237c5634fbd079e7f22928b277f1096", + "0xb8e0af0be0a252b28df25d4a509f31878bcddf702af0e5553393c3dfd4a1f1247ad8dc2668bc8dedc9b41f6ad8e71b15", + "0x811667ffb60bc4316e44bd04573503f5b4dc44d1ec824393a699c950e5fa085b146537ddd6a08a3fede7700396a0df7d", + "0xad834cbf850b2f61ce799c4a0f8ab0c57039d4e1113933c50b0c00175171aadee84894d1376cf325bfd434c3deb44315", + "0xa8b7dfcdb40373ba4d55e751ccfb9070554434df9e359fc165284ee3dc35db6fb6055657ecf5a9e9b7b8e2e1abea4375", + "0xb56a5b9fd41c9d3f65532aa58bf71a38fcf07782e1ae0084dc537862fa02e6d66658b19d6f71c39cd5dbfac418da1837", + "0xa935af5ed224b9533b41a7e79f872f6851591da9e9d906050ccd1b2c772a1d6d010c5fc7160c4f8cd7d3aa14c3bcdc26", + "0xa81e580fc98692567b28323fc746f70c3139d989fb6aabf3529504d42d0620f05327e3385c2bd5faea010d60dd5c8bdf", + "0xa8b352054cdcde8ddb24989329a249b71498a5593a13edad1e913c795dcad3d24789abca9c7ed1d57efcc9e3156da479", + "0xb0de8a2bd7f93284b2bc700e442f52ada16a22ad8d86329591547411c23fff0333b2ab0c9edf82bf7903ebf69916eed1", + "0x843e9781b653d1a427f3534b2e86add49d308ca247546f9fcf565f9e08df921e4d969e1b8ed83f3f849e98c0f63e39be", + "0x84a4098c5dca9f73e827d44025473096101affd7193c40a0307e3215e850e753e9a08e6e74a442d57626ff26df77faac", + "0xb463eaaa2f3315b511c22a97fad353014d840a6a95fe0d457d0677e63e571407d7f5268f8775381a5e7adc3b4163eb88", + "0xad0417edaa16cfddc288eef4173aa7057ca4f81e815541ac588ef5f24b98d56fed6845deb6ae1a9740a28bb1cd8780a7", + "0x9271963b8fb2288a96e07eac13c0543ec41abdc6d978bd7c44ae08251ea49994412b542c77c8208cd71fd8e7852d4a70", + "0x8b68b6db9044d8bafc155d69e0daba95cd59d6afebb085791e999afed4f33a2479c633d31d534ff767b8cd433d591a23", + "0xa6a06a0e433e385437d9996ce823abda9848754aa9cdd25ec8701af35c9ec15df999825669bbc2e17cedb597a96e8eeb", + "0x94d414bff8b6b8597634b77a77d1060db8e1af0d0ddfb737a9bf1c66c8430e93a425510af2464bce4a7b29bc66cf325b", + "0xb6514049562af1c6fb7d0e8df6987b020f0b7a6e721f4862e36b1ba0e19af19414ede04b346be22d348b50875803d1bf", + "0xa42c7fb34f2fbee8aaccd1d86672d0acdf4e6bb083ff0456512d7e1e43be041cc0924322fcd986e6e1bce5d5ecce6f92", + "0x867cbdd169a52440ae0a75d33a28c7d00aa92b4b65aaac5e62aa53a8fc367c08ab8828cc8fa18b6e7d1f908d158e3382", + "0xa6fe0b768fff3e4a6153e59a7b7508eb2ee8165eaf5274d41ac2812bd4563c4ca2b132f0e27ea2f1c98759cc3589b61c", + "0xb3eb1dba43d10b9e17ffec8def053fc96f9883bacb49330a089a0ca5b9ab0182e8b5111ad4aa55c1ce1b6f4afa5c70a3", + "0xa1531351098bdfcda566ff4d811301c0305626c77f954a38420c490e7c684f517eb1a4e4bd2c3904a10bac889cba314a", + "0x92278d106ad2f27eacdb86bdb1faa0a07a93765bb79dcff191873c52253af83480114b2299ffe5324f9c31d0abbdbbd1", + "0x8900ba95a90c447fb6fa1f528af3d7a378aec25feb0620516b6b97e54b328fc31af42e46a8ad5e6e3029d83a6f2bbe5f", + "0x86053d481179c1ac910d5e7b9a5de82794b442f20e854583512ce1f9c3f09e71d1bf97d6700fe776debfe1527ab97a82", + "0xa32a60de492fc4340336416bccbd2591b5e414fca0aead82281212e24490acc01747537b3da783684e27aeb987245cc8", + "0x9820fe8e0338f21797143f368177e3669a1f3894b40ae9fa3b353125f7c8e85cc424dcf89878f2c7667f65db3b1e4165", + "0x934d64711b4348ac5e1395cc6a3215e5643b540f591380d254165486b0ec2a1d0d21c7d2c6310f9e0eed3d08ecf4b57c", + "0xb9fd32d589432eddcb66dc30ad78981360915854cc44b2afeb826b5d48a08e377dc91be66f5bf1e783d1a8bb320f7ccb", + "0x98c972cf01efff4fc2e485b47572e2d8dde22461d127ef401b71a111b0603203971e3cde40912643affd7341cd27e57a", + "0x8db6c1620760063edabd376f4399b6e1355462e04f5c81cdcb3989fdc00f9a466bc85ed899e886c89c149adad69edbad", + "0xad7b7fda0aa6e2aa66a27235ac5cc680aa04b85dce329fc4be84f75c9c961120a3d9e446aa44539aaac8ea203eecb4eb", + "0x8ccb01eaf41d816ce69ebd57754859e263530915e775c4e7d9dac37b2457a9099b9ae9b4c6cb09eb5ff246e3c9320c59", + "0xb895b83b5f7ca46e02697dbaa6157df6c7571864c83e504a8c77d965bc2ba97bf9353a71c56a020df64498bd40e30b21", + "0x8018c07a81c522fbc25f2cb14f2321c61b98bd8962ed8eb7d5823dbe5d1958a5ec2fb5622fd0868e991bcb6cae016ea1", + "0x95b16364e94d01b3664812264d7185032722a4afc23bdd33bc16ae87ee61816c741657c37138d9312cebfb5fcfbb3b2d", + "0x94a709209990a8b09bfb4b9581ab471aae3a29526eae861108b28edb84aab6d28f1d7a25dddd8150b70af34bee4ca2e4", + "0xae06c80839c5a13269b984ff4d8a5938c6f4d8d647b1b1daa8cf7f6145340b76a286cd615ec251a65501e6290162da50", + "0x875cbd0694eeb90d3567da9dc7f570d97b02bd9cf17bfa011efdd48f1d580608a3213bff4006603b8b4079fa66bded10", + "0xb27f88c455f025e1cd902097d6a224d76bdf9c9195adee30bef4a0b0411fff980787285896e1943a62271d0aca531446", + "0x8024880cde783cdb2b863e3dd856be92bacc5b2a1347e96e039fe34279ce528560d2df7d4d1624a4595dbafb40529697", + "0x8883d02c2a5c0e026d941c785128d4ac6f7a9de625ea735b7d6ff27a5ba10fa4d6370d450d99a855d919f40d64f86afc", + "0xa1beb985c45fdc30ac536f1c385b40b6113ef6fabc2f76d255490fe529468847a776efa674ba8fed72180f07d3f701f1", + "0xab83bd9b007561695210e3276fde72e507456ba277ad4c348a2aec7a6e9ebdc2277cb4bd0bca73bd79bd2240a1fc4456", + "0x8db27f516153812149854fd6bb1250e843a3ae1c9637df818b08bd016a769d0497ab6087fe3b2fd4080882713607bf46", + "0xb3891dde4e00d60386aeff161b4a0fbc30bb31ee7918ce5fc0b49aac3238a000ced192c9c4c08d90de3a0ba973d7cfd6", + "0x90a2049a15c02e59024a7a1cb0adea97501c60b1c7442fbbe560054c3d69264e69627ac57b7d9be01bef498bb2a60198", + "0x87df67a4bd72444b5faa4f3b067204c4927c869dd3b29ad192d859589a9b2c1d6d35ed68310081e140add254a9463092", + "0x8f80986a8dc8a0d6408ebbcb4f234e76413c11cb0d66067f9436bb232373100f20a4fded60f08dec3525315abfaa8523", + "0xb061e10beb12ba3683688a4ae3a91600d14878ef78a308d01b93e4918efc666450e3f7b0e56283468e218934231df98c", + "0x86b9e55f3783d62e381659d3e06699d788b88aab1ff99848db328a83c97d223f602201bf2127c5ecf419752fed0a224d", + "0x858d878e29925c87243e010020007f96fa33264e89c8693af12857b362aee3fac2244057e159651c476ebe1dfbd67bcb", + "0x8fd47cdef87d7a569ffce806d2c2dad100692d6c53e5f5dfc6e274f897dccadcee30fc6c6e61373961bbc1f3ecbfa698", + "0x892f2822daf3df3a759bef03168c1cb07408df62e024747a788e94d2da325f880bb9c6e136c7f6643f45b021c6ccb654", + "0x8714e37ac24f5a198f219e7c88a92172fc3db129e044e914663ac708d8101851e7c53fce79d32d0e6da74f2ccd1d30ff", + "0xae95e1dbba8b9e2c8dfbe1c202e9ccfd04fa396470035a699b902fbd86d5e6a31732a7c8cae00b9a4f6e51c8d560c7c3", + "0xb0cd058e77498e860fa20c5f8d9bd09bb249add1badf84ba8d1bd49e704b9b4bcd67a5c3d211840a2c8fefab3fea639b", + "0xb78e468d3a7da0dd481f333ae56534e2ef97587be2e259a458e25aa37952aed1cc5f835640f812d8052f5bada8f57b12", + "0x835de7965c6b26e7ad1b92eb6f0261d1f376fa12d61eb618d9b342b597c9c117a5a8f6a36269aeea88072b4641e6b5bf", + "0xb4d0eb99136b3643468c9c48a20fad62785a60fbdd3c054efac4bd1fa7979b4c9ca6c2c0b18069c0912bea2f19832790", + "0xa00c47315dc0700a850966836a95f3cebfde04dd094bde0742dee77b89a05b5ad655921f86fafd1e902938ff34d4c58d", + "0xab13fa0afaa92229a71ee91efae6d1b15f14b6eacefffb7401d41d0d6db24e24a8dbe8ee19b4680ecb69d2a0cb4e84e7", + "0xaa56c0fb18401210062dbc653df8e3732aa8921a1280e9737e99b26a0100a13a9cba8ad0317a69bba16193362ee0f030", + "0x8b410324a6406b345df0fa25f541ac20b7313fa55832752f70cf4c79f43b0bd3d5b4cdc447e6ba7bca08d0edffa8e29c", + "0x893362241ae412d9e5df46506407595c58ffbd7fb1fdaf0694c3432470599291238997abe118bf7737e56a4f5c9dc292", + "0x921618194a756be81cb49d6357cb392b32cc62d96c8ffb7e16d9659a0f226a0436bd378da7b835054dbe0de2c6372ef2", + "0x94a2904f10994928ff5367b777e1430047736fbece33442cf452018bfdeae62e84cd75cf80f8468285e347d504c94111", + "0xb4b81545b767f380bfe10e0fea9c3cc62ca8db40b43c83ffb245259378731298e3eb6c3bdc3a16932f88f5d8a86edc4d", + "0x936203c2453ff01c6fc635e4d54320d69e60047d805daae3b75633c2259108497b778f011e5a057249f11b2b888ea76c", + "0xb90bf6378d29339443c3f2008b1e2b5f0345f86e393027f14a295e583bf6e6c2b10f54b6dcc42079ff0d356c405b03bb", + "0x916913f550d327de2d8d6c7723dcef2e3869efaf95fd963d95c8980b97748c61ad8e2e629cead8577266d93fe39203bd", + "0xa033c6f3d5ecbabeb83eb363e54e5faa7ed2d7f4fb771b161762c4f003eac4e1afb236806b784baf2222cad54e2d3cd9", + "0xab289d4a5771147e6c29ff9ac2bf65d70081ea6c6af2d9b728c3c144574a31b5fd8632af57c18c389aa2cd994938bb0b", + "0x9488da2019ff13e290eeac132b491df58b5b7b23c2898ff1a67bffd7e9c9464c39bc8177a57950fd28589e3d9ff9c6c4", + "0xa5abe42b2e0891851440fb2aa6c1d8a86b571bce8b80c8e9e2692e5cb6d45a1b2f055c9fc4c74a7cd292871604129ea9", + "0x90bfef698e83c2ba4dc9304aa01edd274169a978b7154bca518daef394f55857d0d1922ebef3d91fc5ecb3b895d9e0ec", + "0x92328f1372b6406ec80786041b6d57018b8507e3881a08727aadfecfdfcfb0824394cbb1150117ac5da5d71b89e895ae", + "0x9719751c5f7a65ae2bed8aff7b4b8c34539ff011b259b7ff54f63f9d987b3fbdce5c99534ed561aadaf07bb6e939e208", + "0xa151816774aa9379fccec21cf212429a1c68cf91b055cbb9d931f461a8d5616c693331a11ac5c6fcfbd17d84ee0b44e4", + "0xa72977b1285618a45943ad00f33f37102e2885eccd2f76785254eeca495068fb1d8d49865343e9e8313c6c2c3b2024da", + "0xa6f5ad2e023a1585d90625c9f7094f0e8851c79f0eede8ec582ee8e063407cc5b8298e5fdc4c786e4fbbcecaf33e787e", + "0x82901e008febcea0c0a14ae21d985a397630e18ee6e346f4a449f23be228e8f338df567d30211a11180b94fbc5204bec", + "0xb9b57fdb8d14d1be87a25f89553b3966eb7869e0519ffdf4cc4d51f4cec90d68f7b81cdc0450e04207276e9c63ace721", + "0xa06eabcf43585a001448f3dc30411f3d5b74fd0a695c81eda9981842ba2bb0081d3f5a8360aa18b6d43ef13ea78b293d", + "0x926fe48a7e8f07559b7237beff9504476dd97b5b4d67acd01a3633358a6ba4c7abed5c87683a11209aa2ee759888e00e", + "0xa716cd3a84a963e2a5a46145b6ef4ebce705de52bf2945c374152a1e41c228a9c4eae0b6d1e222c1eea8b9c13c002177", + "0x8a9b5985df6fb32cdb06ba1591a977545444478f2fe985ed1b10de61c630f0a4693c2185d63f0dc0256b208072c43b17", + "0xa8eab26ae0ebcdf96a59fad1dc2d5e83b94abb2ea1774b607023f9d9e0fe065853b1e2242e794f989a80a47f550c0bd9", + "0x84adbf38164cd04f3d770a7f4b8eae7a5d25b4a803fb63c02b95b71b33e454319c44e07a760d22bf5f58e7e372d09a16", + "0x90f443a3ba1b9129a0bee400b5b29d42e50bb2aa56b0022bbfc3c6f8d69db40299871ec7c1b68421cc89e1af6b13a39a", + "0x81c5a94b379eb98c494a8d0067c748ba47e87a2ada0105202ed7651eb4e5111a0cd8569b06ae68d392c4fd74a37833d2", + "0x8f92324b14a1549ee0b186073a26691088e41556d33b54258fc6e0b000e9624156db4e97861a0ec22960e6c47ca8a1dd", + "0x8b021cd0fffe055068cc460aec3cc455952e2ac32be5fa060e0d1b6cf30ed15381618f801249e893b1b9f10dd82077b0", + "0xb3e9f0dcb3d6f0b138f589fa54dfb01f849890ab97016372d004aac55103f363a64bc0e606ddf75430f1534a30fc522d", + "0x8fdfe64af891db89b25daa859864d479cb7599486bd6f36e593f8f2f839f942261ffc3eed5001a93fde44cbcdc24c583", + "0xa9e4554373c5073e135874e2bacbee69c65308eb0785532fec6a37834e8d0b437b77a2f11cc63c87d7183b82cd9b6bc9", + "0xb4c47daca723ad7193ac5098cad4dcab654186ec5ea5c0fd014a3ac39726be954565a901694ba211820c011fa1c59e18", + "0x8835427e86cdceb4c11cbea331ed724e4e78af15e3bab5be54f6b926bf66b5d99bcc40dbc456d86342c9fa83a033c2d5", + "0x8ea84590a400cedba047c2661378921a42f5ca0421da58c1bcb37bc686a2aed98afab3fa5e6ba3a51029390ef3cdf4d4", + "0xb48551170fc479d69fffb00fae4fba301e92e37cae08f596db6f6489c3b7020edc074f9e8d7465b84e9dcef1b6b3aecc", + "0xa6f318b1eaab00836a330710e88bfe400395b3081485f6a212e3cba9463f6fe7864ba4f71e57a411ecdf2bcb4d189f96", + "0x848d5137a39999141a79f4bdf91150796ba36352d8525821bf3bd6e070b352792d79147341b8254dd60fa8c36e9e2618", + "0xa8526f8904b1eac4ae2a25534aa91e8031e9aac7b8f58d8f49897e920c36c0232f4a30aa6eed305deb0f7793c115b267", + "0xb8b6a727c44c37a8388383e959d195d1d0e51a657d4ba360633d219d43c5df645383e2406c25f1d418e72b862c3a6e9b", + "0x92e64adf65b42c978f36dd03ab22ba983bfbb61944efccdb45b337ceb486beda99818bf20d32a545503c4572bb0a4983", + "0x9653bb83df66260a0bd059cd4244ef7c661b089e403d26ba777d2090783ff31f963f5d3a9c125b1ad1a1d19134f3fc8d", + "0xa74e72355e71ae5eb36dc75191643500ca3e67f18833ee981010e7e7e60a68e1b01b05901eff05014b9ef29aa4829f45", + "0x8b2139a5da14524cf6acc593144db23db424b95b8c7041d8f6c7a14a6725dda1cd09c42bb3ae26a5a3650affaa742800", + "0xa60ddff4300ca44a7c7a00a1f98441ad1438e07c30275bc46551cee1b681926d2c825cc8f90399ee5f36bb9fbd07d3dd", + "0xa04e5e9958867a5acc15fdea0d88951cfebd37c657102f6ba1dcdaa5e46cf1c823ad0d98718e88e436f260b770599102", + "0x95e977abeb70d46fe8d7584204770f14c856a77680607304ce58077550152733758e7a8b98b11b378540542b1175fecd", + "0x8c9ec93ed35a25ce00d61609e92d567459a45e39922ccd1c64ab512e292787125bd4164c00af4cf89fd3cf9deddcd8bb", + "0x819819ad0338250d9c89aceda9e217df12ac54e940c77fb8420575caa3fa78930689d0377ba88f16d38179a807135dc6", + "0x8baafb379d4150ac382b14a64788d819146480d7a1dccd3deef6889686ded375900f5df069843ef14d754ad3d7540401", + "0xab827236996bb79b447714c6993af941c5ae66248df4d9a6f3650d44b853badb5c0cb67804210e07a7b9d66ca43092f6", + "0x927656c3eac8d2eb575e3daeb77f9605771170c325bee6aeade10c083d42bd8dcbf3bcc3d929ea437001c7cf9a95e2da", + "0xaf22b212d5ee44fd4197966b9690487c38a119cd6536cfb8c181f38a94610dd9e057f95774047a446504dd96dd11e326", + "0xa44bd94b9e01e3ba36340f2ac2201ecb477495d4f1fb6726a6b439302deabb5a35d237c6a6aeb7e3b0a65649f8656716", + "0xaf367aeeae3bba14fbdb05bcc1a521000dd9d37f5c34ae56fb306d3dfda201d0329a8b6e89d98e15825cb3c6bfdb1194", + "0xabcc4fbdea43e50ded9e2fb01464f4e87fb136e960141e8d39214f92794cfab5634f22cd40b18d8c0e501f2307aad23e", + "0x920786cbd674348b9853689915dfcab02cce2a4596d117962bce36aadddf4bdd143891e22f2c8015517039a64e8aede3", + "0x8cde63b9bd57cb3ef743f1f3e8250669eed739e5fbd68c500a3cc0c12f93862a69aebcdbc69dd8f476c2eb307f572a53", + "0xb967e65a5f1cd8d5d570f5e87e7e186fba51b9504f8e466392a76d8a971fb91fd9b7565bcc1647f50d7d15e48b93bc95", + "0x8d5a87b25fedf5edd57d870304bfd9081dc78c3e3e3b38b997260a92edac7feccdaf24feb51822d2edc223b70bb4ed5f", + "0xb6cd5d340a57f8ec73723c4f3ecd6601620dc8137a3e75a5d3c578bc79a9cae86b379950c644dee2ff99dad780d025c1", + "0xb6f0a8e754b7f52a85a2a2e6512cfd017f7fb0418d19bb318308951c4e242d3c65bbcb9748da9cbc91a738f9ca577332", + "0xa89dcf7d410bccec385400dd96b1cc6af89026a431d0f531aa992cbd7bc8bfd7c5f360bcb665bda1d72efa17bb982551", + "0x97788e7522427a46c4b6258d15623ef7a565712812fa80d001e1de8dc1791392702f3fa3cce5a8cd1c5755625a0ad10a", + "0xb5338fb5e137ff625b27c5148298f27ce8f493e2527c5d0facaa49f29cae34580d0d6c3c1074a2e46cd8db3f56004ea9", + "0x8962f006d7b1095dd0dd132ffe7e87e328510c95ad893cf3b2ab21c177c5cf2c27f47d8856f87e9762c547be009d25c0", + "0x87fee9ce9c26aa476e67e0791a809e0a06a8a98facf3faea730d438d3e516cdf75d645fa75c906e4e44ab9237a22c016", + "0xb75ab972e1a1214bab0b38cc3e973d44bb233acda5b4291f5e110b6fb78fdcab93dc63f01168debd898e165f615be1f7", + "0xb5a0fb52bca279d3853761a94b206acaf313df33ae6303d9b71edae90b66fc507adbc60fb11e758888736c81d5d80c0a", + "0x849b8f0005010e684701cd3a4e59e8c89e5fec59af6d2de5b6332cde03b865ea84f07f0b80ec3404380b0e148fbd2c24", + "0x96e2b0b6fe78408f9208f809f5c40398100b2dac202c8c5c33c2189560dea868270a598c419871a5a2b67783354f6014", + "0xb234b81f996142d0df2c719760bf996544820a03195a6dc0ff6a72543692f5a369bf63d1f0b477ef2fe7b3234e41f685", + "0xb85e39bcf40da1a12a535740176f4de749a93824079deb5fdaa004f3282fdefaf5275e3418c88c419bd42a3dd2ed2b3b", + "0xa27279304b89a18a4e2b443246f2368fb8b15f46a34533179b6bd2ef683f6e98e222b7a32880b39b8fac1afa90133803", + "0x8923c22cf15c9c1964213d725b337ece9ea854775a06f75f232c4859c7142a3942f418354e33066298aedfba3cb27e62", + "0xb109f714311fb9bc431ef57911e2cad6a3949455b9f23255cd7edea35be629e07f845fe53e2b12a32305ee2f4f264f27", + "0xb51e82ae5c7d48050e405897d0053e9ea4b2714d002e88f78c9a307cd50b9c6b3ee7cb86f86527be9d964b01895fab20", + "0x90db256931c7f98bcf3bffff4d496739185e7a20f329ee7bffd4e0850a37739948ec745285703967f4ca50ec370cf68b", + "0xa0485ac0445d88dafac56bfba2563b020cfc370f54c1606c89d12cfd8a4d1336d2ba50306e476155a6f5b0e0a1f2d092", + "0xa00754c3462e74bda928da855bbf90f9077db395e32f03cce9b2955546d900b72330d247b7d607b65e130f5b0d883de0", + "0x8547d56727c3ad8b5c8ce622ed9ad86fe8cd78e6e4848c9845914b5063b17330bd10b46d8d3f18f83ca09ecb28d1afb2", + "0x95b937b2a979bce0e159ac75c7d5d659be8599c92305e73e942aab414793364a3ec28c7c1c8491a5750ba84a29828d8d", + "0xb011e150f0294e45a0f4c69409999d0c2e602449dbd67ab95e8258466687cd733a0329083a31b03722f4e2580ddc95e9", + "0x924651a733ad5e5d9adadad3ea6a6babb8e455c8d5f2cb5bdc83fa422e7752592190ccedaa827b866861e73506a6968e", + "0xa4d5180122f8e31503ae027e54da50f72f5cfb910a6f7309bd882b5cd666f454672591f1f20e461e182a47d03b47052a", + "0xab19ae659c4f73ea3d21895269dbec583c7029955a36469124ebe295027010faab56c4a475973497f28e9a77c03b8fd0", + "0xae7ea1a803d0f439e91494f8f35fc1167dae23834c0c699ffe65d3da8b09f8df5a53195a99ca7b8558242279e69578fa", + "0xb9d63cf0e30f9800101b43b980bcd2f229758e74b21ad5354866b4e684791c08a184330dc316228a0d67fe0210f2bc4d", + "0x8c41629744391ddb96dcbbf9cd99b13d36e57d65962e0aeb92ebccf1c4cc769626feb3ec0363def08eceb102b3dd4ad6", + "0xb2848ff24faf9e667a8c19d050a93896e9e75b86595f7b762c7c74ccdfb9db126ae094961fee7f5d1192776c1ac1a524", + "0xaf013bc29206743ce934d5887b8d0fb3667c89bda465d2321835a3618513fba6a459dd7566268220ffce7e0c97e22b2c", + "0x8bb799e36db1132da8e8b028ea8487dd3266b4628c56dfae4ea275f3c47c78e3d7445ab8d0aaee4cbf42148b3a148175", + "0xae2b81fd47c038b5195a52ab8431f0d3cab4cf24c4237252d955aad2156adc16dda9d3270157e0bfe5a44022e5c051ef", + "0x8e0129213b1698d2ec6df132356805a8633ba79e672e586dfef664ffccca71834253ba14f296da962651fcba2c002622", + "0xa1ae30b500ae77cd9bbb803d737b4a5991cc780618ac22b5cc179efd8fe10afb8c135457f2e7b86ded485ea12eae70e5", + "0x8a39723077b7c0df6e3bf6548afa3910c214ee275951fbe5155a39473be98099626ea14d844630a6fa90292b9594665d", + "0xa628386c79b61aa7314b01d9814aeec20c2a66e3deda322a39957e7135c2e52b1da486d1b9cd61c87afb22c1d10f6462", + "0x97867f469b01249820aadd9a54e12d4fdadd4555f2d530450e1f8f6d2dae57360578e2c2c8ba41e3b5950df596537a98", + "0x97f192d0457c217affa5a24267dd16cb4c01de8fefde9df4884e1906d2f22e73382dcee6c7d910bf6430bb03f4a4f1e1", + "0x86d5b5739de8442dc74d0d8dc78e49210fe11bf8c6ff0f0faecbc47b64812d6b28c8afddf6d9c0212f1988451d6ccb1c", + "0x8ff3312ce9693cd4a9f4b8e75bd805f65b0790ee43fd9e075fe4cebc87185bdf161335049819f22530f54fed2779a5b9", + "0x8dc41d85548bee5d51941d55752a500bde3c5a8f3b362da4eec307a963968e26605048a111c9166d448b8dddf6f53892", + "0x996bdfd004b534151e309ac925fa5ee7801c9da4f6b4c43e156d1158b134535a2a3956e1255e0dd72ac2af6bddaebcaf", + "0xaead652704b788bf4983c8f725c644c327a6e9f6683215f5c826c09f82fd2e40631791f51d14e6aded91fdc018d45501", + "0x991ffab58a82b98ed8fc7b00c3faca153589fe09cebf6a137ad506387a1ca4dba475b0e4a1b9bdad829f1422facaec39", + "0x9652e6c4ae084221d6bad855ec0bc11b5f855c6efba67f644e0902ab790a98861cecc6ce047c68273c3aa7eeb2f4c7d9", + "0xb88b816507aaeea6dc92b861eabdc96988b74d7883f20a4b30ba249158acaff3c50d261742fc9ad2e9eba888a8d59065", + "0xacd028a51e16c07a10d2073b9d03070457ac5f1246365295a1359d015c460b92b4861125fabe6f114de8197045df408d", + "0x806d3cd9d02d41c49179fe7dac5b05dcfc9a205a283135d4f008d0771c58e6f963d7ad0f6798606edda718eb5c7ff3ed", + "0xb9b71f1657a6b206fc40159a941e127f252a7b324dea864ecd804f48c0ed86da9778a925fb65491204a92bc2a26fef32", + "0x80ed67bd0e74350c875abedc0e07fd42ce7cb926f0f3fb1949c6ac73f2300b5a14a5c6f6ff8aed99d5ea5029bb8e7ae6", + "0x9875f67a7a473714e4dd75ee0c763ddf88101532d9680724b3848fef69e218b04a96b90f88e0f4409aa40b9a21507ecc", + "0xb4a2bb1b421e5243e5e7576a0672dc19f9f70315a03f6411c19f76616ffbb70fc5dc0e57fd4ab85e24ea2261b7ce38ab", + "0x879723002ce43e6c75ba2246f51436efe3376242beff987d025c3c4476495af32d52a54fad5d9ec329a442b93bcff1ce", + "0xa4121efbefd9c3eb143619afa52a916f199c75024908047763b29466cdfc837c2fcc894aca63044c33c41c777e529b5b", + "0x895f637b497a9766714a3d9e3c275a1f0c9ddab105bf4c8b7e663f36cd79492022415bb4938c1a4849bda73106ace77c", + "0xb119acb8b161ce4384a924645a248a656a831af526cd337d97e08405415b9dd22060849c76b88a4785eb5e7214961759", + "0x802e712f4c0a17009c4be6c1e5ba2ca3b82adcb68793ec81f4489b7985babd8a3873d544de63d5e5de0cb4dc5048c030", + "0xab111051e4651b910c68ecfdc33f2d99e7bf4182df68cedbdbbcac219a543e04d93ecb2763fe32b40c095c7ca193c331", + "0x855c73ef6afc6bcaab4c1e6388519fd5cbb682f91995bebd558167715db454f38012291beccea8186a3fb7045c685b67", + "0xa29d02ec6d9baf84c19dfd0eb378307703bfafc0744b73335550f3cd1b647275e70215f02d1f4ab82a5df4d4e12dd938", + "0x91510a45b8a50cac982d2db8faf8318352418c3f1c59bc6bc95eab0089d5d3a3a215533c415380e50b7928b9d388ff89", + "0x8286e7a2751ca4e23ea7a15851ad96d2cadf5b47f39f43165dde40d38ddb33f63a07bc00600c22e41d68a66fd8a0fa51", + "0xa413d4e619b63799dd0f42ac57e99628d338b676d52aec2bb0d1bb39155ad9344b50cdfe1fe643ff041f1bc9e2cec833", + "0x85524e5bb43ae58784d7e0966a664717289e541c8fcaff651541718d79a718f040a70aa8daf735f6635dabfc85c00663", + "0x97f0d48a4028ff4266faf1c6997b6ad27404daa50ca4420c00b90f0b3e2d82ef8134d0a04108a74955e61e8dfeac082c", + "0x8df6145c6cc39034c2f7331d488b8a411931c8faa25d99c5432831292637fd983d4f6b1a6f55522b4a42a462d63c6845", + "0x98c2060f67a916991b391e67fcf23e5f305112807fe95bdddb8ce6c4084126557e4c5f003afb32e30bc6808b30d4b526", + "0x8964246b3c2b8f7312f0a99647c38ef41daf70d2b99b112412356e680185da6810ab8ee0855ad7409d334173bcc4438f", + "0xb56c2c416a7069c14bdb3f2e208c5a6ad5aac1cbe5b1faf99dc89c7141d0259d1c6250be9d9195500c4a41182ad2ec3d", + "0xb7864583a4cae3b1083dcdcff7f123d24a69920a57d6594d0b7219e31bf0e236682442b6499a1f6795cfeb4f5f236695", + "0xa064f94139bf1b70d476bde97099631b1284aa6b4d87f16bfc65c075e58b2f1b3c2d057605259f806e545674a1169881", + "0x80d1bc4acf14c0f487cd57c5d6157b7f38917e93cb660f1c25e474fcdcac3c3dfda50f6bcccfd6676bae25c4b6b5014e", + "0x8ad9a4976c4e3e282843518149fcf5d454240740f4b91466f6310b7216d23d70b9b47c42870293252f29f092f330967a", + "0x914197593d2d99d784c704cad7ecd3f0b9f55dce03fc928d13e1a1034566c4de754f1c2a5ade047b0956415fe40399ec", + "0x8d77f5e29c572ec3c0ca39cbae2072ba4102403265b3d8c347a00386da9c0b8688d6e3280c96037c300d57b3545f3773", + "0xabfdf79d935fd4f06a04938d6580a8cbf9735f0d498f49677f26e73d3b34b7075d525afcb4f14ef1632cb375bef7dd55", + "0xa97a8c446e3edc86efac7bda5e2e5d0158c909552a3bf86151df20ece63b8d18b608f477286fb1c7f05605ab7e6a7c2c", + "0x8618d946c7fd62486551c35486fa466bdfcdc63c941e4cff5a01fbbe566b7ea9dc763cbe73e2acae063060b619a212a9", + "0x8d03ee468070936004b06acf64b868963f721f37faa09887f8a82c155ad5c5732572a6855b531db58af03b1afe034a18", + "0x8d3247f75966ea63935ef6049f7c889c1651374adb446f49499fc9191dbcde7ea33cbc1f1e2d3d1756b6e69870404643", + "0xafc853c3a3facb4ba0267512b8242327cd88007cef3bf549184ee891b5ddc8c27267bae7700758ad5bc32753ebf55dae", + "0x80df863eaea289de5a2101f2288046fdbfaa64f2cf1d6419a0e0eb8c93e3880d3a3fdf4940f7524ea1514eef77fb514e", + "0x8434b5888c2b51d12d57da6fb7392fff29393c2e3bfee8e3f9d395e23ddc016f10ebe3e3182d9584fddbd93a6effcefc", + "0xb78cbb4c9e80e3808c8f006dc3148a59a9cace55bcbb20dd27597557f931e5df7eb3efd18d880fe63466636701a8925e", + "0xacb140e44098414ae513b6ef38480e4f6180c6d5f9d1ca40ae7fbadb8b046829f79c97fe2cc663cbccd5ccf3994180c6", + "0x936cb8dc959e1fc574f6bb31f28b756499532ebb79b2c97ff58b720d1cd50dc24b1c17d3beb853ba76cb8334106ce807", + "0xadda2116d9fab2c214ec10c0b75f7f1d75e0dd01e9c3e295a0a126af0ea2c66373d977f0aefdda2e569c0a25f4921d0e", + "0x89a5cefb80c92dcad7653b1545f11701d6312aef392986835d048f39d5bc062cabc8a9501c5439c2b922efc5f04954d0", + "0xb9acb52747ce7f759b9cdc781f54938968c7eeacb27c1a080474e59394a55ae1d5734caf22d80289d3392aab76441e89", + "0x8564f72ce60f15a4225f1a223d757ebd19300e341fd9c1fe5a8ece8776c69c601938fa2d5c21b0935bd2bb593293272b", + "0xa5567d7b277c4ebf80e09c7e200c20d6cb27acbaa118c66ef71cbccb33ee3ddce0e0f57b77277ae1db9c66ed6e2d8f30", + "0xb82e9c2d8df1cdd3b2417bf316d53e9f3cb58473c4cb5383f521ef53e0af961ef916e4f6557a6d8b4655ec01415231cd", + "0xaa816dfd2814c8a25bd2cbaf66303ee49784df471bac4b3188074ea30816f00f425234454d40d8ad8035aa925d74da36", + "0x9919f384df20faaa2d226b521cab207dd2b62420d25ebbda28c9b2ca76a2a52203b2ad7844c1a25f5c75f005c5a83149", + "0xb24a6aa35c2d0f87e36598b36224c64427cd69642b6f9c1bd478a62c70f8ee69f85028648f6603b4f04fb21355f2afb1", + "0x892e044bdb1276b455eac2204be105e1821f987c2570494b1f32aa09506caba7ed343cd09b1bc126fed5e0fda3d0eaad", + "0xaf0e01a3ad954dc048de18bc46bb1c4971db2467e839698e4dd05cd1adcb9261013fe9fd0cafb946c0b586f6aad86d4e", + "0xac152f0a9ace425378daf02510eb7923ff1ed2c0f8d1deb918e4efb63655de1ba58c96438e9aa23abdf2431dc771370d", + "0xad8c7419c097709347e2394195924e09617b47ac5c7a84aeb9deab8975f22155de0f70cf20d8a976551b14e3a2683a2b", + "0x808f14f67ae801536fb70a5898ab86e50ad35340cffd0648daed2f2c4564c9ad538034b2a179a6a8bfa27e9d93b4cbe0", + "0x80a74ab7ce4769db93cfa695a166db95f0a9c47885ff826ad5d93310f36d6b18b5351c67c858b9837b925e85a1995b63", + "0x95b88c3cdd64401c345828f4e4754b1a88b4875a14c08a668b90acd499b3b858842669ecd73a46c5d9f1de32ec1a0120", + "0x8ddbd770b7b18a5917eb43926fa05004e819f1d1ead05b915269e4a86b53e0633a90559007e59f6705a3769e2126ac56", + "0xab6db5fc220754f19948bef98844e6e38dd623565d1695e1198040c228ac4fd863c1f168cac1d036bbfb718d9d8dd036", + "0x97bef628e977c069e60c395a17740e0e1bc1828f5607ae7f30ce5a0c95f02b53af2ad062700a75212e462aa22c3c5465", + "0xb68d465e04fd17ca98501e61eccb0ce30401855e98046e0c1debba71c2153d6a7a704aa36a6f12454696e78e87181cdc", + "0xa79cfdd048f4181e005bd0fbac0a8424495474956b58ce858d2b700fb0f931c406282bd33bfa25c8991bc528d12a69c1", + "0x843f55fa0a6a0969daf2b48080738f30b269b2e7ec123a799e5b203c0b3b4b956dc95d095bc6550b0013918cdff8a225", + "0xb683cdf2823036827e5b454bfe04af9bec1850d25a7a7a44aee7696b6ff0468b7ed6885a41dde2b8f3ecc4aec880c3d2", + "0x8b500796e82acdc89778e0c0f230f744fb05f762000fee877bcf57e8fb703d212dbc2374887bdc2e7b7a273d83a85798", + "0xac35a8ee87bafecb1a87f15abc7ccf4109aab4ac91d357821e417f9b1474d196c38cc41cd13667f68d1ffab5e79a6e92", + "0xb6e517739390cfed5b395d33b14bce7cd7aaece57fe79a7eb3cbf150dc10765c3ea9fef7976a21a2243687e6eea38ef6", + "0xb53901eeee26692273365b789f2a60afc9b5f0df229c6d21b07016cf4c0e7985beec748aeca52262f68084393ab038e1", + "0xac4804f33d8ba2b4854ca3537bd8bf2dda72d4e94ff7ecaaf9bd3b7f098343d74d765471ef80072ae34f860b052cbfb1", + "0x8c6a30a93f1dde18039bbdd1ef294552bf79856e20bce863e4b8dd72d906be3ff22468ff3610e06b5a7d1745dde7ead9", + "0x88f0607fa3b7cefe20a02115572b16fc3222be86bb19e592c86c48afbe7e0dd523492b0c29a3bceb9a20f5538bc3134c", + "0xa660b801bbddad725975ddf9a8f606f76ecef831f954be224d6178c368e1c72d346f00c4a4c95c289b62d36f2af323cf", + "0xa75b9a6aea9542b698938dcd6cc2f6fe0c43e29f64b2f54aeb05d35fac73d41aa7fd750af4fa9333644aab8db90775b9", + "0x83e1b7129d963d1cd076c3baa5fe422148e939273db173e4d59d1858a7d841eacac7fe817d15ab8f8a493bf46c2045e6", + "0x9060a2e9c24de11f9c70e039b5ffe9e6d32f1ae39f3dda263610df2265d917679e689898e4a8bd84ad34613dca5e3761", + "0xb42fc8b863a2af15e04d1fe6693c09b46007c0b8298973fb4762b45b4590ad7fe0aa758918b2fe5ed1ed0359754fd955", + "0x83e6de7860fb256ecf7b47506a5e557d0fb0aefe57fb513c7dee2bd9604712d08ca26adca7ba9a54b712372a7c585a26", + "0x90586e9cbbf71475ecd3e7b5753b286804dcce61e165502a82b960099e79272de8b7494b8877b54ae838eb5d0f71af2f", + "0xb2e4b0d21208f73b7b75e08df80cde20c4578e117d37092a490af82354e2afd3a7dbab46fa2d12fcb731cdaece69c2ba", + "0xa010961239bb8809fc7fb4aa08fa30d33a130f9f417ee9ea60f587dcc5ef4e1b7abcdcbf8e848ecdcb7972ef6af46e78", + "0x8f511fd58d1e3403a5eefdc0a4ba6b8af848c7efddbf9575ee84449facde05ae9a24aa41a5725416467f6fbd11369c52", + "0xb24ebbd2d4482eb618cea1ac4fbfd9ed8c46c0988a27259300a7ce5ce1bb256aeca0357828cbbc4cf0dfafbf586040e1", + "0xb3ea29e9cca55250e9b7b9bd854edae40f0f0cc65fe478cd468795d1288cc20d7b34ced33bd1356f1f54a4291faa877d", + "0x8a8b20f222d9e65bbde33638033972e7d44c6a310b92a9d9c5273b324c4ad1a94f2a10cbce8300c34dbd9beb618c877d", + "0xb2436a9a647dc3f12c550e4ddc5b010e6f9cb3f3504742d377384b625fc38f5b71710a49fb73ffaf95b9856047c98201", + "0xa13f8b77c70621e421be94c7412454adc1937b9e09845c2853ef72cdbe500e5c1bf08e3c8b8d6b8eff4bce5b8dec9213", + "0xb25de8780c80d779e6c2e3c4e839a5a107d55b9cccc3ad7c575f9fe37ef44b35db4c1b58f6114a5f2f9ca11e1eb9c5fa", + "0x96ba6ad4358c7a645e5edb07d23836cbd35c47d9a66937d09486570e68da3c8f72a578bd2e14188d3acc17e563a652d7", + "0xa7f55989814051fda73f83b5f1a3d5385cd31dc34baf94b37c208b3eaca008ff696fd7f41e2ecffc2dd586de905bf613", + "0x882d0c7c81e58eb9560349f35c35e4498dcde7af7be8d7974b79d262304c26ab67ffa5ed287bb193d5f0ab46b4096015", + "0xa607158f0c1fd0377a8ee5e9715ac230abf97406c19b233d22f5911ebe716967cc10425546dc44e40c38bd6c2b4bca2e", + "0x87e8cde50e5d852d3f073a43d652f7186bac7354612517cfaecd4a1b942f06fef6f14546279c0dc0262e2997b835b2a4", + "0xa1c93acc6db9d5ee426fb4a0b846bb7a7b8d5915bec777a9fe6907246b0beafb8938941c8c79ed6082155f75dbc1e332", + "0xb1e4f61457b86f76cd93eafd7536f72baf239ce5a62bd5a8085a34e90576b1e118e25002d2de49b01d6e9a245ee7d3a2", + "0xa0435fe9a4bd1031ec5973a103ec9396b2ce9fd982f6d9ed780fa80ac06a6e47a0a6eb2daf52df1dc9292db622ee9fa3", + "0xb66d8e8a1717e4bfa42083b6ef4490e090a73168b2912f2111743e089027be0a4945a229ecf5d0b5eec11b23f0e11303", + "0x8eb764f26904eea4f4169be6e75beaa6a39e4eb524625a15a78befe3d8e3cc82692d9b135590c20ed460d6e4ba630ef7", + "0xb7e4aea6bb09829e53fe83e53f49a7a331a6d7bf76e0073d758577e6d6fbe63dab642b23657355cad48896ad8715119c", + "0x8f94207982373a99ffa282673f192aa98d0c4461fb77c31dc4549628bd9687a249f1b3c66b1840929341e42516c5c64a", + "0xa9c673cb247b13e17fa5e616f0399b7f5c7ad043e143e44ae68855a840870ab3d2aad737ebcf74c2cc9688d17ef3a794", + "0xb02635104dd28c02068985256975c0af783899eb996e37d021d9a35238deeea9e836760db21869be7b6c82aa687ded29", + "0xb33bc0966389710812b5f6698afa3e9c84839a1b85492ba11e6ded26695260abf66be6fb355d12d3a8524966f0f89e0f", + "0xa79c0dd09506951c33da3cbc23843fd02d641fc24c640a205e6e8150240372847312b9381fb03c5d301fe4dbee8d0da2", + "0xb74de6f3a2c502b5b658ebe8a9b7edd78afd036f5a2736aa06502863b6865d131b9e3542e72a86fa2e1d2db4927661ed", + "0x99e365def1452ff9fb4b9eccd36ff4154d128469ba5bd73e83ae457ab53977cf6fc04a5d05bdcde357ab539e34bd9fe0", + "0xb4f2bfb95abb47c67870aa6ca38ac8f3ae1b1a2bed064b1be7ff90865ea12e4930fcf66429c7ecd1183fae4a01539386", + "0xae4bde87f36b912e92398bf72e11d5389e93b2de1b277d7ed4b6fb5a9ab9f71a959ec3bcb734c11079440fe42b86fafd", + "0xb826459e568efdeeb66688482b67ef5020787275123fd3192f979b6175e3b0ed59e17cb734a0a052bf13f0afc7bd237c", + "0xa99dd735f4a7c85cb23dcc7f4835f9ab32026886909aaa95876b98029c37dc4d621726c872d3a9e50403443c958f4029", + "0x99083545034768010988bf8a9f34486c2cd9da27a1d10db3ab86eb69a1dd9c8ee723e7da4ef2aced63c1dbd53ccc52cb", + "0x8ac3209349f0142546c714ef7e9d1b094aab5469b8f080c0a37cb0362da5349e108760f272fbba770aa468e48d9a34c4", + "0xaf5f48ed74b21e3f2c1430192adb4b804dc873cd7e8f07130c556c30e7b78df0ef5a14b205368848fa9185e5a68dee0d", + "0xb8b741b65d68df89443523ba74203226f1e0d13bab073d183662d124e83e76cd318b2bfff09879c04d81b577ac895638", + "0x914abe4282d11176d4f2f08c6f15e6c2d0cde1ab4de00bbe888015c205f51929d97296a0a8d3ca5641f085a29ea89505", + "0x83ec306b2a9a6780efafe799df90b1aebdbff7d47921a136ea8a5648b9708a97231245a1082fea38e47ecafbbe000528", + "0x95d6b58d70b388dfcee4eda0c9805362ccfb60a87603add565b175b2c14ed92999dfdb0d3724ee3e5d30535f282641e9", + "0x97eeb4de607c8306e1d4e494f0d5db126d53fd04983ab5674ec5996b971899e734fa4011f2c889da21154ea1e76dbd2f", + "0x84ff21977fbd873ea06bec444d4ec9ff0e3902edc29dfa25f3bed269b3709e3116e99dc06cc3e77f53c53b736bf8fc29", + "0x8ecf483874a040a4a1c293af145094fedf203a5eb37c3e165857e108cce3e1210e0bfc0f26f4ae5e2194024929ba034d", + "0x97d9b92b2ef34609d69402167f81bce225ed3a95718a3b403f702b93e96a121a8f7f072d0ff47e8b25164e204d1576bf", + "0xab87c39cca1803b4e84b32e40ff30289e3cbbcfbe16a70f9e025643824752359be1f10c3e5398df402b6fec64d5a3537", + "0xaf84ca57e6944332884b5c84750afe0d5950015e127acec161853d55d48fd864c7da8d59cc5aba4ceceac650b813fcc0", + "0xb1d23d98edbe7089ce0a8432e0eb3b427c350fb4bb39eb2aca3c2bef68c432078cb9b4b2c4966255e00e734fa616638b", + "0x8e2b5252e0ea96d40835ebfb5693af49946509975682d68651396d6bb1463f09e75fd0afa04ccea49893b5b9c3e77e40", + "0x8db25e762f1d4a89a9a1cbc61c01698e775906bc88a921b2905735457a35df9ab84bae12e1b1b8dafadd50212f1acda1", + "0xb5f7cd163a801770a4034e2b837e00191b0ac63a2b91032ae9a99ec182d748798df48a14644935fabdbac9a43a26749a", + "0x998e7232e5906843d6272d4e04f3f00ca41a57e6dcc393c68b5b5899e6d3f23001913a24383ed00955d5ec823dbd3844", + "0xab2110a5174ae55ebb0a788f753597bd060ee8d6beafc5f7ce25046ea036dba939d67104bba91103d7838b50e36703d1", + "0xa211972a4f6a0303bec6c86f5c23c0d25ab4df0ba25876cbaad66ae010b5a00aa0c5daded85e4326261a17a563508a25", + "0xa49f53496a4041a01e07f2c2cf1e84e2ee726917bb103fd267451b9b7bb1331c0afde85a79a55409bfde27328b2a4745", + "0x934e915c67c7fc47adeabdde49f63f04644fe234672003be2aa0a2454dc8d9288f94293478936a450f2e3f249d395b5b", + "0xb6e69e9d6808ff7f60a01b7aea6781495d7a20f5b547852d3f0af727a7434209d3015a9dd04cbe3e272918e32e345508", + "0xb348d3462092b5c6fead7e515e09611438db8d69650876dd3b56226e303252bbeb9e9f3b888fb911445b0c87132a1d0e", + "0x8d6510334a905efe5a32001e167f1ba06f9bc4af7ffbf11b7f7bf3c0076b5cca373d8c47e98c1ba8755bb22632bfe0e7", + "0xa2d5200f20985dcd473d119ee97e1c0fafafa0f191185bfed9cac429cef8198d17665dac4f70342eea66e6e4a7370d58", + "0x8dd7eb6b1841b3f33425a158d33a172b79b2dc8a01378e4174e67a1a4c8f4b887f02c7c3a8f354ed9eac718155bcdf37", + "0xb16ca19388642f71afcd9f7007b490d82f83210ac1a989da9d4bf4c419de07af8c048cd301ec7e01b9d06abda7c169d5", + "0x93cb2d847d1a88de8c1c9d5b3c83efd0b7afb3682942bd2c8ab5ef35b33dc31a097a3e181daab8630d4e840b677216dc", + "0xa8b648c769e77a7b41c0c689fe2fba9bc585067e004bcb1732cb7b1618e97b317781c36c23a00680fc780b58c301a789", + "0x918c321100d57712866bdae84edf7e42df30a32853af257e0cb4da028842a43b49e775f3cecb85cd817269c728de7319", + "0xa7b0f6ce42e00c519e69b2c78fd9b75a2e7103e5892d3c1afd70c9b5b9e706180a4bf73dbb2d3eed52bfd521103ec5b3", + "0x90041994af3322b010891356afd8115340bd7fd7ba328716fbc4fe458236c8cad8c7564ae473d6091ec3a54bdab524c0", + "0xacb1ac83809573846231f9be2dc5f3e986cc36dd9574a620b1cced45bad0b11ea957ce8c6cbf964a0af916781c574f05", + "0xac54677dc002698fc4d454c7beb862ad085d0514f92576f3485a44c0cb47afb9db2c085058918a3508f9b3de0137d97c", + "0x8dea56e1bfa150e442f8484b2952b116781d08cfa3072d08657cc09b0217276efc4ab6f5fd726bfd826f6976ced8da29", + "0xa2b09e25baf01d4364b5205fa0c4dea84ef8fe03709113b034f88a0f0a502a81bf92c1d4641e2ac9f3a6f4203d3645ee", + "0xb95fe37aa351b4292691a9c2e547224c37ec2751a31ecce59810cb2ae0993da6fbe5efe0ab82f164462fa3764b6eb20f", + "0xa3498947e91a3a540e86940be664fc82f1e83ff41a0d95eb84b925e820602a41b7393c8b458bd4ebbe574a754586787a", + "0xaa2516d3620c832e5728fefdb1af0be30c871cbad4b166a7a4565af676e73bddc2f2f51acc603b3a022056daad2b330e", + "0xa9251b56467fb55f64c70729e2ec77a59d7eac79cc0b4b25ee405ac02aea46bf1cbc858bc773934a6d9bea57cb528185", + "0xae8c0a4ca7ba6bdca8764bac98df0581f00358db904e57867e6ffdf15542e55f7bad2dedac152ef88038b466ed901934", + "0xb0881e27e52cc6a57c4f3f278dffc7f63a9174b68bc867c16d8a151d9cc4d0aeb703d1074d1927faa9ffb43e10912c9a", + "0xb67138465d6654ded486d18e682f11a238d6a65d90f23d6b13eb6a1b7471efbac9ada6345dfb13e5432196d2a256829a", + "0x944c69a6f1126edd38f6eef60b8a5bd17147ab511e44e8e0a442e87244d8f35236ee0b8d3dac0631f8598f16486a5f74", + "0x995679dbe03dec775da26708cb9200dabcad983825f1ba601eb9395f9da350ca71e8af61dbff4c668fd0eebac7e4e356", + "0x89de362f02dc14de6995d43cdea3c854a0986c605ba5eb5dacf24e3a85983229bc99a2fcf50aba3df59f0fb20daffe29", + "0x84607f0e2d078df22d0866285614f5d78cf7697c94a7d1b5e02b770101ceecbfd53806b377b124a7320d9fed65000b97", + "0x93e3faab60050dac76ab44a29bcd521813e76ec8e4ae22712d77bb489bb49f98f9087acfd6a77016a09a42ddedab2d73", + "0xb7d64a7a35f21747b8e6a874be31ba770c0d13cbd41448411994e8cebb59591295a26bacbf74ee91e248a5b111aacca0", + "0x8dcad429a2b0d66b9eb8c1c3924d7a72979727db6a535526a3518bed2a9532d12aad1c5a778824ca4cb98e3e513f85f8", + "0x980882895faa347bd2fd1dda7b8ee7ed49e69843afe646f677b371eecc7a10e0f4e40bb55f28995a40080df471876816", + "0x89e8e7fb51df79971e2f7bf65783614abbb0d7f3f1b4a15d3f0d160deafa7ed1c446d9a5ae1a77160d4dd94ceed8af13", + "0x93fda8d350392e9c4d4ffe6534f7e7be53f32483d9319093e8436fbb8166a3c01085dc858373e65c7f4d014e0dc2bab7", + "0x897521a87b7ebf7152de5260c0875e3c7df1c53e734c672569219ee6f9bd196c5ecef159b6a1d3b7cd95e91b9b8803ff", + "0xb59affa408a0f7bd7930fa3b88750fd043ce672c10a3adeba95a12f23f0dda1793f761a86f7409ce1e6fd3b3b7195381", + "0xb4422ccc12f4fe99c530cda610053af9ffe635b633d52492fd81271d1f6f91b87171d572d5bd0e46ff63e221fb2fc4a5", + "0xa4542cdf3346ee0867c08d630c2aefc57442f1c05c0eba52d223bfdca5e9d0bb80775cff6ce2e28aa2730231fd7b1bb1", + "0xa7d297bb09118b914d286e5d1e87bdf13f7d174b988e38fb5427902e8e8c674072f36b19055a1070abcf357f8668f35b", + "0x9213b0ae24b7cb43ae95e25c09fead8bdbac55141694137d67eb5eab5e90a348a13d4d4d2cbc6436fc4f4f9f7334ced2", + "0x8aed71a0d116d832a372b42a0bb92a1980f3edf8189bdbaed7cde89fc0418b3ab21a04f5c6e1d3b8edf73f1f62bd6b15", + "0xa6c47d77d714c285c84c6b9458cbec5e3b191c0502dffd10ce049cf1ea27ddf868ef0cff13a2377289fa6c932b8e4f28", + "0x92f45622ec02483f2c1e07075a6695416d3768c8984856f284f40734346d56cb5b3322f20c2c9f0ef8e58ddc294a309a", + "0xaf6450d02b79ac9fc79f35655b58fd3619cd5d38c5317564b453f5f2d79d7a030bf767e399fe01b658a72fbd2cac2356", + "0xa3c01fed5240eb8a61ffa8ff4a120dbcebb53b8e19845949c77fb4f9b2c3dd52c7001df6219ad2f76c785a4ee0f64a2a", + "0xaf3136bfe8f774187bdf87555a1ac505322a956229a285d28bab1c88d4f4d12245af8dff35914a62e90e49f3dce6acb0", + "0xb20e21d28444fc96737958cd951858fda324b924b4d3d08932540fd4b87150f053db6985b96903906ce83dde0578cbb2", + "0xb7978101071268d1f485134b4dfd1e35f89b82c7d99ae91f58b6745f5e0273b7e06f3b23009033ecc3e41b2e9e85219b", + "0x9104b7d75245b784187175912cc0ad869e12f1983b98e052710fb33663224362bffd69ceed43e7d4ad7f998c0a699eb7", + "0xa7624cd71b92699ce3fde0e747976ee04ee820032ac45dd27d769edf3b3379a4b8db358e50c9d057c63b5a9b13d76bcd", + "0x9354a76f294005de8c59db10e638ae6e8c6d6b86a699d8da93143da8478d36116211c788d8285d8e01ea6647dfcaa1aa", + "0xb85935c04cae14af9848db5339ab6420122c041075ec1549314e3c9c5a610d9b794ea3617c50ca7af6b4aec8b06bc7dd", + "0xad6835a62311c84b30ce90e86c91c0f31c4a44bf0a1db65bf331b7cf530cca0488efaac009ab9ed14c1d487da9e88feb", + "0x80339f0245cc37a42bd14cd58d2a8d50c554364d3a8485d0520ea6d2c83db3597bf51a858b10c838bfc8b6bc35619638", + "0xb370420ac1a011f6d8f930511b788708ccf2fe23ca7b775b65faa5f5a15c112a4667ed6496ae452baf2204e9ce0dbf09", + "0x8ceab3dadca807a1c8de58ac5788313419c37bc89603692c7a4d96e2311b7fe9e813cc691a7e25a242828cdf98f8bbcd", + "0xac1526ebc6bd4ac92ee1b239f915e494d0279fbd065e4cab1f1b8a1663f67daa89560f6c99bbc3e63fa845520316d2e6", + "0x8240ab0bc36a29d43ec3059c7e6355ff39567e135f93b243145d3ada97fd1c970743819e0d58bd5171967daec144e7a1", + "0xa99743192a6f1967511b2d3038cc73edacb7e85f84b2926d8880d932d2fa12f5215592311a7548494b68a87ec70c93eb", + "0x8ffffc31c235997e59ab33c2f79f468399eb52b776fd7968f37a73e41949111957434f2c0a27645ab34c741eb627cd1f", + "0x8949d955309415d6d2cf6ee682ccd0427565142c1bfe43b17c38de05cd7185c48549a35b67665a0380f51aef10b62a8e", + "0x9614f727a9dac8ecd22b5b81b6e14d34f516db23a1a7d81771ddaa11f516ed04d4e78b78fda5dc9c276a55372f44c4d4", + "0xaa85d3ef157407bd8aa74032f66bc375fddaff90c612470b5ff5d93659f8c3523b2d1b6937b3cc4201c2aa339621180e", + "0x86f8fe8bf4c262dc6a04620a848e3844f5e39a2e1700c960f20ee66d4a559a90141ef4e5091d0f32acb1e915af1e0472", + "0xb3af2eb785b00588371beb3b49536b7919a3f2175d4817de5dcbf7fcc20c512852ef0f313327fd0589b10173f77b92e0", + "0x8388703c512eea59190351f3bd2cce83ff8bcb3c5aefc114cccf9e9b3f78200d8034c3ebe60448aaf6c912f0ff8f0cc4", + "0x95d0dbbbf08ec1ed3975fe7dd542be0a05156a2b3db5092825d918a849411ee536ed958201f74a5513e9743674d6658d", + "0x8d1a48802f1a2db247e633ddf61d3ef7a2c062c48dda59bf858916e04f56651a7d51e367d6535964ebf3ae6d2b21b421", + "0x971436871bfe868f25247145a55802945409b3150008535b372c949760d7949dd2fdb40d9b96ae7473bc8f6e9b83ecdb", + "0x8ca431728ac0f156763090828a7b6d860bf591e5b9dd3bb3b7f3ba0ca74191f9710ee55efd32db7d18eab5b479cee8a4", + "0x81e28f1a506e84c2b9aba1df720cb50e0b597b2c22f98acc34e710c934cc6f97dcaf33d589e845c2c1f6d8716d05ccac", + "0x8f43b11d3f00c41d16c9bc9bc0c44227c056bd77de4f1ca9a799418c5601e744f99066bef47da2d9088ae88eb259327c", + "0x8d330aa52744c08ef98cc5599eec8b9b4dd18aa01b803f1d1ca0e29b74f1aa2886ed0224390fc377af25852851fbee03", + "0xa06f5b203b67134c685039ec2bdbcc787353e2575ce73a415db24a517c0c31b59d1de89f12b97cbef0219fb6a1e90a20", + "0x9269a5f49bbb8fec1a387b5d105df88a027de615d5ca6afae20fe89b11746f8d23880db78dac238c955fc8bb3de18046", + "0xaf5074b3bc0656421c314547b45b5abd3045ca1b17f5e34ba39d8c1f7928a55d4ca5ea9c2ab59a55909b25255233e04e", + "0x8e7ee5d733c8e08f3fb7d85f0628de3de6835121672c65374905dc6d19e02fa2df14c13d5e9835dacd609a4df09abd26", + "0xa9b9aaf83d31e879dfb8e73a0708801b4dbdb5d7c8654b27d2c0f5797ebcacc8d00a82143e2060f0917c9d41f1a03de6", + "0x904872aa1c093cb00e1c8e369a3bdae6931c5b1ed705dd3bffba243dc4f42df3e7d7cf70303d513b34d2245743d765cf", + "0x8a4d6b3b1d6afe67383c66693f70b397e510be28e3d97dbc8ec543d699b6cbb0e72eb90a7f65e83cf9f7ef50fb18b128", + "0xa914de13916e6a0dc0e0fefecb3a443cca80d83276513b70c22c6e566a2d41acbd33a0e2836ee09abeffd3a4894e437e", + "0xb9c408f5f05934b0aefab301ba22f8254c5ebbf5405b6aa788f76e4b328c150b395f441e3566015a0deb3eca89afe9ff", + "0x8d32aa2c81b2a8b89f347c2e0b6567b2117ddbb778fda8a3f19004b7f5aa9dd814b9b3ad35f9223715d2447b2d12f159", + "0x8230e8b9c84cada1bf14ea6aa9ecdadd978d893cf5962fee6c7167ed21239210ea491987f2c8f2e8cfea8c140704ca28", + "0xa5d7b6285fea51c6f21d0976a7c3a97baa3d733a201bfaac0994db6c65611d91c5fc0ebc2a7724ee02b371e575573649", + "0xa54f00a9530f6930069f5e3a8b8b1d52ee1def0aad1763e3c609ec07f25410969b43d5943a94c235ed5eb207b33a402e", + "0xa8dc6e96399b81397734c61c3a8154e55a670fa25fa5854b3c66734cbb4ec0d8f6ba650ee3c71da3773ffc9e37abf8bd", + "0x8841fbfae1af4d400d49f74495f864804f043416c09c64705251d021b3ab7881f134a00b0241e61010617d04979d747d", + "0x95acea7ff4861cc969c1d8cc8775c5eae014ad6e2e0e2d0a911dd916c34ae69f53eef779cc24ff1eac18c2b478d3ba2b", + "0xa5dce74abcfb8c68031b47364bd9baf71a91db01e45514ab6216f5eb582ef8fe9b06aaa02f17be8b93392d9b19ab9c06", + "0x89e111169e4ae2f4016c07c574a3bdacd8d2f359561fbbdaa3474de9bc24ef8936784dfe6fe0e29a13cac85a3e622b61", + "0xa4c511af6bdf3892939aab651828259e4ef6ebecfdd503ecc14e61001575b313a89e209cb55a77ec19a64d29ada066ef", + "0x923c62156fbf3a44926ffb5dc71f7cef602dbe941a98c61f019a27a18a50c16b6135b6099fe04a2e1dc88a6cad989fb7", + "0xafb9191c541b61afa0ef14652e563cc5a557842ce2afea13e21507dde0ebbe6da5233af949c998c00865c79bb3d45ec8", + "0x8a1f0ad65cb2b225931f41dc53547d756111ecbf5bc57c5ee2cc1ffd61b126d0389d311ffe26cf06eaead95af09c5ca3", + "0x9040b20b5ac2e1a9d30abf7a4eea1ec2db8f3077cb2cfc8736b37222d8d3937f5d9f421167086dc5551e9f0bd2522d07", + "0xb6d888b8c6bd448dccaf99c3f690d47f802e134709ce102fb6f6fc68156943c0762be6f386338163e01eed2d1dd5f734", + "0xb94f0e27bbcda793e4a272603b3dcc739d3bf3207798df7319f8dc9d37cbd850e3724bdd30498c929debad971950223c", + "0x9769827767be9d7bacba1b687289e0794c6fe630d33c9b607da1f6a65e3f34cb8bd65327d9287c8c5f3c8b5f6d3d133e", + "0xaaac72c993aa2356c9a6a030950441de42b2d746bace29865382f0ef54835bc96958b2f00237d805ee6a69ca82117c1b", + "0xa2b1f027d80c1b0e79bfc7dd252e095b436fba23a97a1b2b16cdd39fd39a49e06a1ca9a1345c4dbb3d601ffa99f42bdc", + "0xb3fa0ad1478ca571e8aa230921f95d81aed7eca00275a51b33aadabd5cb9c530030691d1242a6ff24e2d4cfd72a47203", + "0xa43ed4368e78daad51b9bf1a685b1e1bfe05bed7340d4a00df718133f686690c99198b60031513328fc353c6825a5f2f", + "0x965e145711ecf998b01a18843cbb8db6b91ff46f668229281d4ca52236c4d40804ebc54276e9c168d2a2bfc299bcf397", + "0xae18e6efc6f54c1d9230210ac859c2f19180f31d2e37a94da2983a4264dbb58ad328ab3cbc6884ce4637c8c2390f7fc1", + "0x83a9200486d4d85f5671643b6daf3d0290b2e41520fb7ea7030e7e342d7789023da6a293a3984308b27eb55f879ad99d", + "0xb925fb6ca83479355a44abbcdf182bfac8a3c7cce6cfc7962be277ce34460eb837c561257569be3cb28023208dea80dd", + "0x9583dd991b62ae4bd5f379ccd3cec72cfae1c08137ddfbacc659a9641e7d5a82083de60005f74fc807bd2acd218d0789", + "0xae73bc32e9ff5926e1e06c07a3963080881b976c9875777f8e4cf96af91bf41bdbed4bd77e91253b8ec3c15b4a6d3977", + "0xb2a3ea90aa398717ba7d8c46743e4c487b63c5abb140555d8d20e5115df2f70d3c84a2cb9a5e0536b2d93d24f271b38d", + "0x91d119d3bf1d34cd839eb69c6de998b78482ab66bc93fa97e31fb9592f36cdfcd673f52366f8c8e8877e313b92d4a2ad", + "0xa1907e20120902cf68912cc3046f8806cabbd7673e80218814cb088e080dd93b5dccba395b13e0025f5755c183276c3a", + "0xb2e2011df72504065ec4c12cbc2137b95cfcd1355509671feb7b00dbf7f8d500476a49754cb7fb9219cb5cba7c8afe01", + "0xa48589fb7a74a3dfd782cb3503e6294a81dbb6adb412887569f9408e9079371edbd9822388e0b7ec8d3297ba270f53ef", + "0xa203909bfe196ac65ed3e6800d577b6ca5c8fe1d40f7f925a43852951e38883f2ffd250a9e16fab3ed3dc1249650247b", + "0x997ac293722a8b98f7e819f8e6c2d4c5bd1103b82d489d8b8aabeb905e95450b9b75bd61442cf68cc957212ec1c55617", + "0x9895a3de62395c33509b153b7820bd94fd2b011f0cac135fcf916482f1eda272ecc79f83a61837e99c3a3c4ab2c5c2a2", + "0x98c2ece4d49a64ec8e06407a0585081003bcef88af35210e22eab91169f8f0c044d611494b755e5bd915804b1d857747", + "0x8bc6dd083b36d076ddf0e0bb1bb87cfd059283ddabb3886f02eb7e27f1f0539b2819527b56b5c13436523c4603ac1d12", + "0x85ab8b7a696333c82dd5e179e12b2e127e67d911de609ff9a03cab95cbeedb1f364aa1f2b5e59353e4ba0d177f996151", + "0xa9478e214afa68c395aa2c7daf8ba1627feb71ad6d8bc7339734cdcdd5a42838e032736c28e6251c808d5a4875ef0d06", + "0x8c53f62cf06a35321c8af3871ee4459768d0745ebf48942b9f464206309f42fc7b2c50f196ae1e43b664f0e2e718a23a", + "0x8ba80662f6642d8866e832ec8082a4204ebc993fc304c4b794666856de0407620131a18dc053597bb40a3de0bf8aca22", + "0x8c8fac6b911785d1561a985580c03fb2ebc613ae33e486a92638aa7d4493374118d9a6d9d99121e29c68c3d67ee4e3f3", + "0x90f2c793eee07ad90157040b30558bb3b0164e8ddf856389d6742cf5bd1c712e4c6a8e5678da70a8e9e242ec7864117e", + "0x954abed8f6d58896b7f6438c9780236c1c83b02d60a29fa7361559e619e5bc9d67b3646ee39ffafe2b3019bb3357fb50", + "0xb79874f757a33085e1e751544de8fe3afbea92e0234f9c00254c2b36115a16ee46f085f22aa66e0c9177e5106f51b03b", + "0xaa148b287cf4f60c64f774282b421aae075f0eaa93a45aab4927750f47e2ef0b811d1846bbb15eeb2f293c80a7612e83", + "0xa588d8825e7b0168d45499dcff6faf0dfe1ba4f090fdc7c06d50344960c0121f10ad109b0b9d13b06ef22de5a04eef87", + "0x8f61ec93d14ebfa9c31731f9ef0fb8907505fedc79378e9a3f65c27bed4d74b41e129c97672ce5f567d897befbceec8c", + "0xa008218633f1da10efd01c155f7ed739faec902da6dc48e9f19ccbc8d32bb318d71806285cf2003de2c907bbdd4f8b22", + "0x88ad82c66f7085632d7e348d69da84200c53594553acf5432b50dd1e87f410c802dfea91be3cf804e3117ce13103f23e", + "0x8498dba17de0318af227a3f9ed86df37a5c33f9a538be9823f8dce4efc3579e8296cb3b7200cee7c5e0bfd9da23a4b69", + "0xb3c0342231dffe4c9bc7d9265597bc8cc4a82e2980ac6d1407108db5b00349dc91d5116fab51cf2802d58f05f653861d", + "0xb3f2730455f9bf5a058598bc60f47740117ba51f6a767e1134516a4e42338b513f377027acf8825da5c4d047a62984fd", + "0x816360914fbc9d8b865157bfab07aeb7b90bb5a7c5cd64847b1c3184a52266cd3f8f8f3ef99309ba2edc4622304bacc0", + "0x8fd21b2315b44a52d60b39ebc45970a47b9495f42b88217ae057bebcd3ea0e2476c0c3d13de7f72016ae12ae966a008d", + "0xb62014485bc217a0fe892ef1aef0e59604ad5a868face7a93f77a70ba3d7413443fbe7a44552a784d8eae1acb1d1c52b", + "0xa905822507e431b35f56724f6c8d2e93b0607ed7a4533073a99cce2b7c1c35367382447073a53036dfdb0d04978ccf2a", + "0x81672e39c2b31845142963351de3d9cd04c67c806fdfe77467867463dbbd8a9b0e2400ccc55016e57cbedb02d83a0544", + "0x90919c970ec668de8ec48a2a73bb75cb94f0f8380c79a7909fd8084df61ecd631476ddd474b27103c6817c8f3f260db9", + "0x8fbe37dfb04bf1d3029f8070fd988fc5e4b585e61eab6a8b66caf0ffef979d3ed6a662cd99468ce98ec802e985da5fad", + "0x950939aabb90b57a3d667f9820880eb0c4fee5c27fe211ce8ecd34663c21b5543c810b3676111d079ac98644c75ee0ae", + "0xb06201ec3c3cfdaf864a66af128effee8ec42d25f1e173c1edf9207979fa52c871757000c591d71a9b6cde40f5001a06", + "0xa79054e8febd0450c96ac7a5fd6bf419c4b17a5926f3bc23a8616f0cfbc2849d97470174cd1baa7c739b12615334b6b7", + "0x81c7391b2a1844ed26a84f054b5f03865b442b7a8d614cd44805b5705fe6a356ac182b66a3c8d415132e389efac5f6b2", + "0x825af1563d0fe53925ec9ac0df65d8211b333474e59359bf1bde8861eecd03f2ac74534d34b7e61031227c2fa7a74e1e", + "0xb60dd9bf036f1825295cd2014ef1f6d520cf729b4d6cee0b42cb871b60ae539b27c83aa3f96ee3d490ec27ce7e915115", + "0x89ca43d5b7f3622b42df7887572297a7f52d5204d85e2e1ac6e5d7aa7f8aaea5e3a07280477d910db025d17cd2e7373b", + "0xb93a2bc9b1b597f0e514fde76ce5bfb6e61eee39cbf1971ea6db38c3ecb055e7913ec8cd07fb0b0ffae3ca345883101c", + "0x8d45546bc30266b20c6c59fc4339eb633155aa58f115a8f976d13789eaae20a95b064fedead247c46665cc13ba856663", + "0xaa8eacfe00e8a4d9815de3f7619d9c420629ada6489933ca66a571bf6c044d08b391e0d9eec7d1cbebe8def1e7523f1e", + "0xb32fefc59a0d0319ccb1946b351ed70445d78d9fbb536fa710d3162b9659f10288f12d82b32ecc026d55f16cbad55441", + "0x99c7c45c34044c056b24e8f57123ba5e2c2c039e9f038a66899362840cffe021733e078866a8708504cdc35816cb335d", + "0x80def162c134540d5ec071b25ccc3eef4efe158be453af41a310b7916c49ec0ce06bb43dfee96b6d77339e11587de448", + "0xb5f2fa4f68f6a26bcb70d8eab62ad73509c08ee7aa622a14b3d16973ffff508ce6f1aff9ced77b8dcfef7319245cf2de", + "0xb4d0436019e779c789464716e1741c189e8945dab7f3072720bd9aa89882fa5b085a1755c48da21541f3cd70a41b0a71", + "0x931e798ef672e1472f4f84c727a101e70d77b3a9f0c0803a5220958d6bbeb8aeeb56c769ab472a3d6451249a13a3f56e", + "0x918c10a84de268aa8f1ba24b38fe55ff907be07b1e86b4a4adbf305c0d705c1cf5f65ce99e03e11676cedc89f1a4f331", + "0x8e55a8413b823715ccd92daee357cedd797e69a0e78b6fcdacb7318646b9903dfe05e5501f47b3c52e74055b9eb619a4", + "0x8b329bb63e6c985d7d072dff4680b3f8b1217ed20543277386bd30ec25240d9dc378837dcd5cf4fd9548658635f4c537", + "0x8c2be5386052b22986b33dbc63c5afacb6d0095495564ba4aa28fc8c880a3c78242fb083248d788ed928deb1e30a82c2", + "0x83a2b7bdfcbd25d6b059f27218e009ecb5ecc4da68ead885e00216411d8222062ca42f21c4d9cfa19c31522080af677b", + "0x9620334d2633e85646b2e2fc48dc6c3f09c64ef1706ed78a3bb6ce1f6b274a727364df71e97531dfdcb392f70f27f536", + "0xb6c84970ec04545121ec3b79376f4e45053c97e8bf2b11922cc2490a429c38735466097ecb81cc9d9692c74d2fb8abc8", + "0x8e55d707dcf265c5ae29a32c27ce66f200fddb724faa5bbf145ef42280ef645fa2f0cc3cfe2db8599b26c83b91e077df", + "0xb910b96b763966402bbebd68a32c15a225ec21e1357fa298478c5981a4310e556103fef0c73bd8903e11c4ed2c065647", + "0xa8fd933a0e9fe8c459809bd93b8ce153e2af55df94b61a1490736b19c89469954da8b72dbd072d798fc06fc3d7a3d60a", + "0x811b279c113828e114fd82c2070caa7eb089a46c8cabf865f9c77354a77ebebe0c4c6400dda0e66dd017cfc44d76851d", + "0x8ed03e91c331afb3ad6e42767e1b3e8d3a35fb831805ff1b5fd3e91878e04027ff5af1165a3ac295f1578faf2c83b581", + "0x95bf53683d64a0621bf1ca6ee17446783f6c535b7a54d6ea57723487a215759a54f886597a55dfdd560424e368ab2759", + "0xa9bea378768fb1d7ba365a16531c51fc1975f1c73caf2a0891da28509805fa84e2a8db7c6ccfbc620e9002317abf174c", + "0xb8308250891015deaf851c4e5a4cf4704d104f94064418488d7e3076d49f36240dcf6fdcf83f45fe8a1d97fb02e3db59", + "0xadcda6b63da21f4074f142f8e7f3a2274f624c733e3a4001054a1809711529c61356aa087f73aed877a58ccb41d38d12", + "0xb80e7869239ae26d1da2e6683f064d1dc93cf4a2b66e9439b3ad9b25324e969bf98014760d29e6b8de7ff152ef498d0f", + "0x8e9bf968911df3bb5e3a7655e9d8143e91ee87f14464d7ba9c86e1e31b03ab31b91eda121281b79cd974d9ed2657e33e", + "0x9007277e8335a43e6bc3c2f5f98c0ba7024a679b7156aeefe964f1a962e5ac82154ac39d1ffbad85a8f2440f3c1e354b", + "0x9422b9d670e997b7c919a429499f38e863c69c6a4d2bb28d85e36ae0895c620f68b71e39eba785e3d39a45be91507757", + "0x926094e01132938000d82dd9a571fef5ef104cd25b4015a25e3442af0329e585aaad5472f0e7a69899ba2d6f734b40aa", + "0x95552d8057f7e32c24d69e4d6c51c98403f198a20c5be8826254d19cab2f84d5758e2220cea7e38b7c8a7a23178fd564", + "0x8abcf8dcc8488bcc9ab23c51b9e7a0d91dfc7bebe88b7ed370ee68eceba643e939c5eae66a4aa5fe85120751780e351c", + "0xa91bf8198f029e6a4cf6f0cc39b629e9aeff1c77b8739e1d5c73d8c1d3fb5c8f6f23e27b435bf10b5b4ec1cf6a7249ed", + "0xb932d87ee3a4b81341511f90fe5aa36c571e8b914f25abcc33dd40ca67a3f6444fe9362c1434744e4af18d6e045c54a3", + "0xa8e960c2be9b1d805d387b3ebe2134d421a65f1fd4c1b4cccdce78f9926f139eea78e3afb449b3d6dd19b5d16ace48fe", + "0xa7e2f57cce509fe66707eaba9b4c042c1be93fd6034a9b51d1d30c45c4363eac79d54663d525c9873ab0eec0b1cc4ed3", + "0xaa162a31c2078f4b080199debf24494a8dfdfb9d8fc85b198a861b12a629c73128c55a883e4c2de3dfed6e0e1b83eeab", + "0xb5a4d075433eaf4115717a84b4dc37f843d44bba0bf820c92ecdedd5afb61be60f7708c8a151a678d9d5c0ae531bffb7", + "0xb56ab96f7a463c0079e05dc766f3a6a31cae5c5044947734ebe0a26e01367c6763cc8de6c2ee2f3b8218f05bef217474", + "0xb60792ac506b901065a8bc0180a86e028fe34b62ceae1ad640c759538ebf3a2ad9c8c927d662deed6f489ff3ff7813c4", + "0x8c8c2cdf075504d12d441a58542e1f8e4bdf92b3ee4775e836b2734c5ec1e3df919b931386417d04489a1dca806c87d2", + "0x8ed78e91e5c4a68894cefc2f7fa71f02e5e12d40f1bb74332139bc7be4d92c24e07d5ece0e82150ed474aa1337af4c18", + "0x87119c22ff8aa31150bde537d863cad661cc5159b12f084cc319224c533f0deb28526ed8568d00a1441e7d8bb4f05673", + "0x83a60ba5a9cccf22cebadf7318b706c9f29abd25db0e2fc1c802965351b53cbf316df72ee3e9b2d3ae7f3c4494cfdff1", + "0xb73b6a9fdd3e7463fbdaabc9a885b7c82201ad867d1bced1c2484300a01cbbb3f1e21afa95d4c7cbb6cb983416b63b90", + "0xb1d89ad16981ff9217708090d4017662d8838f21f3a3296cffe14590b533905fa06a20e40dd497bd291fa4dfd1bfc511", + "0x8abde560083e071a402e3c7bf31930f537f67d2a7bbc734a7480b1b760aa712ebd1cbcb65b00e11e384e980222fe14a9", + "0x89c731d8f31afea8bdc9c32527bdca257f2a840764d40f6e49403b8e75ae51017d505ea4fff91bf28b6f3a1bc65b8bbc", + "0x80e9ac8e077e86ad050ee73dfce268a69564ff1b8419e9c236d981fe7a5f0c2bc756e8603ec604b3b9e36da8fe10a49c", + "0xb4f1eea0f304898b1323c6382732e6f40e556bfc68af9ce73f6d54e92f5f23cc4f78eb3f43d578d81e7627fb40f092b3", + "0xa0e3a8d1348f8f153e08ac4839232d75d1d6e81b5de184ec4724f8213baf98d3fe739a96f6b39d79a053b628c3a09981", + "0xa6915ba0b52ffe4a381bbb8ff3791d9d3b848bf89b3bacbb2a7d2e5ae21f1353cdc304b3cb6e82416f7e604035c27d7e", + "0xb2c4c9cdfdd2fc9a340ba3ade9423344b9f429e8c7e20a8abbf26400376e312f3ae35d1c456be99dfb5c02fc8a36cbfa", + "0x9657d57ca0641825a0aa5687f3f87659d893f33aee819bafa5b1ca1db554811c1c844f971e278606e3a2f096defdc67c", + "0xa4ad24d0a557704ada24d8e27a15604bca28679e260b2c69ccc8e6cae5499866724b700605a90df7dfb35130756939b9", + "0xb18d9ea6682f73a1f99a9a4fc98c38fcda02c1a18e8c5fc080cf935a2ac877dc5223fca273dcde190b906178d0fd05bc", + "0x8ea5fefad0799c885f50ff10d94bd0af5b99b0a446cd1f367ae5ff529cc47e09f3018115f3c0ccac2fa05bb65b84945e", + "0x92450d52e6c7d13ebfcdf5674d6761bbae2fc5aabc865d35d031b588c383e0a64cf69a73dc93948632e2b98f74a5ed86", + "0xa356f171a98df4ec5a96d556eaccc6ad34b4238aafcf0e94ece27cdbb491749fc9692e78b84dfe80bdef2914079d34b5", + "0xb918703a4d3507d266414712ba8eb7ad17da07cc5f952b5c62ef130cc6ed1ae3bf01237fc8848c179725bdddd465b301", + "0xad2b0554570bfc9d97510cf59bc38e10ca54a93649c30ac9919bd0255e43bf525ab11b74f78a51ac0973cd0c5a5dcb54", + "0xa7ecaf4b631d179d32ac1632390d95196a0035e00da6c0e6e13b5c09ae44b15ae6c21538b5a31b73bc5f650ecd979b59", + "0xa37704eb4d728df2a367e59fcb6c26023136230e37f3b8a2f3ceeb1467f5cd30186fc0116f98b64a8146fd2c5903e8d9", + "0xb09373ce92314678299ae10ec1f93c702911beb4115c6b5ba6efbcab9c7afb599f59793912df70a98868bce6545a33dd", + "0xb52a878a1393094fd2b93f2d1eccabf2830ab10800ba4cc24dcc7849cd0978733263aef2fcb766a7cb575a7a99383db8", + "0x8dac097e006fda4fb9d6d7ae52adabd9448ebc8d5bd5b38ac0c4ed38ceb510763174f7adfb0b473c38e52147ccab4239", + "0x86b19c41efb949937d74a7875549ee5e997f9fdac7f7198085afda233cf74341a38d0ca3767c76cd35f875b89a35f78c", + "0x99f0d927e5ad25cd134f1c70b72631cc6b5cb4ddb86c0642b900464e33d971213a5239dddaf71f7a42f2d6d02a12dcc6", + "0x8355c38806c335d747d4e97f0083fb96585677da18b409a85175ec35dc3f74671817b34203eb18c2f729717ce083ede8", + "0xabb3603adb061a036eae0afa5f23d79c3b62442e0e3bcdeef896f88995585c1105cd3065410368456a4d36b5b0485a83", + "0x9051c5c0011784885187d04749f774b9b4f6bc594b0e4e18226de79dedc4d7aefa3529c3d2c728e180f96f3e204d578b", + "0x91888213e7d321d0bfac884edbd5cb756b280753bb5f8bc6acfc208f525757beca24bdf86fc68d3d8736ef176a960b49", + "0x91258bd7ce6e3b7516fe2f5391a368d826da299e0e99b1f82eaa44b62b110ab696adc92debab8ba098a52f38dfb3c5d8", + "0x96e3907340dffa9da3602d3b94bacff7e1bb8649edd3b9bbd06e1bc6781e78f91ababab12c0b9be7c66dfedc7001b66e", + "0x9513555688fcfb12ba63952ab36a67b36affdd71f7b843e8eb99ccbd45421698024608233efbdc905eaeb26b334b33af", + "0x9913ca9bcf11eeb408da02e4317c5ca0010fb2f4490b282ddb758001c08b438c3b35351a8cbe10b7fffc1293ccd22d4b", + "0x85dc2471860ebca88e5a2766161fdd77f926d2a34825d1134a30418f91a741759668e32fd1e37c415d07ab5824338e8a", + "0x8b128917e828a0b5eb6fa8ed72b52fae2dfaf74febee69a2e2f87e8df702f0c5bc0fb620c8d1d2a07f35a15ec9c0f5a8", + "0x964c39e7840c130b01bb481ae7bfc92682b0f124c9c383f9dbf3027f2249151925f4faf36905af476a54778d69da3f48", + "0x80671ece658cf850e522d46d25678f934ce6df043f25f8707235125765d40c2eaaf39eda6092f75039b22cb58bf2c29d", + "0xad4bb0e79fdaa340b1347a46b0f64e801c72a89770dda0a6e4bfd35f2df5146fce9934e4baecb1c2671077c771eb8089", + "0x80b3bd3adc6cf198fcd997f8867d2839a2eb28f57390352ec423b8a14cc1f2ab21c6e286505d6a21fb134dcd8d8f11cf", + "0xa26d46a6b8a75748895a1d599e7fd120d896340e79813167a400b2fe463452532a4cab419074663fe1d29fa716b76a33", + "0x82b1f3a8a1df29207d7ff020809113ab06080a7f0c631f76ad33f47cdfb6a567143144df97b4ed7f676d929195b04bba", + "0xad96633a3744648ff0a2e4491e8219c9c6ba6e655cb058c36320a8f72cd5f72c00bddf97083d07650ea9ddc005fc1ff4", + "0x91d0783788626c91662359dc3ff36a8bcc6831e3f4114f85c99910256b1d8f88a8612f53c7c417d55581dea486f38926", + "0x84edd9e87ff3d193ebb25f43474c33fe502a1e2100fd3f93fda6520f5e42214cc12e9f8045f99aa2423a0ee35e671854", + "0xb55e06a4b1fc3ff9a5520e0b7c8b5ac11b28385cce78d91ce93b82f1bd7f7afdd4195d0c13a76e80d0ed5a4f12325fa7", + "0xb0b15c7ddede2b81d9c835ecaa887650622e75d0d85f81b8bbec7ef24e9a31a9c9e3de1f382d8c76d878d1b01373f6c8", + "0xb1adb47c20f29784116b80f3670182d01b17612d5d91bd6502b0dcecdcf072541f582aafc5e7dd9a765cad52151684f4", + "0x8efd1018df9c9e9814a9c48f68c168551b999914a6719229f0c5bf0f20a288a2f5ba4a48ba966c5bffb0fbd346a4fcc6", + "0xb34ea2bd3269a4ddb2fbf2514401d2712fc46c22642f3557e3b9c7acbce9b454dcf789573ede9aa14f39605fdd03f8c4", + "0xa9e1428ce24eacfc460aec2e787c053327ba612f50d93510d58b2cb0f13291ca3d16358325ab3e86693fe686e4f526f7", + "0x91eac7361af4c66f725c153da665a3c55aca9ae73ead84ca2662cf736fe6a348a301be1954723206dda4a2120202954b", + "0xa6f02db89739c686407825fa7e84000ceedb9bd943e8a0908fef6f0d35dbc33c336072ba65e33e15ecfcd5714d01c2f0", + "0xa25666faa12e843a80365c0fef7d328a480c6e3cb7f224763c11d8cbabd0e7e91a5b647585ee905cc036afca14842bae", + "0xb4348576439cd2e48c01cb9cded7cc4a0ea364ab936dd679ddc7d58b48807e7fab070f2f1ea88595b11af4500849026a", + "0xa8c6c731e0d0464ef7e4fc1b049065eb4ce100c01e1a376365c636a0b23851022bf55805963bc15eb57434a837e81167", + "0xb0952937b154e3a4c206f96cd96c76ba37624956b0e4d43470bdd97b4af878326b589e3eaee82fc192437123096799a2", + "0x97d07ec31ecc9923192e48d37df2cf08750050fb452dcfbdb350fbc43e146bae3590c5b732b31ebfa1ce5d884ad5ad57", + "0xa69359aebbfe4cbc4d39d178150039fbf284cbc0edc68a6bd635ee3a1c76569a4a575c907fff691b2a4d82a384c2945f", + "0xb321c2c0f6b5902ee9056cce7404d858da9a573d27348c1a6bfea29b2746f2aee7abcb6192504e5a583b0caeaba117d7", + "0xa74e738aa6eb4eea58855ae6f422af22812fb388c83aacca5bd5fa4a88d4c01463174a229aea2830c348dd9ab9307854", + "0x94306a3b106bc1644346bc45c05cdc8287811d5c86cad691bde0c65d6a686eb9c0ce79ad91baa4547e5d058ae8bf7310", + "0xb64140fd77a07633e4ca8d60786452311dcdb8ce7095ba51dad8486f57c3bf4e69bced92603f71da992a48ad817ab275", + "0xaffe7f4310f1dc68e5e3cd640bedf864f51bfb46bb752063bfc18e95930021f784e509261ff9c560f53000c361b142d1", + "0xb0d2fee222c6f963ba3385547f921a48964da031d737892604f8f2677d4905dbf615046db57eae6c6dd756709ae6932a", + "0x81700c66aad7c2e51168e028b0fe086dea75d3b17d93a4dc1f47a6a0f025df0bae1c8c997901837ad859a84197e7bb00", + "0xaa4ac5fdd602f8b79cace18690e67bad557a93d00c0e295074185e8c6b4059a65495d9971685de2fc01d2171ac8b706a", + "0xa8becb3a64fdf35d65d2857898dcf8053b5057a73ab8c5bb5324af1a8015cff47efb85dc3eae7364cd5c850b7962bedf", + "0xb72ea09bd0b72f8cde3466f359ea69b194ede93dced534efba1b9ebc6f3bd53942fe2965e992e82edb6050cac4ed88dd", + "0x85bb8dd7eef023a251fb6f220af54687747f4c91983ff728163c4618ffac40ee6edc29a0aa6d455276bbe017f63757c2", + "0x85a485254a11b4c4a943d9ec509c0dd1cbfc0ff5273a00cf5c9f0babec973efb15348e5d9451b548293d778e3a2b62a5", + "0xb109f3ac809391e772b589c196b013db69a9b2b10ac3898feb70b986973731f30722b573cd0c9324158ec20416825385", + "0x8a4eb579a840d438bed008644f373ea9ba2f28470d50cf1d70af38ba0e17326c948527b1719dd1bd9ac656ebd5aedd10", + "0xa52e9d66ead5ee1e02ce6108e4ded790d8ec83164a0fa275ab1f89a32200726c8e988d66df131df9e62dd80203c13dce", + "0xb541cee9febf15d252475507e11d65c4b7819c26cf6d90352f5e8a8f5c63e254eddf22df0c35a7be5b244233e8e4ee5e", + "0x8153c297772adf4603c39349142f98cc15baeccaeae10c3230ee87d62255f6814d88d6ed208c368d2c02332426589748", + "0x970dc9782f1828474e9fab7dcdec19aa106725465a5844caed948eef5c9e48199c1b6bc1a637ed7864116927e84bc65a", + "0xa975a920624967f4ecc77ea5d9869c434caa64c330024194615a8d0640c5d4d4fb139ea11a0c73a5c6ae6dd3fbf0ab5d", + "0x811f0f9e0c12acfb4b9dca359eaef3bed18083bad96188befc036ad3143b121fff4777ca6dc70a835bbc4921bd25f5ff", + "0x82341c6ebdb97c8b72910da95c7eebccd1308b6a92999886aab552f0642882d5c7cc60931577d200efd6066530c998dd", + "0x860f7162c2f5fd1c0953c6ce75bd8c52eaa48032b914410681b8cc05e00b64130d1f96ec5a52df66a04c78a9f9f42981", + "0x8a578e674875571fe1a0459843495a5ee1d9fb6cd684b244feb9488f999a46f43363938cd0542879ea18ed14fba10a6e", + "0x8df217aba4da6781f0f5139aced472025523ed6e17e504511c04b677ca8197488e237d8bb5dff7b6b3898cd5a6393dd5", + "0xb2c9230ad35d7b471d3aee6f771517cf3145ad26200bd6fe9c7cf28120e2945fed402e212d2330a692f97bb9ac4dcf12", + "0xb78b89e29e8b782603b222cc8724eeb83b2d9d56bc02f59a3c899ab76429dc721358b07dcdaf422f59520b7e7ab4fb55", + "0x82682a5617843c4ac8d4efb4c3ce715c76c1da2c3bab1ede387db503f3489c1bfdfc07d9231d96f955df84fd225bc81b", + "0xb0f53725cc610e78b8e8a4e6823a2ffe44dd15a9a5bc8151ab7a3787ddd97e1d7f2f0e6efd2876e5f96417157143e3bf", + "0x92c5a93233085e2b244519078770c7192af62f3562113abc8902f9d72591eacf52bd15ce78653ab9170d5067606287f8", + "0xa43ef97dcd9b6ad288846bf31fccf78df72f94bc7ad768baf5bf0d5dfa27bd74ffcc6b6c6ed1d1f09e09be3afa5eaedf", + "0x817d43bd684a261fb30f709f7926cc4e1a31fd3a1a5e7e53ba4d664856827b340d7867e23d55617ab3514c8a26a7040d", + "0xa599e22d3286b32fafaaf79bd5b0c5b72f6bf266ec68948478f055391336d756b58f9afea0167b961fd94234989f0f02", + "0xb70db7d8e8356df2e2070f8d658e560081442f3f3b95e20f4bf30106835d76161101163659d5d12cc0f335fb042dc66e", + "0xb8f725b70c957aa3cd6b4bef0d9647393f7c9e0b7343e92439372f0e9aa3ceddd0cb9c30be331742b87c53f2eb030593", + "0xb2fb5e7762f26036e7e966f4454f886758804d1f4c2da17f3d13b0b67ca337f1fd89fd3cc798b07da6e05e8582c9537b", + "0xa377f944dccc300921e238ed67989872338137fe57f04cb5a913c787842e08b8a1adcfb4d2200abdc911fc1c766a7092", + "0xb82e98a606071c2a33f2ad44e7ace6d9471d5434500de8307b5d4e0083e3a5cbc67f0609ca8055f0ea0ee7501b9ed916", + "0x8e58f9a04d33a41ace4944615041662dc35057e645f63e127cf0d70f96ac307d33a62ce98f164d6eed8536c1a747dcbe", + "0xb5b11388071ffbf57ac47fc195736613b964ebb91cc8e2c17b32646f91d64ea506282b881897fca96c317364d3290de2", + "0xa40ee9b7551133856cfb3904837f9949a9558e59a418898affb78adf1500fd6ef6328fc4422161909aea2c79ad08c14b", + "0x81f9eb4ef28aacdb43e11dfc9aa92ba990be4d3c14b484fa677edad3a3fbfeaa859a7f9322b5e95818240d7326215abf", + "0x84939b2b6bc859437d1a7a8d6ec9a357c6b716c4b4cc22abc274af872655940cfc72c99f5d0283d90e05191fcdb1c232", + "0xb78a5b74a90a805410b6225fb9576d6d73752520f25cc3fd1edf8ea9f6559d3080f9acaa2246809b6a66879cd2ae446b", + "0x8d0a92baa88bf38dce5385ccf15d345b28e2e5d0a2d469e689353d80eaed8e8408933816d70ad752f226c59a0d5b5f0c", + "0xa7e15f8a8c1655b7b346c9488cff278c793505379b781b31b273b4bf09b3bdfca1c8ab2334746075d636b2e05859f215", + "0xb70daf14f2adce03c7b92d6aa181f0c507a80a37493d8dd12419d5ed5f943a98099fefb46ac827d6e4efb9b8233c99d6", + "0x8c2480814661744d116fba7355bc6b1914975e44cf0e976d50b6a20092bb1c636b7b44ed3fe8d63b5555ffc89fa759d6", + "0xa6059528a4fed36abb74ab992b22a4f9bf1d05c5de2bfe6837b9af1adfed98bc37ed7481b5a99675d432743021fcfdb3", + "0xb7e19f1b25bc159e5a769811e773c3a8ffe8be8ac77ed0b711540915e5c6e7bafdb407cf9b85c551f67fd621ce8142a5", + "0xa2f66d4f7d16ed3e7ef5fc90b42676c61a98ff18bd26ccce91de03b6a0130c1db17a6bc57be135e410a76d2255b15813", + "0xa139c916927dc3d3fb83598da9217ca64f0ae127215332e9a7ed82be923b89a801c44580d5617297175f9dafb1c4eaf3", + "0xaf08e1e1b04ec95366a12d99c80a9a9ac40ac984a575dd0230cdf4eb346a7686da55ef0a276f3356f814af31f9cbf1aa", + "0x98840aefe287369221c0721cd7c1b15b1d670c3cbbfda191cdb5434bcad757e59c30ec82b2d8c75947405888d44da435", + "0xb7c61c8d42daf2e278a12d8f6eed76090b71c82275f8b33504aba75d95103840e8acd083e97a5a5aa79897876a68940d", + "0xa0264048d2a2061d32eee4f661957ff351e78436bf49ef973c059612874ce9c91970869d011dc13a5b7c754476880a68", + "0x897199a4d8db8aa2db5d9be3d4f4312e41fa0739eb06c62e2e046c4b9be829a447e5d47227e2d96195d3b7b66eb59da6", + "0xb512a9082881f5dc90b02f8bc4f38b133348c2e933813852f6a8e7d8c270c9ce68a5524af7d1d3123e53b2d02a53d465", + "0x80b332469254a96f53c95ec79bb5a8bb1c387d40e58b73d72f84384c696ba0d3c81d6ac90be2979c364c44294e90432e", + "0xab680c2e547ea5cbf95bf813020beb461d50ee4341dea944eb48f6a8584d35682d20186e3b190b849a1ba25625a7f499", + "0x9070581993a0531d6be372d370c2e4ab2ee53f30e04a75ae61ea0fc2c320914506c4d2d4b4487c1f8fa88356fc45c895", + "0x8424303dad6b4051ab633ad27ee51783b2ead61c5a6dae1eb3ed72fc1f36e2a9b1f315504a4bd90f9664091f2f403d4c", + "0x82225611eee626556553b9316dab4043aff241a81826a33aebd9864a91e299b765ba1fb43eea2c2047e6b75b6d7fe3de", + "0x8a3fb221c616ad55c352dd5e0c09ee892022013d6965aef40d4f277a42e9fa01226fe973cb99aaf6ffe4f4f348fb54d1", + "0xb07c07679aa51713e8a7d7bc304dc15ed5664b66bd371877023f3b110b3927e09e259ef22895c4001421a69c6c013cc6", + "0x83556c76bdac0dd8db6da231b863c335be076e7299802eebc259e0818c369f933a4a4b18e2df8ca07e82f60767b462e0", + "0xa516f659b7915d2f7cd0f0f5ea2491b15f0c84dcb191e7671b28adf7cf14a56d42cfc0da94b3c269b45c535f6eeded49", + "0x80d7cc6f26066f753041b17ff1bd27f6d4b5603a43729d33d596e21a67356db84ca9710158089def425f6afaf3207f9e", + "0xb802a47f9009dbd48851209ea1e2739020e717f0ae80671d9f97a0e43de923273f66b7fcc136a064c8467372a5b02d28", + "0xac92fec1864a8a911633f377df87aab56713876316d48240fefeee49ab97f7406c22e70f4938b5912c5c4e766146b7a5", + "0x89224225b9835d04428b0a74edbff53dee2be285ddd1e5a3a8c37307c0500578155f0c4052e4bc8be04c56862fac099d", + "0xb1d3c8492fbf22ea60732745edd3b0163ba5a20d1a3315e3773f2540ee38cf308d42ec72cbb3e3dcea457d1d132c3904", + "0x8bd00e38ec30ee6c44a0e5b222f1f737c9ed2a4bb9225f1741d6334df966318c8a0fd2fbb109557fe8c9479694b8d8dc", + "0xa930ce5454efc0b247dc148aff869963fc5c240241d5590415cbd36634801a04d3873d93635911bb9c0c42ecb005cc63", + "0xb83d4f80e9e0fa47b42175df74935ba8aad2e559b80e84478ab1685bc3eb65d51b93e5738d5ca968cc055ca0c552a03c", + "0xb3ae21258f98051f13af3878b8103bc541fe6f20b1c3f8fb4689ddb8800b3c25cca9b55f0a4104bdf15dc4d5844abb8c", + "0x831ef8684c1cd446c58c59d0152aeade5cc305bca6aa296b92162615f052ba280fe289edd62fda6d9f0667c186445f52", + "0x97bf9659b14f133885916733b7d4ac7e215495953caba970fa259f7bf6b79e661090ec8d79e1c9ce8dfb17e8552f93af", + "0x84d5a89cc2332baaaf3d19627a65f4b107f8dd9228a1434b327732f59883bb54fb8ce60d6acd026ed4b0e94e545d1c33", + "0x8e66cb743f95ca5486400b0d89d02e20b98044be1e3a12983ff9fe086179e5a0ebf4dcd5098703191552e9aa660a6de5", + "0x87b4cfb35bacec805f8148786788db84eb8f4bcecdd0570ecb592c705450ce1a90b6d183d37ef58780ede3995be67497", + "0xa72a4fece5478011973afa543f6d8a8ea06a64b241cf7d8bd81fa3740ac2a4cf10e5120abcc1c1101f94da89507a40ca", + "0x89dc6001a96adcd2679916f43dd19ea00508c8d5dd6b0090eab7982fd2f3571b62f3029588a0649e73f49124525407ea", + "0x8ca75edf1259599e873530eff6151c822a4018e71a340534219ef8641cb6683215891df41d4e3c0ca2560e57a7aa913e", + "0x9282d32f868e5ee6f7fc229dda5b94b603476de30cec0a44a30edf396b52dc0ebd472b8f726d4b67d76179fecc1666a1", + "0xafa24704223707db89690bcf9761f07a093f6009ca9fc945e0a8801fc29f9f51292bf95243e466fe736088af36c55ca6", + "0xb51332508ddd9a2610edd2b0ad120272ca342e96c28baae37a2c4f07e689303a46c237712d07e446b1d67c75aa8ce32f", + "0x9219249f3799dfa4eb4770ee323f821e559e7406bb11b1f1889286221b22c8b40ccacbd9ac50ea3fa9ed754860bc24f0", + "0x993515270c128ede64fe6f06755259105d0ec74947b7eb05924a375fa5c6d14822f3d7d41dd04fa5df8aa2aa205a1dec", + "0xa83be4c2511bae430034ab15b194ac719d7b7041f9c0e321317f513a97db39e97b9ee1df92a1962f265b7a3e98cdd753", + "0x8ac7feaecd26f7b99fda3ed0b8a08bd6dd33ed5ba687c913ec0ffc64bbbefcda6f265072add4d944f2005634601ce68b", + "0xb4e3ac6b09299db9e1a469f3a0b2d8d724ee47a417a517bebc4c2ac3efc5cde086b57b9aa4efccdef2bcf8f456d973f6", + "0x9262a24a84fb7b2a84d700f98dcf3fefab8b47293778c20bfc356860cb84e0bf102bae9facd9986d92d1762e0a955836", + "0x97be2041c42bd25e5eb519279163b0857f8bef627492c27b1182f8bf0033769246be5886422cbd2409c08a2615352465", + "0xb0b87d059a00e3effa2e5e4925da913b245785f2932ac3ed364ad19a064d3561b8aa6afea22c951316074f0df179af36", + "0x891644b7b3321b06a2a40cd96c2b8b29d81cde5b48546483fdda439000982a9cbf1f6333fb6c089d39da6492cdfaefe9", + "0x8da9149b7f4783a24240b7b9c7e6df4abf8d699d3834e31ee591489bf4744141ab199c173db64397c1f9bd5f9c862ca1", + "0x8ad7f9fb2742654aa2964fd468e7645436cefd1308b064fd63fdf0d3adb4caf6cfe5426354f6cc284f208b03d6b2d918", + "0x8435e4668f7aeb027100d21e4e0b6ee22b401d21966a3736b95610de86c7e2f2c9ee5d0f901353675eee5ff458dad69e", + "0x9010895f045538bd11b47bb8996f27198c8d6cffd3220569e6b7407f68f35c47d1efdbcecbf9b5e241c3c2879a4f6936", + "0x92a9aa443b5ee7bf13b6f43f2d8d8db7f6f33fd4073a606ec5772421a55f464831419726130dd97829a7d4bfeb1ab078", + "0x843f3266560be6dcbe0258c3c7d7e332330e10630c069892954290288eda301e247f479505a8a1bf7e59c99ccafd104f", + "0x915bd1dad808f8a568725bd243f80b5476a2999d0ef60ea3ef6e754155bc4121b2b879d01570725b510c5a3f09cd83ef", + "0x97250d781815b1825be192714884630e9f564b9bd737d55b8ac79ab48d0fb3ca53bd21ead7b2fa82a05f24083f25645d", + "0x81e2d52333391ff2faab39611689a62d6ead77039e8703f4e012d53eea17a4d46f2e3342e44b6edbe73a542b461bda45", + "0x89c9f9fd5f638156b018831c1bb70c91215f4a2f5a73c84b1208bdf6ad652a55df7213336ce12bd910a0e1a726474f95", + "0x92bd02984d090ea7e2f3eb7d36d1e7b9d731b6b047e3cdd4af7cc4ee177415fea7a145205e484b366d84191f06af85c9", + "0x85a86fc61d5d916ccbb219db52953e1495230aaaca63237e9165276405f07ad9644e253ae394f1ccdd231944e7143313", + "0xa2ca5b3fbc9f3530f88c0ed7071ec3d89b272174c366eedb5d15d2b648c65d23c0faa4e92c776357e7c6883a0084d03c", + "0xad171f5badcc99c8ffc9d8b707d792046f86cd0aa478e0e2fbb32fe095f96cd134ca548d1f7713057694dc6b26465315", + "0x96bd15d57da9980870fbadc98c68db76824407dff2700c45b859bb70d98374d4a4ba99e3ed0b0c17f480fe08f16c6b8a", + "0x8300bac69ca088c3ff35749b437215e9e35a16393e9dc094f520516ba57a485def7029d30adfc72bca36eeb285c19301", + "0x8a09e20be64f346668fcc7b07fee9c0ea8094c935cbf4f3a4cdbb613d4b936c1edb9256b7c884efb72393d97c0da00e1", + "0xb1f85827ee6f041f93ab174d847a55710824fa131c9ade9561168c3962a25c617475ebc4105eba6e738961a754442bc8", + "0xa131558f92e215969f41b6a57d1e2f424149eea531723821dd4cf8c54325cbe66b002de2c8287de6b41ab4b5c35f060a", + "0x81ba492b8956f73557f361a856c6c884ebb300d828287d5699e22e0cfa75c8e77a61616551d0be5178263898c461d6f7", + "0xb2608f44d3c22fac8e13cb59e4ade8b9a98c4eb1ec0959ea400c97eb937ae3f66837e91917057148befade8389af2f6a", + "0xa6ff0323b5a18a4becb2cc6b376086b47cb2baffbfd1b0f2229ef2286fb4a34c5cd83a5faed5def7bbad519fcab8a856", + "0x857d879cb9eff22501d883071382832730704bfcc5cd5b07cdce7ab8dc41c565a1eb0e7e4befce8e0e03a4975d3f11ef", + "0xa2879a20c0360c516811c490289be7dfbf7dbd41d2f172c9239f99e3d091957e0446854f9d0f753d90384a80feb6fa56", + "0x83518624f33f19f87096a47d7b8e5f2d019b927e935a9021823fac6564c4f2328dcb172e25bb052748191e75ac682bd0", + "0x817ec79132faa4e2950665712b2c503d7fb542aa57b7b36e324f77cda79f8b77bde12314e2df65c5b5296a6bca9bb0b4", + "0xb2abf8fb7c3690816fa133d5b4aa509cd5a6e3257cfeb7513d1408b12371c4d58c44d123ac07360be0d0dd378e5bcf99", + "0xa9fe1e4fb1574c1affac5560939face1af6657f5d6abce08d32fc9d98ef03186dbb2dbb9fd1decd6d8f4e4687afecce9", + "0x89b2f41e51f33c3ca3e44b692e8a6681eb42a7f90b81c9e0a0bc538341df9e2039ee61f26d2ebe9e68df5ed1bccf8cdf", + "0x8b35aa7b1d9e2135b35a1d801f6c9f47c08a80e48603f3850b425f64e7fb9860d1adda04f92a1ba22d00dd0a26e781ca", + "0x960574978cadedbd4cd9f764bee92f94e08b7af65403de36b21bffc9424bcee845b3b028af2e9e545dd77cf1e69a6a7d", + "0x840aa0f34b5b6c39471f54d9e85f1eb946468c4fc01963a9027cd7864df01f73c2e864f1f07aeed4b1b1af72808dfa07", + "0x834464a84a11200e3c60f816044c254a7d9baed64aed45a17325cef7fd62338e0a26da78d199d30ac3411714dc813223", + "0xb4ac6fe2f5059546f4ad9a361426ead33237b6b9030b129bf0122085c85fe4ccb33cf90f5a7f23c5b708a5ac64b487f6", + "0xa12aa9035464795f2a67f3eaba478d5ebc838ed9e997c7dfa241e1ed60a94b367d3f969ccf0ef02028c35215698b309f", + "0xac8d926492ec2bb68c6d8aa9bce49085d3d266f3d5f1f924032b87c42b44e41da7c047eeb01e4618f9d0f123dcaa537d", + "0xa5142425825d813ed8ce1849d81aa40b11f1cc3daa89a9f798dd83065c74820b4da6122b3308f528b074531df66e1a5e", + "0x87ff55c9f5aae079e7bf24084dd9c6b3bc260727d942d79cbe8dc13341d98525b4ece3ed8169994b56a387642f09134a", + "0x88e680f148ef2ecdcfed33b61f9e0224790fddc9069bd6999e9bede1791e761637c0fd60b52990b6c93e6e5429e483ce", + "0x94bc20bf5aac6e9f1060d02eacd06c42aeac9a1c5635b15a83985dfb03938ddb4999a822e865635201489c7f75601b29", + "0x849221cab7599f25f0b114df092bd5e8c2430503ae959bef1543a101de0790a78245db6a145e26f40b5f9bcf533219a3", + "0x88b6f2c2e7a7954fad11009d839ce50780921f80292320868d481e38d26aecd80fa607e82219a99532d88cf33b39f562", + "0xb0d82947dc23c0b88b86c321b582c15decdb825ed909a731b42d46bc895009515a3dc646c98dbec7d71b0722df82392e", + "0xa2cfb9f7c1a76c8073363c1c3bebe5dc29fa76533caea41046c51ea9bbdc693a121b957cd96be5b6da18704d1865cff7", + "0x8f0ffab9a83355a22683a9d998d1c1089449eb308711eaad4265f05927ec6d0d1ca39217082a0b372e02234e78dbaaad", + "0xab024661e2b2937ad374c8cf2e3669f1dc55558a3a881e9ec4d461f27e0fa92e2bc88230f038bfb051cf2145ca747a07", + "0xb98d9b9ec9eefa56d38cca959ce1aee7b6d4b41a8dbbd34b3f50c0a5f97f84ed2502ded1ce8cdb5895872360d4ba6d61", + "0x851244158b3184a62d2c98d148e2b1102cf0d5500906bbc2deda95acc5e3bc4b4a3344febbb31ce05a56dfee86a74913", + "0x860d9e2cb886bd3620b5d7499d14b415532482569bd45fd76e3e8052d78a73ae4b2b41f139f9cfb136564108cd93c0f3", + "0x8305a052a0fb2bcd41f3aca075c5f7f233bd8f861451d03f3a6e6e31f7d08dd89fe1eb4dd7b238a78b12ddceaad9768c", + "0xadb703e4778c7e14fb83541ab00b5fc344108243ec6827c5d9b302ee68321aa569da1718424e6a57979ab7536d5eb43b", + "0xb1a754b87b9e21aeb86217ec5b4fadb7535344567f1bd15e88ec12a833fed68e26bfbe03b7709ce24ba6c925ea0a0e07", + "0x8c1e2f6bf820e1653f3b8213e9d959d8649196223c2aab57b7ebda094f4919f88d883bcc6a0cd0be335f26f5a2a9c962", + "0xa082deb9865fe8668e91db0e4fd7fb50fb3fdae3e7bf1217ce0aa6f286a624624cf936d762bb2b6c3fead6826694f846", + "0xa10540ca05fbcccdd0a2a66aabab3b36e9bb525794cbae68bc3dace6116f58942218e9d5e9af10d67b5f6fb6c774fdd4", + "0xb81d22c4ab0ccaf447cc5fc2ff3bd21746617e6773bf43257c0d80331be2e8437b88c9c45309ee46402b38d3d4911caf", + "0x84c7c6e924713cab3b149f641dabf63ad5abbc17c1d8ee7802a6630507aa1137f7e034ba1d12ec13f1e31efbab79bf13", + "0x8773b9d236e5fcfa8c32e471b555264692006bf9a869a3c327aed33da22dfbf5780ecea7158904d4d6ac4acfe9789388", + "0xa4c2c1bb7290eb7af2013f7dde78282148593f066b09faf42e61a3fcf81297caa5a00fdbf6b93609c8c5782a0f25341a", + "0xa7bfa6e3f273da3dcfac7cb9906bbe9fa4fc2872b184d79813ee273e6cc4d7f37f46164362707a1976f5b6a2c5d7ed1a", + "0x8b71502019e4263fcda354a0fd10aaa7da47f4abb7a0c715c7b017e9eea14f2b64009b29b467394668c7ca995adedf82", + "0xad7460fba7deccc3f9a7d204233de47ce30ffa55e1e164975cdf06480a6108720bc397b93ca8c959df77d44a1e1f05f4", + "0xa5b8df96ccb7b078a3918e74b1b10da21df982538d2c9313f5129b2797c8a6db9ff8707241ff72d3e9d5983397321736", + "0xaa6cfa6386660c01879656da6c4e72497690708bae6c5cd1d088f443cb5bbbe75561d6eec256a72b9728377eb83ef973", + "0xb9699ce7c5c878e44114ab7a598646c6c7616b8e08a9ef8ec291189ef9945c1a538d2abf1ce3b0da0f8eecb303b81b43", + "0xb8d0fd1d278f53c455de92ec4357885fc6648dc5f276930263da7dc885b4a9628a2113e28b66b1e64fd08189427c614f", + "0x84ad8d262f6ef5d93e82ff6f4af995148eedf6d8e079124daee9b99f506e2968922eac2c7d4aea741fceb7733f20b2d2", + "0xab5e30ab54641e3a44450118b8235554e0fcfffdfbe1430ceb3f7ef33325725741995fbbbb0c16f0875aef0f1e0c98ec", + "0x80e2cf8bf386ebda46045852751611f2af80eca2e910d9ec5f6e2c7376611534604ceafa639272b3d503b02bd66525a6", + "0xaaac69af8fbb87da1c1b7c1b9e59942887ae839a91f0c1d191c40fe8163d7f1dbe984e4fd33619c73e63abfa7058f1e3", + "0xa6194224ad838ab86e84dc80e9b8abb121ae6c3c7fddc476463d81f14168131e429a9757e18219b3896a667edda2c751", + "0xb68f36aa57aedc7d65752b74761e49127afa65466005a42556230dd608ecc8f5efdb2ce90bb445a8466e1fc780eea8c3", + "0x886c3fa235d6977822846b3d6eccb77f1e2cd8ba3dc04780666cf070cae208b7513dc4525d19a3fb6385cb55f5048e2a", + "0xa9801273ef850b99eb28f3dee84ba4c4017c95398730c447efe8c1146b0719f252709d3397ce60509e05da74ed0f373f", + "0xa58c2a5dd13e08ffa26a6c5e5eb18bd8f761ab64a711e928e6101512401ef2b1c41f67ba6d0823e16e89395d6b03ebb7", + "0x91318b564ec8b2d8c347ca827d4d3a060272aec585e1acd693b2bafa750565c72fec6a52c73bb3ae964fdaa479700532", + "0xa058db5d76f329c7e6873e80c7b6a088974522390ccaf171896066f0476742fd87a12fe9606c20d80920786a88d42cec", + "0x9838e07f9ed8b3fbca701be0ef32a3f90752bbe325aca4eaea5150d99eb2243332745c9e544fd1bb17e7e917202edab9", + "0x85a9ae7dd354f36e73baa5ecf8465d03f0c53b24caf510036b3e796e4764a2bc17f0373013af5b9f1b8973226eb58cd1", + "0x896a4ff4508d069a7da6ef7bed66e1080991daee8b227f3c959b4f47feaf75fd1b9e03d0917b247c2db11e105395d685", + "0xa36d9a6a037bf498dfc0e535f2034e6cd433c7b52e520469811eb2e9f04499a6ce40257d2905300df7d81f38d1bba075", + "0x97aac3c5492aca879b4c06db1834b30b8850a244d29296046a84c637d9580c8521ab4752ef814c96f255a139660d7639", + "0x8552bf592a84ab4b356d01643c90347377ebf1f2b38a8c2e55a3f34537b8c7dcbd62e6776d6c2114f2bc2d4344d1567c", + "0x84474ad163db8e590943ccd1dc50b4f444beb8275919b33f53d42cba89831e9d42ce2de52b26f4412e2a0676ce913277", + "0x900799dfaf5eafeb297c7b4f892438bf2a65ce04034d66f8e5cc3836e4eaffe782fba4f4455a0fcab49102a240d1780e", + "0x817176415e35ad4a204b9fd5771bae6cc270f6ff050996cec89efbe461b2940ae5dd3c6c7d7e31b1da5285b207efed27", + "0x965e5791c927d47569bc54ec9b4c5305788aecd87a26e402aabeaeccc03480df46f0586ca2e2a9918885cd03332af166", + "0xb96d9ada4b5a04a94807d71726bd557de94fbd44042d7dba40560eebe8658d1da49eba54499360619f3b2c38e8b5ed6a", + "0xa07b6d641a43e02e7868f30db4dd5069a2f221b4f122ce9b11eac04abadc4f25f3207f1d2d86c7935b1a3d9992ea9814", + "0x8250d4d8ccac846a4b1a9fa392d9279b5bf2283c8b95d8164c3c0d199fec8849eab85755f2a2a99d584a0407742e3200", + "0x8324cf49f56fc14162f9a9ebda1ebda0388d09d8688f1938aef7dbf9505fc119069efc552f68cc7cd9213f96fda2c6de", + "0xa98e6f1e85268dccbe3bf4e92c9f455c58dcb53de1dba3b78589adf2e50e79f8e245f956e0d098eb46f5d3746826c6dd", + "0xb103ec12f266b4153d67b54d8fc079357ee342cbe5008adc3e0689a7f788534c4601e60e939731f49e4a1e24fd589f82", + "0xb2d7681e866420413cc98eae67614d383943e3762d5742cb3c57e26157633c20880eea1209feaf68402d5d33dd699708", + "0x99fed0ae4112ec9ed74baac70d202a885aa51cb555a3886b49016744dd4017640dd5dd564998c4d842a9f38f3e004e68", + "0x95c35401314467219c8bfb1ccd1f1eae6ef4fa9e48fbea14f70d5315e67b16c46cd03554471840e4a5030b077d2a3856", + "0x8d029380e0c294400d6b8673a23aed43697cb6460fc1bcf217aca3b47cf240886644ed09521d6a05f6abf56f99722d84", + "0x8ef54d1dc0b84575d3a01ecba8a249739edfd25513714dd4d1941fbde99dbbc392f7eb9fb96690d7052609af23aa57f7", + "0xb8ad2b7af4812417aa8de8f33a26547f84bb84f39501d4b7c484cc8bb54c7e166c849b95240fbe459a4719a6e3bf1651", + "0x9858545de898721d19930d8b360cacc5ce262c8e004867a050f849f7a2f2aba968c28d51f24a9af56aaba23a9ded4349", + "0x94ea5043b70df1db63f9b66b4f9d8082776f721b559f27d37b45e0a84faf47f948d7c4532dfd854a4bac49fb2ec8e69e", + "0xa2fd88d7b15e3c2778f6c74470d0f9e1a1f979a4d58bd205361eacadab9973d585a6508e685e640b272d6f8a448eae05", + "0x88defd6bccd55db8ca84e3c8d0fc55a3456b41788f1e209d0aec19c9c70febebf3ae32cacaa1dbbf796d7ddea4b17995", + "0x88b8cde2449d5ee7de2ee2f32e845d27e171a51ef64f1d3d8a5fd7dbb9f898ea70eb7f6410cddfd7b7ae70ea8073cc2e", + "0x8e044fff6ec557824866ac76301b6d93ed19b7177aa6baa95046330f5d69b572b59200e3653cf2f2b559455e782e8960", + "0xb5446b4d6741c824885790d2d26258729dc0ba2f469c85a47d38886d933b785a4f38a951d37f3ef4bd5091c03fa3a071", + "0x956c8afa8056e9a71ab2e8be5241ddbb3a8b3cff2110cb0e7389493d9fa45e6c4b769ebef540a952db6dcd8bd55baf64", + "0x925950cae25615246e29d594ebf34fa7d52f78a9867338648158f2131e6eb4dc17e18f9db8a5fdd76d017b3a9798b3a7", + "0xa17ea4b43211ba990270c21562690b3ef154a46c3d669c4674c80bd424cdfa95d8850c8e882b8d06504f929cba3d93af", + "0xb315ec723973a138508afc387ef651fd8a8804f93975fc36c2eeb796a304eeb1508518d8703e666a74d14318253f526f", + "0xa995742d7433b3f230e622de23cb2d81cac76de54831491cc29768eb4a56da60a5cbd573e1da81fddc359b489a98f85c", + "0xadb2e89f0d15294d7118fc06d4fdbd9c51d3ecbcc23c69797e5b8197eea0d6cd1240910cf22fcab4ef1e2dc2dd99da91", + "0xb5ec9f9fcd0b5d176b643df989bb4c4c1c167112373d662fb414875662d1a93160dc0b5cdf540e8a30e5fcbe6cfbbd49", + "0xb1291b53f90aed275df8b540c74a1f9c6f582e16c5df9f5393a453a3e95624ab7552e93d6e2999784e164046e92ef219", + "0x8bc7b7b1a584a12d5ae63d0bbe4dc1b63c9df9c89bdd1095ff4b8e7c822bf8c1994c92310a3644033c7c9689f4b7d2b0", + "0xad7fc45506a10ca48f991714ecc055cea376c0cbe667f3b40ee8dad8446218835439ae59bccc474cf47b053748ceba6d", + "0xb134756828a5f5725c0b95109e09ca450e3834b127163a0aeeb544e63cc0cdcdf66f8ed98c331c7c98758f46af369a84", + "0x94535bf1636be0974b112fcec480ed8eafc529933f3065c40e417e608e43a392206cfde8bb5a87b720263446c90de663", + "0xa4df4f6efbc3701000fb072e5cbed2754b9ef5618386c51ff12f95d281d1b700fea81fc1365f4afc66a7c83bd0228fbf", + "0xb0336b3552b721087c7e2194976a9119aee13ebed9f1c3c494353707fffde52d004a712965f460062ec9443620716302", + "0x99a39d1d1ee4283b75fa8c1fa42b6a3836b734be48bdd48050f9b05e48db6354fef509623c6ec8d447d630a9b3352b77", + "0x8e3dc3583d40956f9e784e8bbd0b5e65671d2ff2a7c387b20fcb7da9b969f2d122aaf7f054d450dc611737604548c03a", + "0xb5068ec5b7bcb5d8583d51cb25345990f50d1f7b82fe535a6a6b17756355885047916f466ea3ab09eef5516bbf2dda90", + "0xa8284ec1eb1d21e693f31a6c074199ee85d8a8da2167bffab5fe240defa2773971c8437e358a18f7e58d1e2954f57f6f", + "0xaa7415639d29081acbaac3e9c6b059d68e8702db3f430b86bb6e220d476fa74841c875e9d471c8a5423c58b6fee3cb54", + "0x8afcfe6f65fa6e07c2cb3e1756c0ef2c589830be96edd50c3c248e3b17f51a4b08ba92ef7eed7991d81667ddfbf2bf7f", + "0x83b9c8dec8ca8f9b85f0e36c08c5523cfeafb15a544398e6f93b48b5fc4b15a0bd05c0f176a9c2469664acab8dffb0a8", + "0x82a128a89ea46b9debe5c903b950c0ab30cd7570b979ca911500b5c2cca5c4ee6b2c2fa414b5f28e367f4671ffce60f4", + "0xb79fd0ccd2629a361cd6f9307c02ecd4d1f07e4ee03ce4b542997e055b07a026cbc0ba05fe3da309efc58db2e401a8fe", + "0xb190751141093823b4b5324cc26c4f3258552f7893241201f2fca1ae9b1a1d4d4964a9abdde8642cf308ded61ce5ef09", + "0x935fd48b95aa6f9eada0cf9a25a573f0ffe039888b3410788c41d173747bf384c0ec40371bb4383ddcc7d9f2db3d386b", + "0xb9affe100d878491ff345636ffd874ce1f27852a92417694afce4163e6a80c78b2f28d78102fd06c3283ef273ad37642", + "0xa877670276d49ec1d16c9f1671e43ade11c0c1a1413755f6b92be9ad56bc283e4bd2ad860367c675d5b32ff567301fc4", + "0x8c660d16464878590761bd1990fd0fc30766e7e49e97b82ec24346937856f43990e45aa8ad37283cb83fa16080d4a818", + "0xae1412087da5a88f3ccc45b1483096aeb4dcf4f519ff3dbe613f63712f484bdd8b2c98a152a9db54cf1a239ae808f075", + "0xad83cead97a9c3d26a141604268f8a627a100c3db7e5eefaf55a1787ddc1dd5ffc7544e4947784cb73b90d1729003c8f", + "0x97c3140ce435512a509e6ff3150da385fdf9e0883a5dc7cb83d616ec8d0a0014e4e0fa57a4d12c7997cd84e07d49a303", + "0xa353773ff68f1615454555bf658eabdcca40a9c7bced8537ea6fa8d54764fd1f032889e910d2a2a342835513352e2d2e", + "0x89e8df0c17a36ffe08149c2ef8b27306d04cdf437135aaeba697abc65e3c8e91bcf1817919a8a826acdbbe7dce79a18a", + "0x9928c2da15ac6cb20b15859c22508cfcd452c5643cd22eb84abf5f0a1a694fdefcd8fc329c9b40babc52630743d6b65a", + "0x99d837b556f8d13108eef6c26333a183f59383b39958dd807b10590c3d37f62ade6c4a320ca2e70567e0218b0ad5807d", + "0x9272da080e4aa18720b634640b01bf1fe506c7c8a89dee8759a53e2ca5cdbbd4a4f3aca54924c46b935362cf1eca066e", + "0xb4d39752c882de1c1daf3854202c1d58c2bcf35c882006eb640fe54a97be2655281cdb91c30d1a41c698617c2cf64b01", + "0x8bf827f4a7d47e07374d338a3d8b5c2cc3183015b5a474b64b6086fcf0cdcf4852046c9e34d7917d69caa65a9f80346c", + "0x901bffc7db9c9416e06f593a76d14f6d9e5dea1c5f9557bd8c93b9e70aa4782bab3518775c2a5b285739323579f7cf0a", + "0xaf7e204388568627ca23e517bcf95112ca8afd4c6056b7f2c77c4da4b838c48791191565fd38398587761c8047d11c47", + "0xab2576b5366e6bd88b347703f9549da7947520d4e9de95d7e49966d98249406ed9270fe69347c7752dad47e42c4ea2f4", + "0xb12e3b228b761dedd99d02928105494ded6d4fea3026d73d65ebffa2e85e2cd75b6d091135d418dd95ac102c22b5ee31", + "0xa20b4a752685d5e31ee7e2353c8a1b9a5265f12bb775004d282a3ecd9deda44831bac1ac5151646428b66909b2a423f5", + "0x91a1d4bc0062a86cc6786a96fd3eb4436d8a4a187b7cbba02190d1cd6ed3c3797d9ae7d6ddc413f1c94a21f62bd04ef5", + "0x977f18da1a5df5cfdd0276f583cfba2b2a0fc6139520664e20068f8dfdde33e29d179abfd722f142448f4677aa47be6c", + "0xabc3ece90f0f7b1d80fd917de27ab0d88cca584ef959da520825e54cb5a71336b15f8b348532d08d47a6fa600527ef25", + "0x888d36a2c7cc13a1c1aa338a183a74a1f57713e76cb825f9837f43279ce4741999b76a16928147537bcc20f2e0195b0f", + "0xaf3f5dfdc2dcfe19de893f385f39f550cb1dab67c2e97f1d5fa735e5ec96d6680066803e8a0eb010dd4399f654195513", + "0xa0fb4e08ff56530a940a86c28830956eb6dec2f020f7faaea7566faf0a4fafe0cffe01480e87763ec22f201be51a6451", + "0x92343c5b107910b203c64a79c93d354f7ee5b7d1e62e56732386776e275285561cb887019cc00d3fdbe3b5d54460bec1", + "0xacfe7df83c4624188a1011ad88c1e1490d31a8a8c8016b40aebcdd7590d9c0793e80d2d7ce6a7048876621c252a06a5e", + "0xa7da001dc1e33e0e129c192d469d2bd6e5d2982eb38f3ba78bae0670690c8e70f40e8114a57bd0718c870ca5dd25b648", + "0xa903de5ff97dc83628290d781e206ef9d7c6b6d00cadc5bacffb31dc8935623ab96ade616413cb196a50f533e63641d6", + "0x8f9658d42ad14a60bbf7263f6bd516cfee6b37b91a8f53715d69f718a090ad92484061c2cef999816760a78552fae45b", + "0x8c15b72b3d5fcb9ffd377fd67d9dfbdd706593fba9629002639973db12aac987bd1db70250ded31c88e19efff612cdb8", + "0x88a2a4034decd854fb557960194ff3404e239953818a8a891bf72a0b26a8e570a65c4a630884de991ae7452b3234f31a", + "0xa09cae5c4c190537bf1dd75bd7bce56f7b799762af865bb9d1ee970f6a133c27cce0dd0f14a0e0516ceac41054e6998f", + "0x9760ebb1b40f9a97530c3b940d4ef772a225e5b63bf18283f8e302b9436c5209f6294980fd37058060e429fb7fdc3a56", + "0xadaa9400eb86d857dc591b25dbe3bc8f207b69e77b03cb5ee01f7e4b006b5c8f6ba2b51b5a45687479885708509363de", + "0x949efe6b00b3248846747a9ad4a934d6e4255994c2b540a59fbbde395fe96d69bb67908441cfadd8c8bbb561fe52da03", + "0xa19a45504b6b1dc3a0fe0e6a1384734a3dcd5a7cb8fb59eb70e49426c4fc44946547443d558e5719a04884ab3a2811ca", + "0x8934c9ee21e8d1435426fd0f64232a0670a7946ec524c054cd4f2cc8b1be9f89cc11002ca8aebae646a2050d91716b10", + "0xb1150ff8ffb34ffdcf7d603348c0aed61e5f90ee0a1b814079fc2a41325c75f2f9ee81542797ede3f947884266a772e0", + "0x86ce8cc7c1f92af68de2bca96ccb732f9b3374dad6657dfd523a95e8a931a0af2a80df74098514a06174406a40c16ba5", + "0x90faabb9ace9e13fd9584932846ab28a618f50958d2ce0d50310a50c3bc6b0da4338288e06e5fcbaa499f24a42c000d5", + "0xaf4a935c2d8df73332a16dc6da490075cf93365bd0e53e2374ef397514c30c250bcac569b6df443985cf3720a4534889", + "0xb7f948ee90f394789eb0644d9f5ad0b700c8e44e5e9ed0e49da4cc18483676d25740710b1c15a557965da635f425b62e", + "0xa917913091245beed6a997ff7043ecf60c4d655c4db0b1ef1c704fd9b0e1ea1335ce8b9f45d6e120f81805ce31555e30", + "0xa48099da8406399bfb1ba834f6f7d864111d0036969a5cb64089947a63dd9467d3857b605e9f57f5ad5f4ec915088d9b", + "0x9784c3f9be42eed354542b1446d734521f8e3f01cd9d495ae98f2e4a3a16767fe2ad909e0def5d9a6267f3fc6a172cd2", + "0x8d9afaa323847a3226ad7d7b60d87322ffcda2e4a8df89f58a076f7972d896588de685a2e155e243bcf9456b0a0d6d1f", + "0x994413faf0b843f4ec1842c706c45ea5f24351c68674a27887bc8b182eda756856e507a4e8bbfd937e2c4c581b629ee6", + "0xb3e72d9d1ddaa00c7d22f25462d6e9f2faf55e30d138dce8bb1517eb0b67132db758668aac26164fd934d732633bdea5", + "0x8e95875e338f714e9e293df104f0ad66833bbd7a49d53a4f7f5fd5b18a66a61aa0a0f65cc31d55e0c075e0d3e412cb90", + "0xb980091862b1a9f9334b428eae14bbf1cecb4849e3a5809773b0d071d609727270f6ad97f329eca896c178ce65883db9", + "0x915d7ae5ae780bdba27ba51a9788a8852a15355b569581d1f18f0d94bcdfed2c1ed5a4f58e049e9825cda11f92b2c2d4", + "0x83e581058edf9259d0b06128282327cacbb6afc939578223cbf93544599f799a8dce1fb21d52464f990a877086f42506", + "0x803612a38b6f6efb97941997e101ac1878e192456f8fbddb3359aa7f3023434ed8fa92e60ec8e7b4473b1948850e4311", + "0x864a1bf4ac046161617dde282e44ab3cc1843da01a09ca58aa00ed00eaea9351a07a9ec16d910819e7dcc28b8d2c8ada", + "0x922eb142845975d5f6f7dcfee6cac8c299b3730400e6bf82cc0bdd9888de21de9d9f1530640f702c003e1ed63b140cc7", + "0xa7db03c5be647dce1385ebc02f4825a654447fa8c4c8d4b22e635dbdd2b3ccdf219384e49a80cfb1e9e6182b6e4227ed", + "0xa167289ff0f0967bbab6479e4a8a6f508b001bbe0d16cad36ab4c105ad44f3f180e39a6694e6cd53bc300fe64dac1e8c", + "0xb7766431f6379ce62cba22ab938cdbb1b0c7903dfb43980a417e0ee96c10b86b447241e9dd4722fa716283061b847fb3", + "0x90cda18c5d66f5945c07c8c7dc453dee1370217ccb851bbea32578599aa669b4dd245dd8a9711b27c5df918eadf9746c", + "0xac690cd2af39932874385fbf73c22b5d0162f371c2d818ec8a83761e0a57d2db2fca1d757343e141e1a0348016d5fc44", + "0xabac820f170ae9daa820661f32a603ed81013c6130d1ca1659137d94835e1546c39a2be898b187108662cdcbb99d24fe", + "0xb2ea5a5950096772f2b210d9f562f1a4cfacc021c2e3801ac3a935f2120d537471307d27b13d538dcbf877a35ff79a2e", + "0xad94af4d0699cd49ba8ca3f15945bd09f3f7d20c3aa282a3113cdf89f943d7793e59468386b067e3c1d53425dfe84db4", + "0x83788367ec97cc4bbc18241cbed465b19baa76fab51759355d5618067009298c79d0a62a22e2a1e6dc63c7b90f21a4a5", + "0xa3e142d879096d90b1e0a778e726351fa71996466c39ee58a964e6b5a29855123d4a8af47e159027e8e6be0ca93d9955", + "0x860831f8d3edaabd41be5d4d79c94921625252aaec806251fb508e364e39fde8808d38b10d557e487603a1b274c9bc3a", + "0x88da39f334bd656a73c414ec17dda532059183664bbbac44eb4686c2601629ef8ff9da992c337a842e3885b684dd0032", + "0xb50addbdf7164e8303f33de5ce854d6f023d39c1c1984b214d9e5fb6f6001cd5bdda816f048a438ff3d696872672f805", + "0x999e58c4c69a912b84561cb09610e415b43832beeb95897eca8c403ef4754f4277754d492eef3673afd4362f50060fc9", + "0xb88ea0f60f8119c5a1fd9294796d387472dfad22442b29659713d1d88e7d854cb7cf5c9ef773627781188626bb2fb573", + "0xa068b3844e9dbcf74b54fd55904d56af754d8ce4c619fead7a07f9bfb9d02118db7c512ccec2489d2a84374ec1d1fb6d", + "0x871dee023768636003c799e6f6fd8d31315a4c0da7286345cd64264a016693b3485e0732be1bbd34dd5fa04dfa58a983", + "0x8021e8f508680df12e4a5a1bd49f2d7142df65158b0a7198ffa83abd16053a542fb93ffc33e5279020ba8c6a26feacf2", + "0xb5d3cd64df5bc965228b0bd4ce9e5797c409f7b64a172ba165e44a8e4b38e3d5fabc3e0b9a19afbfe427f887c40a315d", + "0xa54fdebbb594bafcefb1a03697711e0091c072e1cc24fb441fefd4e0a0518675a1d7b0966cb8294051d7ec0ac175d0cd", + "0x93922202337f72969d6d6e14a29c9c75e0420dfba712029941d1504b9f6f9761d706cbc0652cd09a1aa5d22aec766af1", + "0x9711ebf1c7c7426190d4afd5dd03b014a456bbd9d90ed101623866a280550df26a629dde400c03ee3699f7d827dc0bb9", + "0xb4d686d8bc5c1e822a50124c1cc23c6bc3a1577a3d0b8d4b70d1797418aaa763283c09e8a0d31ae6d4e6115f39e713c4", + "0xa533ea2ac683e4ba07e320501a5d82a1cfc4fa1d65451000c3043f0fdac0a765cc1125d6cc14fe69975f3b346be0fdde", + "0x94ee563134fe233a4a48cf1380df55ead2a8ec3bf58313c208659003fb615a71477e5c994dc4dcfb2a8c6f2d0cb27594", + "0x93e97d3f3f70664d0925be7aee3a358e95ae7da394220928ae48da7251e287a6dfbd3e04003a31fab771c874328ae005", + "0xb57440d34615e2e7b1f676f2a8e379e1d961209fe00a0cf6798f42b7c28dbd03172fce689305e5b83e54424bc3f4a47c", + "0x97644084c6f7b4162bc098bed781dd3af6e49e7661db510975528f1dea8154f3d87e979bcae90c3df3a7752eb0752889", + "0xa923b27b225b2a6dd5bdc2e3d295b101cac5b629a86c483577e073cea1c7d942c457d7ff66b42fcf33e26c510b180bc2", + "0x86698d3b3873ed3f8ab3269556f03ac8d53c6e2c47e5174ec5d14b3ed5c939750245441c00e2e9bb4d6f604179f255ef", + "0x87946826d3aa6c7d53435c78005509b178fdb9befc191c107aee0b48fbe4c88a54cebf1aae08c32c3df103c678bad0ca", + "0x860864896c32b5d4cb075176f4755ea87fea6b9cb541c255a83d56c0a4092f92396a3e2b357c71833979b23508865457", + "0xb78fa75d687349e28b4ddfe9e2d32bb6a3be13220b8f3ff1ded712088bd0643da9b72778bcca9e3b103b80097f48bdd0", + "0x8a188b940446598d1f0e8c6d81d3cada34c4c1ae0118ec7e0eacc70d1bced28ae34b99667d5793d9d315a414601c3b22", + "0x842ac6f7dc14191ab6dddffcbc7cb9effba42700a77584aa6a8e17a855cd444c5d138f9d61bf55f43c6ffbcc83f92bc9", + "0xb6742902c3d145a6af9738c01cf9880dd05c85f0d0ef7dbe93c06fdd6493333d218339ebc2a02be1895436a2f734a866", + "0x98bf18488483c627b7181b049d3e6f849fce1f15794de59dcde6e5a9b0d76fd484a46e48822a6a93001d3aa12f48bc6d", + "0x8769cac10bda8c53a1c19419ef073a5998f73dcf2ba1b849561615a17cbc0a49bfe3eb4ff8801dd36a22fa34b9a3a7e2", + "0xb45c084d58028fdfae792210fcd183abc4ffddeb4cf52ebf3f8a50e4c4eec2a2758f1241b0920bebcb24b757c778577c", + "0x85c1216eec8e1fbc1af9b36b93c5d073a81d5fba86a6daae38748ec1573eacc6bef209e76c87a6efbd7a3f80e11d4c3c", + "0xb8007e34bb3f927ec06a050b51e633d7eb9e9a44715d5b39712e69c36177a03cd68391090cc3293098e54f6cf65f6caf", + "0x8e85527b27c9152b1ba3fdd532a76a79064ab097570508f233e09978761dfe3012d537411b47d0e4b65265eb32cea2ae", + "0x899779f3c31a20b76068ec8d59d97a64d2249588ddfd69dcbaac6bfaee8ce0ff3c5afc4e17c934ae7cd041b760eb555d", + "0xa5dac3d8f5fbef018509612e25d179f60d2a62451c76426bf546e9666fcdc73263d34aa6fa7e2bfd4c9947bbf5095eff", + "0x896900eeef9be2b2e755128e7b1c436af6fb3984f1e66c444bc15fcf3959013b4902c381f0eab1247f878a6ebd1f4ee0", + "0x8cb17f4b0af2e9b2cbb56f46e6a5d6874ea0daf147aae77303020b4e592ddc92e0dd058def7da96258b3a68b223bf22d", + "0xa1b6d3f09a9fa7ecc021ab7c5396541895da6e9bf1f9a156c08fc6f2b815a57f18c337ccfe540b62d79e0d261facb2be", + "0xae70888811434ef93da60aeee44f113510069fd21161e5bb787295492eb8df85103794663fc9305f04adcbcf11ff0c5e", + "0xa84bbc8624100acfae080ba8cfb48fd4d0229a60b62d070bd08fade709efc6914dc232d3f7bed76a59204f9252321aad", + "0xaea47d54652abd8ca213cfc623c8e30780f37b095b59ac4795252a29c2b6bc703a5203acff8831314478b8ee8771d4d7", + "0x8dd438eb8be14935f759aa93021c2b24e1d588f7a162c42c90ec3a647b0ff857f60e24c0a8953eb7bb04e04be70f11ce", + "0x922b07b5469680a10e7532766e099896f4dc3d70c522d8add18f5f7765d4ddb840df109146607b51ceddd2189fa7b9c0", + "0x83ef6ebd0ae6c569d580093e8b0b78daa964760556272d202d343e824c38eccb424262e5b7809d3c586f9e2e9c5c5f22", + "0x97f98bd357db6e093e967fe180cf67ed09fa711580a5ad48f07cf095b2e8fabbe6319f97d1f15d62c0ec2227569d8dbf", + "0xa1953a4a22fe6c2beaf2a5e39666b0eb53018af6976e3a7aab5515550ff2efa89400605a43fb2c4ac1e51961dbd271d8", + "0xa5cbd67f4c0bc98e20aa74c09e6f5fb6f42c08e59aaa477b4b4e61434c8884bc14f17cf11faecf46dc4b6c055affbad2", + "0x87d96818f2c4f12fd7705cf4060a97bd28037c5ac0f0cc38f71189ec49361e438ce863e6617651977708094d5336d1da", + "0x85e7c2daae5fe59f8a1541c94df50402a671a17dbb8838113fa4b7aaff6114cf2bb5969410cf21e6a162857f2f7a83a8", + "0xa19575083e1731bb04bb4a49414e97aaadb36d883aa993d1f6847db50007315444814740e67e10177a14e0e074fd4c7d", + "0xa00ebfb5bcc3a6da835078189038a1e56b7dab6be74332b5ff7440e53b0f9e1eb9973effecbbf37000021fcf50c7c1ff", + "0x8969d7943abd3b1375fdfc7d6124dde82b0f7193068ed6ec83bcf908734daf3487a6a30f7b322e54a4818ae5f86d91c0", + "0xb959c8d210fa43af9b20d1fe0ea8c4921280eb4544ef6ea913309ff9d61c9327096707e84dc1662960519be8e7d080a4", + "0x9011d8ac651c42e0cb03931a9e960f58e02524c6b666047525e3b9097e9f35fb2b4b278efcce2bd5ad463c6d7fd56694", + "0x937e3b22ed0fcdbd9ea5a1b97b84bbe86b7f5b2de3866a930611112f2217f4ee7d9822c4ab1253823f77bceeae0c8e10", + "0x828997e5d121f4c305e018a0a0ba338bd6a34a7b4dc3c5ceab098ee57490311c130e2c045b9238a83908d07098d9fc32", + "0x8d114808eac0f2e1a942d80dad16756ec24f0276763cd6771acb6049472e05a9bb1d3bbd5957f092936b415d25c746b0", + "0xa063c5c26267ae12887387cbebbe51fd31bc604630b3a6e8e177e71d4f26263be89112cd12d139dd4c39f55f0e496be0", + "0xab1e1582c8d67196d10f969eeb44e6e16214f1316aa4a2a821f65ba5834326da6cba04373eabfd3b3072e79e5c9717e6", + "0xa17b1dbaa11d41457e71a9d45d032448091df7a006c1a7836557923ab1a8d7290ec92a7a02b7e2a29fcea8f8e374c096", + "0xa1ed7198da3591771c7c6802a1d547cf4fcd055ca9010756d2a89a49a3581dfe9886e02ee08c4a2f00b2688d0600509a", + "0xaf09aa60c0a185e19b3d99ffdc8c6196d8806169086c8ff577bf3801c8ab371e74165ba0f7329981e9252bfe965be617", + "0x98c04cc8bb26ffce187fa0051d068977c8f09303a08a575175072744e0a5fb61191b1769f663a426c30d405515329986", + "0xa542bf1c9c3262d488ea896f973d62923be982e572172e2461e0146190f2a531f62acd44a5e955a9f1e242b3e46d63ae", + "0xaef7b7f30efd50e4a66c87482386f39f095bff6108e68f74fd3bb92156c71c75757912b111060cdee46a6b3452eed657", + "0x8afe1e0ccd00079702f16ab364a23bbbd3da1889d07c4f8cb04fd994bf9353216360dbd364492932bfe20b8b69ae8028", + "0x9896c690999db3c08cd7b25efb1b912c3e0f976db98a3e830f086aef93222d06ce570a7b2babcd7c81d8f9955169669c", + "0xac7bcab6a281468907ef1ea8a6c1cd624159c88839131bef6aa0c22f331fc87ec6128a2c2a333fb79df549e4587e1a12", + "0x987935c08a30b099d19f96901315a2e60591baf898581c40bf5eddcda806ff24a4536e30ed1e6c0b128a83fc77b6e81d", + "0xa0a6945bbede3bb09a4a09ef27baa20619d3e15af5673b9350601bcebe952597c989870746cf75767ffb73b32c6c9c6f", + "0xb0f5590079f0a0302b08a0cc1b7a5f39cc6900c2a5cdc7baa333d8328a731b2df5dbb67e27a154d3c44ed1a795fc4adb", + "0xa7294bdeea210e528f277f3d50e89e6d79950494478998181ecb38de675020130256f2f2a075899170be964d478458b0", + "0x8ab3041b895a631869b439d5599a66facba919226ca9b39d915f19d59f9fc82393ea781377e9bd3bcc5a310e41376914", + "0x8da399b59151fd48b2579948bb82698e3c9804d70ec7d6f3cc7e82901f9f2de5ee850349a7d6f43e5e9ebd47bd78620f", + "0x80e8c32de83d1083916d768b11a982955614a345d26d85b457f2280ff6c52bb776958add7c1c8878f7d520d815b8e014", + "0x81bbec7bd99d2917d2dcd8a288722fb33ad5a4bf5416fba8609fa215fb80e0f873535349e7dc287f892aa56eb9e39c4a", + "0x9665796fe04c8519206fba58496bc84a8b9113e7ea8e152b65f7f732e88beea271dc97b1ea420dbc8257cc4b18a77463", + "0xa97e342aaaf693ddc87e02790278e4bb50117af4413cd703bdf3b7cad2d1facf31fde1303b43ab2e0265467474f97a8a", + "0x925549ebebed348886e37773b05cd8ad04906eca4536bfed951d1ee41b3d362ddc6e1a302c21ff3a2d1e70e95117922c", + "0x818fdf74d7903502101551bbf48d3c7819786b04b192d9e94362d2fcb85760d8b6f45165a5443aa5221bef400525ddb4", + "0xa9d29de7e8fd31b59f4a087168d062a478b1329cd3c81c31e56de4fb40de7a5be9a5269ef0be452c487443a0b097dd50", + "0xa85286ad573db4c9aa56221135da1e31d742e0f6ff01d6b159086d7258f78b08dad55ec8eb5c91ee9d3404b2eeb67e1e", + "0x92a79b37db5e777f9ebbebde24a95430a199e866e56597c7d0b0e7fb54c7b092c2f6cf61fb24470ddf250cf609898281", + "0x8d79f5ca67ed67d52c82949af342a9fc60fb793c47c76d84b4863c550796fcae2dd59e285897c6fb96fe31cee1efa62c", + "0x8ad2e0bda03415ab86324992bb62dfa3612d2d003765bcad1468087c27971d08bdbae5252681f0115a184f4885d444e4", + "0xa08815af979286538c31b4aa5ec805053790af1ca58a8c4341be51136d094a8a05e569d876a079033298ad355ccb7ca8", + "0xb96c2978d0165d619d08281d295e90df78bc2375d0afbc3142ebff9c2cd4b0f0aa97a9a0e3740bc4dce0ff8a9fac8252", + "0xb7752cd0e582f35ab0d0036ca9c0a9fe893a6ad325164d78d865a604a85d3d23729e0362553e8b8a3d51816beeaa30cf", + "0x99cef1fafc29e7adfe247c753c475ad4bda7a5f9558b79c86e8a65968ede67adb38dc30071925c9d66a13860027a6735", + "0xb9f6c65af178c791b6137d71980651fb09cb5b42f268999c728c6e129985a9c7d77b3dc3b50751bd29ec9ee0b3111dfc", + "0x8d73ae61fff5be883a281782698075c5650083f00399992688738856d76d159803be0059fbd9dec48f4f0432f0590bbb", + "0xa8a4a2865226de9bbf19e12c7e75318439fa6cf1cbf344d5e79a8f363439d3bc5bcf4df91b54581e7866e46db04eaf0d", + "0x894582aeff222e145f092ba15c60d3207340c38f2c6792ee2ab4d82d50fb544ae366c2985cc2b6c2f970bcc5f4b46385", + "0x956014ba2d20a056fd86cb8c7ceeab9a2c6f905dae24fc1c5278fa5b84335148ebdefec5dcde8eb9b084700724fc93d7", + "0xaf217fe2b654eff6d11a2a79fe0339a1d4cb3708b7be9f09d852158b5a44b4f9b04406d6d67c4f144fb6b69a41ae9d0f", + "0xa90752a784bc00df94d960e523f5596695d16a534fc806179e0f878fc0e82a91b25e758e91a165debd815dd1af5f1028", + "0xa697606fb32979549ad822b31df8eaaf50de4ead984439a0a33e955937d326519bb9f62c8243ad37f764655f8d32cc80", + "0xa3ad4a30922e45a3e665551e5611384f1c2d414f6fa806184b0c826af05f014dc872585e255543794ee41e43cdadd856", + "0xb29c255843a82ea74a013bac6c36a694646e61e6b9cefc4c130e2ee261e3bb5da3e0fe3ee7e6fbb009deed0530bc1c82", + "0x87e1cc7febefa829cf050aa2aea59385d1048f8617abba691f7ea9ef58eb90ad12eeb9c439af228b0e34897ba1cf1b47", + "0x994d3222f89e9c8c154362190be7167c8c2662f0cfa9d50eb4d8175b255ff0de09dc548ee312fc8226963c8c16f43e8b", + "0x8f1a980be640820f2d1e953264ca4c30330878971669852be3d5d6b41c488be1628b935388bfa2bd4de484acb0fe661d", + "0x854d90d0721579c8c88e147a4aa83553c960617b18075f8224b975562dccb30b0e02e81fa9df7070f356a0eeffc3b14f", + "0x8e156da9d4330a03e32a25a2f0b861fd3ea5c719fa4f834119baab6e5fa5236a9baaf0d44147bf0841418900037f6eac", + "0x96586fc49e53a6799242ddf617000db5a0ad20c6cb1686af2102623d64a71aaddb8e468b15fa6d100d0384e448548db4", + "0xb44d8d85c8df95d504f82d597f8c515866d4d4a326fa1b816dcc5bb0cc4ef1a52647aa5d2e84c62e194c01cae0885d21", + "0xb75c43e676a7efd199f8b32ae31f176ec667e714df355e9eecee97246f72af5bef9c5b04c11e7e90fc37bb9163f957ec", + "0xa49835ac0565a79f6a9078cf0443c5be20561a68b448289589721fded55188583f1d301925a34eea647f90a6e66c6774", + "0xb47c17ff6824a00b8f29df0adb7f06223208d062bd703b0f763c6eee4ae62d4217eef2da4f4dde33f0b469c2f2db9e42", + "0x957cf039cea6f6d41e368e2bd0cf77315938a0738f15ed9ca342f0a28658b763659ac1d1a85ecb362f13de12b77bb582", + "0x903a52f8d2439fa63f59e1e9aba864d87b0464ded63814474947112375236a6f84e8fa003cc4433c8208d80e05fbd1b0", + "0x8afd524209ff08d1eb6312b078f7afeb8e1155af649e930ab711dedda226dc2db6b0354aab9652eea7f433f90015bf7b", + "0xa95c3c9277b11bc8fe191773bf567641be57c0549913b973fb18740ff9cd7b3f7ce198fa4dc1086b2b8a446012459193", + "0x9455ce8163fce04aeff61e7808ef3aac4725e51404f0858fe5d39d7344f55dcc7871ca332aa5cb1a63a4399529e48907", + "0x809fa35b6958f94e781f2c584438b33f5ed528a6b492d08960cf22ecf63ea3aa1e2d29bc879e17296e0a6cc495439cb6", + "0xb0f50774de212dd33e5837f6b496556215c665437e657f674fc5117e5c07dadbd0d057e6ac4c42d50a8eb81edfebf315", + "0x844c65e263891d0b2fea7db6934cc4b7fb6bee2c1d0b9ab4c47f2eb3e9c5d7197dad828d38c54139123740151420280b", + "0xb13c78c9efcbb3b28eb3fe0b971380b7d5151c80948a99cd93c78b4c3ab0e86df6226a64d91e0a2ea4a1c0a46bc0404e", + "0x90300a541decad460c348b8f4257f7a29687b2362ebee8d92fd03cc0e85b285ccb0ab1cb2ff5e29c5cc5295e351017cd", + "0xac49b409ded770c6d74f6e70104c2cdc95b7b90609da0743c9923179e8e5201ead03becc0ab10d65b3d91a5be0d52371", + "0xa257b815bd8289dfdfc21af218aaba12ccfd84ebf77642cc4cf744d9b0174ca0b0d7ab2a545c2a314fd5f63c140f41ab", + "0xa34778d8446e4d74d8fe33de64b2694ef1e50bc140e252af6eff3ce7b57acf8b6577a02ba94b74a8ae32e5113cf0a29b", + "0xab9e935bcf0d8607e3d66f013d9bce7909962cb7a81174923db02dc89e485c2b1c33d6065bdc7bbbe0450b5c49fbe640", + "0x94d2c5c5c309c9eac04be4636f61bc47fd9579b47aded57cc6c736fefb8dfd8f8a5de32210f7baf2052d04c0219d3b4b", + "0xb8dda9046ae265214086355101be3460421f7cd0ed01bde9c1621da510941d42bc93cd8060fd73f374fb1b0a5f38d45e", + "0xa6674649dab5f92ab9fa811d9da1d342cf89ff6eff13ad49f4d81de45438e81a384098d3ae5ccce4c67bda5dbe246d95", + "0x8d619f7564677bacba29c346c4ef67c211f7a3a14c73433dd1a7692e16a7e2562f1d0532454af62fc04c2fd2bb1789b0", + "0xa2b93d2fd4c707f5908f624a0fc889e20164d3c61850af9125f47a1719757a6ce6375aa1910eafa4c1e8b6e20c312775", + "0xa07d5585447654d82817ef4d199984542328b238157976eb9a267f0bdb2229acc25aee510be68f65a312b68fdd9e0447", + "0x8ef55cf95e2b24d8ec88e4136399a7763bd1b73d5e90ea45e9845123e9d39a625cc336e9b67988374b8ebcbc75f2ed21", + "0xb62c1fc32e27c767c461411b02fe9aa44a86586e1427406f4ef0b346d077db91952abce79318b382ec75b7be23058cac", + "0xb252900345f5fa15a4b77fb6af6a2d04db16e878b7bd98005333f7f6e3c8e6e46cf38fc5d1b2bc399c5c2ff4af730dc6", + "0xa4ab5ac0cc15d3d17b1747c6e3133d586870eae0a0d9c8fa7fd990ebd4fbb62e9090557ca2792a6bc6271856aa3c9a05", + "0x8e706b3f2e902faee10b22742c6c33bea6f670a8937c243db96885143c1db5c979e33ab73a38359b52b8d668ccd092a9", + "0x8a6792190ee6c959d79f60c22980ca140c638d88d75660adaf9bcbe6dc4692ab5f01e0c460170f09f74d5e582e85ff1f", + "0x97ffeedfc94c98ec85ea937e064d7b290a326838e62cebd407facd1ab4f08d9c0c109d79af7cb6170fccfa6c8243c127", + "0xb79970b67c09453614ffd83a0c923c17f857c6ce3c87a356298f8351cab0def7ed83efd4f6638f48df67e07bef4ad9d8", + "0xb90f1931c7cf1822cc0a97401119910cdfd0482daf09a4d7612e4e05046295cfb4cc50d5214b31676bb1a1c9d15f9c7f", + "0x922921ad813c01fb5d12fa7fb7ed8e0b0abbf7b19affa190b36013c55b88fe3c7df0ae663c970eec7725ba37b95a7cb7", + "0xa124f33e7f28feabb4089a063a08d52b7395d24eecd06857a720439dd9414b7073bb86fbd0b04e7bfac62d3dc0fdb2f2", + "0xb252fe50bc6677c004550f240fe670974a33ffe7191ed7675da6ac36c780c2f8d02be7da5d92cbe2d0ce90147847f8b1", + "0xae5f8c9c56070f919f3df2d2284348fa4b2e39881f7bc42c9b2f5b7cb1ebeef8ecac000f37329bbe04cc1680cefc7f4e", + "0xb432a4575caf7337f11eecfcbd34a6705d0f82c216301725ceae2b3c9df20fa53d1ebef65513e305013d1e0c2df522b6", + "0xb7c016fbbc4614cdbb12db1c9ac41f9a45d5e5ce82594d568a30cd2c66c3cc9d91a2c959697b67c582a0913de661505d", + "0x8f6f3e5e0347dddc1b2a34ec0dbbbb7cafbf976f19c9c902efb5c1427d1bbd4b71abd9f3fba20dda75c35a39393c989f", + "0xb0042a1d33a1ee9fdf3fad2299b8d70c4f1862d8393b5ebe3ac2189a2c5a58bb826128cd7a39b70d524a6dd976097e26", + "0x85297c4e8ae8d9b44c3fe51aa926c77d55db766c2a9f91b659040de36e34c9a4fc6f44380f8d61704498f6fd52395a49", + "0x8c61a988b6a00fe5a277450f30bf6daa932e42a2eae844568e3babf8815e09311f3c352dae6eb2d57a98d16b7beb2d22", + "0x990be28aaecd932e7edb2a97b9be2789a3905cb88737b1c79881302585801c69a3dd5fb230808b39db1352fc06e0b4a8", + "0x82fd14bdb335aa46f022dfe0ed4d631911e6b6f5eefb10d11e9e2e02a7df55012ed8162249d10b58eb76ced5a7b06cda", + "0xac39cb058df764e161db9c39b185f09aa210bddbd66f681f1697ddbe6b305735612d5dd321d3ffbb4876771bdb321e2f", + "0x858a3f7e57ccb81387caf8e89f9b6039e9aadeab06886d8688fe6427151a59ab2e77e85ba850c67d099965426c97779a", + "0xb57fb9ea623cec432946819937c6bded0b5d03c8c67b52b44a4b67d34adfb055e6cabca67a48e4d859b4be45162c5083", + "0xb84d2990b563d6d7fe1f4c1894989db25b81745090b94b1fe2ef708ac3b2110ef93d647820b2a51fcf78e3f00fef5412", + "0x817d85b9f5e1521733d2b1fa6d4f4957ac445dc803f97fc495e20b819b14e651332f9e0573d684b854fd47824c53f0e8", + "0xb09e18e97e93a8523101af594422fb71afc5b8826002314269016fcc1b44002d91bcb7c90d923d460f0cc03bddfe9af1", + "0xb867cbede82102de7cf6cd0dae68506869576eaa66c3fc806e73585310602682fc912dc37adf5ff6f0f34a07831735b1", + "0xb1126255798368b692f2796a3470ed16e5ffdee2d8c9e0f7ee3d2e92950c3e6365c32895171c3494aff2a6d6356f7e25", + "0xb05f0a0996dec16335c770a5df3f0b08e20020c838c2caaa1d3a4a2490ede98552f5de349de2ce6e4c4a839731d80919", + "0x98c512bb91c8fa191120ddf5d63c88076581cf41e15eec3c168822f12b3dd0ce4d6df74a7e3093d3e35cad1cb3135421", + "0x84ce38fd97f7f90012c2c1e59a67bf9f465a7ccfb6f308bdd0446cc82b8a26ff7c30e5c7cc375011718cad1b31adaa9f", + "0x93139db52c9fb96dee97a0825f21e34c5d6d36838e1e42f4d12d01eacbe94426c85a811fe16ca78e89e08f1c27383d28", + "0x81454037b1e7a1765f67e4288b8742eebf6d864d9b0f508ab44fa3243168ce0ed30cb5f33dfcdb995cd2c2710ff97a6d", + "0x828deb2a26efb2ff1842f735e2cc27162360f619b6e3e27a85bedf384912d4726bb2759a3016937973092ece1bf90540", + "0x87e5a7d4e7bd301078f625d9a99b99e6e8e1207c9f8a679f8ebbbfb467bfa0b5f7ef4a4d577c7d2670efa88221153012", + "0xb9dc9d0ea48deee201e34379447bec789c8924aecd030eeb93db159af77eff230976ef60ea9f4b4a9e9e95c1f9f4284e", + "0xaa6528268d46bf0627d87d58e243d3ac34b863513c725908a2617e4c6a46ccb1d8c8334bd6dd0eea7ffebec44259dae5", + "0x8d26c9ce07293f6a32a664d31e6df9a7ace47e6c38001635918efd9872aceab62de7757b13b783d422eb67bd28ce7bbb", + "0xb0d3ca88d9829a7459b89b0dcbdb8bbb5180b00d750bd959bd110f53c2dd5d4db554b6005c4765fbe7ec5903669e5ebc", + "0xa94d1c72bf3b2dc6bfebc9dee40f6a89a516b252bd9f4fad96f156e3dbfc151a9b8a02324d764c7656d59230a18eb61f", + "0x88996e79171e30b16505638d8ecb25afd875e5f3cc3e29860937f2b5e751c66e78dc77f744a0cc454a8a655142a93ffb", + "0xaf4d94f342665fe7ecda318de6cf1bc1c40c37dd83d060fedaf827459728152b5f0e280286ff5e6a0012036f6715f53f", + "0x96beaa7a2d565ec14a4e5cb895d33624c69da56b75c8d06ac729cb6d0cb64470ed4f9b0387083cd827b1609c8cabde8c", + "0x96b773fa2fcb7377bf71a7e286f37f1f24ee42cba5b4f33903c4566e5e5bcc501ea360e3c8435749107c3de84e272d8e", + "0xa69ac6218454c3f40ad0beb48821a218fb0a4f33ebade986d2fffd9a3900d8cfa613bc71676c46cfeaa5f644d1f239a9", + "0x857f139c08fcc45370f448ce3e4915bcb30f23daa4134407fc6d78efac7d718b2cd89e9a743eec7bf2cc0eccf55eb907", + "0xadeeba36af137fd3c371a2adbefea614c3ae3a69f8755ce892d0dd7102fb60717f5245d30119c69c582804e7e56f1626", + "0xafa97ca3548b35aeda6bfed7fbb39af907ed82a09348004d5705b4bb000173270ce44eb5d181819088aa5a2f20a547a2", + "0x8423bd2d07073b0e87819b4e81997e4d3188b0a5592621a30981dc0a5a9d0578fde1638a364f015078a001afb00891c2", + "0xb92e9d4ec3966981ee574695d6e4865810b8e75313e48c1e4bc5eebae77eb28740e97ecc3e5c42040f9eb1ee4b13b0ea", + "0xb07b218321d54cecfcd2ed54a5fd588a6be8d7a5b6a66dff7facfe061222c40553e076e57cbdfa0bdb08e0a009c94ba5", + "0xa71e1ae4d6096eac9ea4c21f621c875423de7c620544e520fb6ec3cb41a78554aedd79493cbd2c2ba4f0387f902ddd2a", + "0x807cdac291246a02f60c8937532c8969e689b1cfe811f239bfdee0791e7aa0545e9686cfb9ed0c1df84748e5efa5e3da", + "0xa1faeb4504c057304d27d54fb3ec681462384a354a4f0b6c759d4fa313253a789250c6b0f44f751b0718592637438a19", + "0x996bcd3215182d49f1cd15a05e1e0a4bf57e264400bf14f7253c6611d2571de7130cce81fd28e0411e0a80e9054f4f98", + "0x89d15b38f14bcd46f4b2dcae82b0e7bf9a35e40bf57aa947e9c4a8f87a440b5cea95229708de08ca596762062c34aaa0", + "0x8d8ddcaf79374c750b8b0b3d196acb6bb921e51b4619876a29d09161ba82a42271066187211ef746f9f40a5ca17b75f7", + "0xa3dc7f70f3a6c7edc483e712770abbaa94bfa3174cfee872b2cc011b267e0ef9baa1ab49e4a6c6c30dbba0e0a1237117", + "0xaa9e958bbdcb192b19c43fc6fd34afcd754949fdada98e9f4848e8db0e23acb27d19dd073c951a8819000f2356aa22e1", + "0xa4714e45ec853eadfe5c3bee7f683b81f97857bbd7833192a48936dd1460aee68f700a21658658b74b737c4fecf90c7f", + "0xa1ecab4215c1892e4a8ff3405d710163875e5dfef8a8cb84f5cac4e317d89c7696e3f496ed1747ca6f52b304190f4ba1", + "0xb9b48943eca3686219575026d395b969e6ff8159dc5317005df090e79d26901984e40ae4b1af060ed3ff6f42e0417d76", + "0x9644b9f90a66edb0396abd8c00066886f978ebf56fc22081031fbc9ce371bf9b04aa5a4ef59e59319b3a05bb7fb88b43", + "0xb2bb14f1c055a78596488e4e2d4135a6470c1ee43961952160b8498f674a4d23040606e937c02c1fc23dbd47e9bd4633", + "0x8c61f2fce9a42b94a389c7e52d7d093fc011099d0f4914f6d6f05b631df7b88182826edf9bbb1225971a080ca5c0d15a", + "0xaa6a7b8499cc7d256043eacad18528d38bf3be970bea4c6d4cb886690280bdb373688ceba3e506471e1d9493dc76f3f4", + "0x8127703363b3b35b06762c2353d4de82b7b85bb860db1028d3640f46bdb78f2d104fa77ee3e0d9db83833d2b12a966f8", + "0xb7b01f5909f2c66ae0fab156be5d79954e3a304615e1fe55945049dd4bd95f973bb3821117eb54db7e9ed1ee9a527652", + "0x8be47ba5dfe212420649193490838670c40540e0ea24adbab18c4a66e7ac3dcf94f068dec2533b60e08c1f64e7533e54", + "0x905a6c7e24b86aa54a05c329a6b4616d335bb0b1f1e9987562eee0acf82ad302c7c44981a1dd6b24c6121ca12fb92996", + "0x86969ccfd91deed93b355a2c21319e3bb08cc652b741463bf68c626b7ba2afce3f7cc397f2fb74588c2893477c948ae2", + "0xb5a9d20eb12c331d0d300fd4b85b0ac0bb74573178a5fac8ec9dce5e95acba07fab444260355ece442a846737a2dcd1c", + "0xa13497c11df21b11fc1a63b0ffdcf7f432da4dc2c98f8d07d36da4fa68aceb57af2158088e5b05e334fe0f264aeb7a97", + "0x882e4597cc66498a45e86a2ed9ee24652da4699af00ad35f73b5e74fde6ac3cee70630962d5ddd86162d4aaf11bbc11c", + "0xb748858c2bafa4a14ce44af35195e9c52aa75e109719243bbe278095acbfd6a7ae7e084caf8dae6939039b5a4e8fd675", + "0x83a2e0524507e74f51fe976441108f8226ba1b3a33f4e16ec45c5661ce80cb1840a93d17122cb8ca9e0f80d14f69877d", + "0x846cd2946c93ee5f24243d9ebc69936b3a1a6d59f45fec6c79b1eddf15ce30a8e73ad03cf606ee66baea3d8ff115f70f", + "0x8d98d0a3a94f6efe158f8423c041b546416145c5c2254bfa157efea0d1c99fe58acc7df6424ef29f75960b18d664ea4e", + "0xa39fa47e4b79f54dbf59d0b1726f1e78bc219fcfc56ad238c84b4b610e7892ff1e65d537baf5118a32f5e2eb80d5ee0c", + "0x8c30969a4519131de5e30121c84c04f67b98c8ad109fa4710dd3149cae303d51778add3f258f0482f1c89c169824dffc", + "0xaf7f80d141ceb78b4762015de17fef49d7ff6202d292e9604deb508272ee7569f7fd5be3b2438da1dfecf0c26533ef86", + "0x97cf82f70128251944d79b8845506975405bd720e150d836205b048ff36ba8801eb74cdcc6425f28f6bc0acec0a81463", + "0x8c276c876eb88688957d1868bf3a1462375e608ff72b49870a5dac82cbf6584e00e3f36f236f732348a47502ccf9539d", + "0x964765f1a5c8a41d8025ddf56dc01b78424703d8a64a4e5539e477cb2445cb541c70127c561e717256d13f91a830ba83", + "0xa2aacd9e21b8c8efaf2319611addea1b9f41430aee42e7f2a640cc693aa395287cc8fdc2806b76b577d84fbd05378ead", + "0xab11eabbf5be4345a77323a3b75f9ee93b011fd2a9d0154e88183cafe47f82a7888666af16b40d3cb677c94bcc755ff7", + "0xa0bfe715a7af5a29b1b6148b8cbee585d2b49fa6ce59bcd173ea3bbc60d71a62f9da27ffcbbd5a6da75502112fe44d70", + "0x902e6cc38ee42245103d90b65028a471bc7a48b825599d361aa81d8c56e0fcf9fbe8d4c13802040d2cfb85b7e022eea1", + "0x8832e2b5014fdef4003bdbb87e3298fdbdbbe49673f6b66e2373f1cb2605f9c4af2cdf9bfd45d1993208681d29ee1c9d", + "0xa7d39d3fa1ec1e0c87730fa43d4900e91932d1cafb36c76b2934907becf7d15a1d84d7234591ad4c322b5a24673bba8d", + "0x836ed5f09d99624204aa3aa7ac601980fda223f3b4b96b4a8fb235c574a3545d518787c12f81bd5851987f2860d41886", + "0x94235e94445e6086f6e9331923262070a4c2ed930ec519eabb8a30133bd4fc6debb99185f4b668431fae1b485c5c81b7", + "0x9828ffe20b9405f117dac044159be2d3c6e2b50ecdd1651d6a73f7633e6e2a7ba3d783ae939973604446d3a1ef0fb20f", + "0x92f03dc365dfe9154743ca70e6dd2758f064e3286fc543cf8c50f68effdf7c554bd17b3507c6ff4127046d9bbb5522ef", + "0x91ed07df479d8eb3d31292a0e987672a7f3d45ecafe72935b7abbc3f23493605134ce573f309e226c9efe830b6868220", + "0x93bee582661e6d6cefeff29002afc2f36dd2c13dbf33f0574c35b290ddc426170a5f7f196369ad592efcd72cfb6f8fc0", + "0x89a51467d966f48fed15dea5a12dda54d0015f69e2169b5e34f44c7b5a5d4c282d6f138116a0cd06a8476980e420f8d8", + "0xb8ccebc14b6679ba2399370848864f15f63512fd6139df7359b7b93e82c1007fd85137ecb0597294b46643e1a9e7ab5e", + "0x841fa301567fc57b2cd09508ce75326684e12bfb8add671dc208f579b2500b93d5b641e9f59bba798ed4ed1259757f7d", + "0xb3cb45c15eb00b4ccb7013299f761cb8fefc17adf6db50e9ecb8abe927a3bc7f28e359e64693813e078e1dac800ad55b", + "0x96e55d3b9f445f5679e34fa5425b3e87cb221cfbdd07f8353868c7f7f4ba388ee3841cb9a1d638583bc20d03a9d071f2", + "0xa7dee9377de740270c5b57cf86699004ba8dc2766af56b388b5cb0814ec71bb99ecf43ee3d82a552733854ecc7def0fe", + "0xb129dfff23b3c1c95ddb214c4711961fcb129efe2b6557ec9e116ada909593d0d2eec2c628434493393c58c52aa86847", + "0xaed2670e201cb3e38a8be3c86735a4d76255e1e5a4c67b91df6ed262d09c8d10b0a3891da3e6ab934058cc9a7178931b", + "0xb20b8921ae52e5b3c94fa3a8b46489044174f7b897779e7763d6eb419e808d76705b7e7ba5131576f425aa81b6b0de53", + "0xa7e45bbc3ba1bc36617291ba7663806e247f1b57a89e31520c64a90cbf8d426cac2e2f381338baf78c8f92fdbbcb7026", + "0xa99e651e73a507e9e663e2364fcc193ec77e8afdc08c2bed6ad864e49b537ec31e9114ee72291a7657899f2033a849e2", + "0xaf966033636c2e9e8280d173f556fe07f8b6940bbcf6b2df7e2165c30bea66cced2596f6c17ca7c1aa0e614174953ba9", + "0xb69ca7a79e3d55ef21e0ebdc6f0c4bd17182d30cf6290cccca7d2551c91c12b966020d8e40e4ee4179488c9809c03ae4", + "0xb981cd36244e035fef043f70b1d7188d7cd045b4de0581c459fc5730e10eb7f3d5893b54cc4243849c0855e4e621167a", + "0xb20fea858a36921b35a3051ce787b73f70fdecd3fef283c15a2eb1bffb1dcba5991eee4a047ce4e87802da923fd9457b", + "0xb040e6f2e56dc1860274c263d4045837456f74b354a679f6b5ea70919835ebe5d32bf1f519e218730096c98ff396dc9d", + "0x8d2dd60e702c923a7204b530e7d6c193c6f93ca648c4f7bb38f4edbeb0aaed84184213afafb8db6aeb9197c24364276c", + "0x95dfa7348709e43d71285b28a0bfad3ca805b6ed4ae99753e9f736c79d58a35a3a50b42760ccdd03eda50f6e59494968", + "0xb8585632a13f18c139a411bb2f02df809591834d127cd1ff081e26d0abfe0e3fbb54abea26538b25a0dcb4d7e969590e", + "0xb46ba47858a29c6d523c9982660949567666daf2582b93393a4802a9e077eedbc0d49d454731696bc8e46ca50c7caa40", + "0x84b756b901b98a4404e58d70f39f6ccac877146c866732ae65e7e82727448d1550343bf7cdff1bfd4ee1ed73793db255", + "0x83e5be888eaf877a2c755897410865f64a6d1169a8ccf0336092f3932abab915e542ab75a35ffe016042340d581ee987", + "0x8cb274fc39285aed451a7def72cfbf73168ee10be02affe355a2bf87cf361a81ad284e9334cf00c5bf99a13d9f75e116", + "0x91ff6220924b94ae13f50eeac16a159232e4f16a73fbd5c22c0e185cd1998403904d36bad203baa82b85819ee4a8ac10", + "0x87f46e08e09aea2ab37b55fc300689d9b58ff3e72f1cffe023386035888f714fac4673c7c5193d3f3f3c568c640694f0", + "0x835d7d84ca7641e1b15095830114aa6072fe12260d2202456cafe2308c22651af9ffbcf6b7e56af97167dd0c4e2a4cf2", + "0x91202183f79794f114fd9e3b9bd05553c0e8985919965101a57d97ef666b028863e6cea9735af016dc1864f1542dee51", + "0x81ab2b02a9b0a490a74ae615ddd4fe560734c1bfdde6b8dd13303c1481ba0e8ab14473535a93cfe4e824a0ab29445f8c", + "0x8a32d73f4fc006551d4e2c61eec6130355ec9b8c39a65c24ec1edc00e80155ca83a8ef2455e892521a3d47634d82a987", + "0xaf70d7b8f13bc90193cc1cfb0c400c4224cf10f1887848aa93e6380f7087782fc41a159926ab53c53eb95c2383b1a849", + "0x989bf42f9d357c51774f1c7c0f7c0c46a8cb7398a74497141c32685be098e38b4230ffe833a6d880ec391a35b1a747b6", + "0x94cb6715ee95700020c630b8c19e35f231de970219bd7e6ba7ced01899197da473b6c45cacfab0d652ddaf547b4ea58c", + "0xb12e3331f1f7d7458393a785e22e9a5e1d1daea521b4e78c0ee8ca59b41ade1735a29820e18f6afb2f2c3c56fecc16b6", + "0xad4b7cf654349d136fb41fb0dd65b588199f68b462b05f5c4e5c2b468bfaa6c26329033e3c3f7873dc8ace89cf873ea5", + "0xa3279969e1ab596df0559ffc5ac7a6dc849680354e01c3f4fd34c6413a3f9f046f89c1e1be0b315d8b6dfab3d23d5c14", + "0xac74cc5562836ed89d09a9ae6a3644c936d64bdda9e77659d9982f1be29541b03ef2723236d5465e398373ea19a4ccc6", + "0x98138ebce1af531dd8b631b3e74c84f0c700355a2a9bde31e5e51bb10c8bbd766559c63f6041f4002568803fe08438e0", + "0x9006445da131349fe5714e0777a4f82a82da343612589a0c1596393e8b6894ce1cf42784f95ff67a8384ffe1f1a4ad76", + "0x88502a84a85e4ce54cfed297b5d355867cc770a8ffd0714a6f23b1ab320a9903c6e42809e034bb67dbf94c4fc0d9c790", + "0xaa8b4bf123d1a6ccaa44b86be8f980005f2a0a388a76cb111b0e85cd072ef64167fb0c097c7b23c4bca64c0260f6cce0", + "0xad49eb35dfea9feabb513a78dd1152ad7eba22fbb02a80cefc494a7037699c8df81202dfec12acc1b9e33ad680cb72d2", + "0x8694da730231b29afd5196371ddcb15b4dcc499574bdd063f4864ab80749833ea38ab8b0ca1629a367fe378e87a60a86", + "0x8eca7b488e810c479e7e32e24b8afcd837f7df183fe4f621a0336b53a9ed77603c84bdc365d8be68179a32b71a1deb7e", + "0x8875cd3e23c7e1af55af1b091025a08255743984186770bcd43f30b4a58d175cfdf1984bad97a15e08dac2da27198c3d", + "0xabdafcf58ec72997e494d4714645f40d09dcd0fbd0733e640eca44eeea67c25bb0c270299c459991f2fae59d13b4f4d5", + "0x8f040970141e61489284f3efd907705eae6ec757fe8e1d284eac123d313e9ac1e8dc14ae3f04d281e1effc49d5d2f51d", + "0xa7ff115f0d2dbf66c0e8770b3d05157b37357b9e33e9a447f0f3fa9da69ad04e371fd1e4848cfb9e8d05e3165bd969d8", + "0xa39b1a8c39d317fcc97bf6c396e6ed4a85640aeeadbf45166bd02bc3bdfb6266509159c03afd492e642384c635b824c0", + "0xa2e1b90f3dd2d0038eaa5be52127844ccf35d997143179d95ffd3749c0896398b130094d01eb1bb31ffe80ef34b42b48", + "0xa2bbe31f89b0c3c375ffaf63c8b7831860a921d5e388eb7907dbf61f2601ea40db86bb3952ecaa26a5eca4317a848ff9", + "0x87d885bb0f2ce04b40ce94d2557c15f1698dc652e938f9a2d69a73ccf4899e08eafa1a59a20cae92823795f5b94f04b9", + "0x8f7746370f8a24a2889d351f3e36b8a7d60e75e50e8f5abeea7dafc75441e95915721654e61ceac51bb6f112780d352c", + "0xa7272847526ed3d9e0d0fea1d8685b07b5b908971490bf8a46748c8b1783c629b8644feb5bac772ae615daae383d5e72", + "0x978c9aa2996d8bd6fda7e0393fa8b38747f8f99712427705c00f6e9a12c36f8d8b4cedb03fcb9867155cbddb5200e6e1", + "0xa4dec4a2354b2b32434c5bcdc380bf84580c6f9940f94dc0498a5bfe89c675a0921e66b807a3d859a6059a464cb2a9ac", + "0x99459ddecc7abce437f68722dae556d8ffaf8ed974f459e52e6d4a64f176caa4d42c2f2ec57e8a5b5f2034638e8acb0a", + "0x928c68c0c9213fe6258ab5bb0c693d97203d15da359784de7824dec143212da57d062a1fc70a79172cee31adc7aff382", + "0xaad3f318f1622ea87e12541dfd982d71629b8f1ded4c301f9f6b6af9432716ad057773c33bdaa6f15dc151b0ee4505ea", + "0x8eb8e978f149a983fd6ad01773f9aacf57bd0cc622d8a301e404184b37e610123dd081faeda571a0ab1f149a3960af10", + "0x851e7191d7b94bd422bcece5b92609fc1b1c8556229bc53e32963b2d2fd1cacd8ce5da9040b599eca6e610540f8a7987", + "0x9414157fe9d50e5a0b5a7397417681bcb3a651eec1cab63f2a88d5df68ab1fef6e4c1d7ba657cbaf241a7cb790297633", + "0xb5cb2dafdc5408959780754a58b2da55b2a9136672ebca42f34da4e329ddc89360e7218cde3efdbf784ddb390deacc57", + "0xac6b70f65503a8e94b773fda3e72615745824930114fe72b6d833484285462392617c1b2eea4a250fedbee88f503f3ba", + "0xb0829a5312f9ac6c06fddee2f835a3452fe994f6d42c9edfc390d7d5b3240ca544433b544cbbddd6516b38a6d5d7c21d", + "0x95f8e2c59905957e34d53be3d6fb85732f834e2cb9ab4c333fea2f502452a87ccd035fc9075d7c0bd8530bb0a0c96527", + "0xb93f279b7045f2d97c674495f6e69a3e352f32f43cc60300193b936c2850b2805c15457251f7e3f633f435cb2b60405c", + "0x915abf16cba1a0b655b92a8a70c03e7fb306b86f3bbfb66967ca63e64c003b59c7a5953675efa4fa0bce9bed536b6700", + "0xac2047f50a319d09df1ec44d71afdcec5ac3bd2765dc98aba347734aa780863545df9f6d71214d443e3f37edc0dae45a", + "0xad49c74ddb24c8a26b14ec08bc807313c77c5967fbb36237f55994d7511bbac8d7e7b9b8ec53eb1b3b066989f078dbd9", + "0x961483105f605e959213fe9e8a52b76dac62d7efd2319ec71fc4e92d68fbe44cd2f65d7adefb2eb64d591b91648b8085", + "0xb67fcafc97d8df2b3075bbff7b3d7471dbf1f3048f309e55d5e2c5bcbc7a73aebcb0697859be9f387cbc7ce98041e154", + "0x8da70ac16468cab6066992389cb37c79ff5e0babbe67d76878aef9408b9597a3dc2eb5de87428bc761a0d78957b0eb28", + "0xaec0ce89770d299b631f15ae12f94b1e1014ac57d38fcf037c2c7712d770d074affa06e97c60691bad8733874b6ad2ed", + "0x8b702c85fa4c915a09fc86507f44d7aeda0993b77af87780d70cc98d580c6e996b64b7c16cdb4dd4562cb0f75da36ee7", + "0xaaeb43aa472aac2253e211fd1066c3a5422ea041cef20168702d0618a1a742a44f7fb30a76677640fea1a24e7fae1996", + "0xa8820e92825d6e02b9b4ad5ebc86161d3244cddd3d244333ba1576b6ae10948145b68d9e926bf6b7a2c25dab4cf43f3e", + "0x8ffdae28a1f1d15d7ffa473628a66ee9a739073f59ba781248286b39cb8f7255f66d62337064246713cbb5017e615174", + "0xadfc5dd142b7911326d8424881d5d92006f3b17de4cce91674d6ea37f00fbb266c791ac13f6c7a0f61d04f2a952e6a04", + "0x87f98982444bf661f539bec73a10256f079a4baa88a1cea0351ae3de929e1c500485b2d1b5d933063cd7d9123d5050e4", + "0x8f217ba4dd404c5ee384f0c9a126686db001ff0344c01c82174c5e5ef89d1a241b146008c534b13a0da6c8afe7450fbb", + "0xafc85476dddaf1cbb4ba8b22186789f3818c7964f9f613e55010278800cd95422702248bdf9c73760702ef24854795ec", + "0xa59e0f6ac2ccdfbd01f002008034390c0ea78716f5e0de4e474e3558755705c9c7afb6e3c5c4370e7bbc85958a9c7a63", + "0x97c0695c58d792ec31d9b86d3b2fc1382f0855057b24d5f6a54c41f76f9e2f52882cadc89a8b2f121530e7f1393faa95", + "0x8e49112de0b2649c08a96cf737af68fa8055f1af594846a2d0534c94df6f926f200405edaa6e6ac9db7e380707a2571d", + "0x99a1bd83a7ac5f8d77ddf044c80ebfc5745b998714696d67b94d185c97e9d6db989bacac646d9def463127a8b2febc00", + "0xaba80725f9f9f7abe10760eca73ba427ca8df864a157122eb9af828a05b0199de3add02019a297750bdab5380e505c58", + "0xae18f62573275c1eb268f74c5e54e8958547f9e7d1d36a05b084eb53e5704fafe2200b8aff95cc7e9af5be2391c42b7c", + "0x908b8031d09d22b2aefeaa876a998e0a97c7a1070aad9e9c97836cc5aa6d2d5ef94230e1222074837b5e21b4e6490f01", + "0xb3132282e8b41ca6789ec5c43c1fecf3a65b8eefbc2f3d10f746a843b9ba4ce6db664678e75e424f7b11a00c1440de15", + "0xa1eb49440cc106ebc09cf198c93e8070271eb5a936d31c04858a2b311a037350100c7957d5545c9653f396aa968b91f4", + "0x81df6ad1bdd5eee4cc2f94318467b8602d15cc1be2b48b09ade12cc46ee05cbaaf77a20397e5015030b1f1db5dd9dac0", + "0x87236c68a2a93c8442d15d7f1d1dc01d1fd123439c183e1d843f4ddd2bcf638c128f66f1ef9b710e5d1f64a52726007a", + "0x84f2e7f85563bb2f61b10a712c7605d63f79af5be0dba056814fd3efebc20e9c53227c56577b72c68d185571b775eff6", + "0xa36d4ae06688ece2927aeb2c7f058a3cd2aa1de1601282d4e688e1d76ef20728b892928deda2314eba41675eba3912f1", + "0xb8326dcbcdcfce017b263c456c47692fb476c4225c95981666fff0b7d4522fc23b7f12273f0f47cf0442662124e6648f", + "0x84c66463ab277cda2cc7007d0509269e89cdd41c5e0d3773a92615f0fc5da63811186b05d7a11088048a5d4834a7e0df", + "0xb20d3571d970712ef4699b0e7034fd269c361f53e1572e2ea2676b4245e992d43b8b5931a801439a44d977a988cc360b", + "0x94dba6007e6d4998ca1eb84aa8e2a7e9f5c164b9d80df2825f2208ce5640a05aacac2e4f08918268990f43ae1ccab69a", + "0xa1c25f0b3ef9d1982153207570d9ce8d692e1b6963b509958dc4d9bcd80074bb221c46804a6d9a29e76149cc7787c282", + "0x8857748fcdab1199fc96084323a81d3bd8b5a7f0b1abc5bc3b5252a19268344e2e7d2d086c90fc9b5fa4b92feedb93a4", + "0x8b9c1d841447354b6c086549e4d1d435ab64c13933488c34bc30f0f6eb36c5c5b838b7b6bb018542247edd1ada091045", + "0x8f5b655416da0e719a204fc567e93792c301acb4374cf7bbabc6ce51dbeaaadfd75c2db0e16ce073ab8e91fd3d7ea9d4", + "0x90f2846b19be46a75c5cd0cafefcf9192e6fd80c479e8d6320c4b8d8d7d96703c9e77ff31a67afa9858e6b7bde1f7cce", + "0xa53e383947fd98aa1a55ac956214b46b20a52758461e8ba41341a23a835ebb713038bf048edb1202bbfd0b56a96bf292", + "0x9542d7debbcfb9cda6fa279c699a7b655c03b9a9b456a5d3cfc41a826c94eafa43e01155a29e39ff0bcd965f4c0c512d", + "0xa43792864ec5fc549f7afc02622454afc0e425c310c4039ba615067243ebb26a4c7ebfd19bd4d57ff412a4bb2a7958a0", + "0xb85123950e30c048465bf32365d24a5d4b21fffc6183cdbf71643a07b87463989b72dd9a6a47f134856f704909a6b38f", + "0x944ea689aec1376f855c0bc9c51378ad06ff758a2c075b95a60b535b88b36eca0be11e4edb5152e98cb2137d6e749f27", + "0xa6bef52cda22325e4c62d323e2a0e3fa91c5552fcfce951edfd52ad6f652bfdcc2341f1cd349e6b5d447924dc569bfe2", + "0xb56bff8ffe981bfcb30791836da10b87f2ccbe17ed969e7f7a650af07d27ae0223805b1264d985148208483be50578a6", + "0x8b209cac898dd580c82d854a553e2517497ad1a4cd198e1360b8b50639b380aee70ee4b87625d9b2278228ff644cd25c", + "0x877cce233fec74c7158b3c5bf108365e98238418b8a71f058f1aca44a0fd3a1021e3e9025bd11fe244d9fe0f5034ce7f", + "0xb1b871aeedb03d6f6accc99816b89f5958178738d8d8cd9717527d04363c80fdb5f6848122ae19fdbc450cfa11e753c8", + "0x858aca51b9e5b0a724e88688d5124eb24c9faf01a3d465e74d31de6da315f311143f22f60201ea09f62c92f61f09d889", + "0x8521d409615dfc8c8289e00f6aaa6297c2c4e1439b25952afd76aac641b81c70b9cef07cd58c1c0198382bddd2bd8544", + "0x88647c3e41666b88acca42505f1f5da226937e0522b538fe0cebb724e9a99730ca2522989e94a96cac94109aef675c0f", + "0xb417fdaf719caf38854e89ce52031b30ce61a632e6c3135adec9002280e022d82ab0ea4ac5ebdb21f1f0169e4c37bcda", + "0x9367a6feb5e23ea2eab8ddd5e7bdf32b4d2419fad1c71a1ed327b77362d8942dad971a1c2e6f7073885149cdf0a0c339", + "0xa71c5c08d50c57d094d6a4f02e97d3799bada92f238ffc07bd223bbe8379507b7310d20b28f5bbbf331e5e153515e491", + "0x9630a9a3bcb044b51299c4d3d3388a4ff47308dd27be3229601985478c0f6b55faa7e20815d8694f910611396a9d0d45", + "0xb0bfaf56a5aa59b48960aa7c1617e832e65c823523fb2a5cd44ba606800501cf873e8db1d0dda64065285743dc40786e" + ], + "g1_lagrange": [ + "0xa0413c0dcafec6dbc9f47d66785cf1e8c981044f7d13cfe3e4fcbb71b5408dfde6312493cb3c1d30516cb3ca88c03654", + "0x8b997fb25730d661918371bb41f2a6e899cac23f04fc5365800b75433c0a953250e15e7a98fb5ca5cc56a8cd34c20c57", + "0x83302852db89424d5699f3f157e79e91dc1380f8d5895c5a772bb4ea3a5928e7c26c07db6775203ce33e62a114adaa99", + "0xa759c48b7e4a685e735c01e5aa6ef9c248705001f470f9ad856cd87806983e917a8742a3bd5ee27db8d76080269b7c83", + "0x967f8dc45ebc3be14c8705f43249a30ff48e96205fb02ae28daeab47b72eb3f45df0625928582aa1eb4368381c33e127", + "0xa418eb1e9fb84cb32b370610f56f3cb470706a40ac5a47c411c464299c45c91f25b63ae3fcd623172aa0f273c0526c13", + "0x8f44e3f0387293bc7931e978165abbaed08f53acd72a0a23ac85f6da0091196b886233bcee5b4a194db02f3d5a9b3f78", + "0x97173434b336be73c89412a6d70d416e170ea355bf1956c32d464090b107c090ef2d4e1a467a5632fbc332eeb679bf2d", + "0xa24052ad8d55ad04bc5d951f78e14213435681594110fd18173482609d5019105b8045182d53ffce4fc29fc8810516c1", + "0xb950768136b260277590b5bec3f56bbc2f7a8bc383d44ce8600e85bf8cf19f479898bcc999d96dfbd2001ede01d94949", + "0x92ab8077871037bd3b57b95cbb9fb10eb11efde9191690dcac655356986fd02841d8fdb25396faa0feadfe3f50baf56d", + "0xa79b096dff98038ac30f91112dd14b78f8ad428268af36d20c292e2b3b6d9ed4fb28480bb04e465071cc67d05786b6d1", + "0xb9ff71461328f370ce68bf591aa7fb13027044f42a575517f3319e2be4aa4843fa281e756d0aa5645428d6dfa857cef2", + "0x8d765808c00b3543ff182e2d159c38ae174b12d1314da88ea08e13bd9d1c37184cb515e6bf6420531b5d41767987d7ce", + "0xb8c9a837d20c3b53e6f578e4a257bb7ef8fc43178614ec2a154915b267ad2be135981d01ed2ee1b5fbd9d9bb27f0800a", + "0xa9773d92cf23f65f98ef68f6cf95c72b53d0683af2f9bf886bb9036e4a38184b1131b26fd24397910b494fbef856f3aa", + "0xb41ebe38962d112da4a01bf101cb248d808fbd50aaf749fc7c151cf332032eb3e3bdbd716db899724b734d392f26c412", + "0x90fbb030167fb47dcc13d604a726c0339418567c1d287d1d87423fa0cb92eec3455fbb46bcbe2e697144a2d3972142e4", + "0xb11d298bd167464b35fb923520d14832bd9ed50ed841bf6d7618424fd6f3699190af21759e351b89142d355952149da1", + "0x8bc36066f69dc89f7c4d1e58d67497675050c6aa002244cebd9fc957ec5e364c46bab4735ea3db02b73b3ca43c96e019", + "0xab7ab92c5d4d773068e485aa5831941ebd63db7118674ca38089635f3b4186833af2455a6fb9ed2b745df53b3ce96727", + "0xaf191ca3089892cb943cd97cf11a51f38e38bd9be50844a4e8da99f27e305e876f9ed4ab0628e8ae3939066b7d34a15f", + "0xa3204c1747feabc2c11339a542195e7cb6628fd3964f846e71e2e3f2d6bb379a5e51700682ea1844eba12756adb13216", + "0x903a29883846b7c50c15968b20e30c471aeac07b872c40a4d19eb1a42da18b649d5bbfde4b4cf6225d215a461b0deb6d", + "0x8e6e9c15ffbf1e16e5865a5fef7ed751dc81957a9757b535cb38b649e1098cda25d42381dc4f776778573cdf90c3e6e0", + "0xa8f6dd26100b512a8c96c52e00715c4b2cb9ac457f17aed8ffe1cf1ea524068fe5a1ddf218149845fc1417b789ecfc98", + "0xa5b0ffc819451ea639cfd1c18cbc9365cc79368d3b2e736c0ae54eba2f0801e6eb0ee14a5f373f4a70ca463bdb696c09", + "0x879f91ccd56a1b9736fbfd20d8747354da743fb121f0e308a0d298ff0d9344431890e41da66b5009af3f442c636b4f43", + "0x81bf3a2d9755e206b515a508ac4d1109bf933c282a46a4ae4a1b4cb4a94e1d23642fad6bd452428845afa155742ade7e", + "0x8de778d4742f945df40004964e165592f9c6b1946263adcdd5a88b00244bda46c7bb49098c8eb6b3d97a0dd46148a8ca", + "0xb7a57b21d13121907ee28c5c1f80ee2e3e83a3135a8101e933cf57171209a96173ff5037f5af606e9fd6d066de6ed693", + "0xb0877d1963fd9200414a38753dffd9f23a10eb3198912790d7eddbc9f6b477019d52ddd4ebdcb9f60818db076938a5a9", + "0x88da2d7a6611bc16adc55fc1c377480c828aba4496c645e3efe0e1a67f333c05a0307f7f1d2df8ac013602c655c6e209", + "0x95719eb02e8a9dede1a888c656a778b1c69b7716fbe3d1538fe8afd4a1bc972183c7d32aa7d6073376f7701df80116d8", + "0x8e8a1ca971f2444b35af3376e85dccda3abb8e8e11d095d0a4c37628dfe5d3e043a377c3de68289ef142e4308e9941a0", + "0xb720caaff02f6d798ac84c4f527203e823ff685869e3943c979e388e1c34c3f77f5c242c6daa7e3b30e511aab917b866", + "0x86040d55809afeec10e315d1ad950d269d37cfee8c144cd8dd4126459e3b15a53b3e68df5981df3c2346d23c7b4baaf4", + "0x82d8cabf13ab853db0377504f0aec00dba3a5cd3119787e8ad378ddf2c40b022ecfc67c642b7acc8c1e3dd03ab50993e", + "0xb8d873927936719d2484cd03a6687d65697e17dcf4f0d5aed6f5e4750f52ef2133d4645894e7ebfc4ef6ce6788d404c8", + "0xb1235594dbb15b674a419ff2b2deb644ad2a93791ca05af402823f87114483d6aa1689b7a9bea0f547ad12fe270e4344", + "0xa53fda86571b0651f5affb74312551a082fffc0385cfd24c1d779985b72a5b1cf7c78b42b4f7e51e77055f8e5e915b00", + "0xb579adcfd9c6ef916a5a999e77a0cb21d378c4ea67e13b7c58709d5da23a56c2e54218691fc4ac39a4a3d74f88cc31f7", + "0xab79e584011713e8a2f583e483a91a0c2a40771b77d91475825b5acbea82db4262132901cb3e4a108c46d7c9ee217a4e", + "0xa0fe58ea9eb982d7654c8aaf9366230578fc1362f6faae0594f8b9e659bcb405dff4aac0c7888bbe07f614ecf0d800a6", + "0x867e50e74281f28ecd4925560e2e7a6f8911b135557b688254623acce0dbc41e23ac3e706a184a45d54c586edc416eb0", + "0x89f81b61adda20ea9d0b387a36d0ab073dc7c7cbff518501962038be19867042f11fcc7ff78096e5d3b68c6d8dc04d9b", + "0xa58ee91bb556d43cf01f1398c5811f76dc0f11efdd569eed9ef178b3b0715e122060ec8f945b4dbf6eebfa2b90af6fa6", + "0xac460be540f4c840def2eef19fc754a9af34608d107cbadb53334cf194cc91138d53b9538fcd0ec970b5d4aa455b224a", + "0xb09b91f929de52c09d48ca0893be6eb44e2f5210a6c394689dc1f7729d4be4e11d0474b178e80cea8c2ac0d081f0e811", + "0x8d37a442a76b06a02a4e64c2504aea72c8b9b020ab7bcc94580fe2b9603c7c50d7b1e9d70d2a7daea19c68667e8f8c31", + "0xa9838d4c4e3f3a0075a952cf7dd623307ec633fcc81a7cf9e52e66c31780de33dbb3d74c320dc7f0a4b72f7a49949515", + "0xa44766b6251af458fe4f5f9ed1e02950f35703520b8656f09fc42d9a2d38a700c11a7c8a0436ac2e5e9f053d0bb8ff91", + "0xad78d9481c840f5202546bea0d13c776826feb8b1b7c72e83d99a947622f0bf38a4208551c4c41beb1270d7792075457", + "0xb619ffa8733b470039451e224b777845021e8dc1125f247a4ff2476cc774657d0ff9c5279da841fc1236047de9d81c60", + "0xaf760b0a30a1d6af3bc5cd6686f396bd41779aeeb6e0d70a09349bd5da17ca2e7965afc5c8ec22744198fbe3f02fb331", + "0xa0cc209abdb768b589fcb7b376b6e1cac07743288c95a1cf1a0354b47f0cf91fca78a75c1fcafa6f5926d6c379116608", + "0x864add673c89c41c754eeb3cd8dcff5cdde1d739fce65c30e474a082bb5d813cba6412e61154ce88fdb6c12c5d9be35b", + "0xb091443b0ce279327dc37cb484e9a5b69b257a714ce21895d67539172f95ffa326903747b64a3649e99aea7bb10d03f7", + "0xa8c452b8c4ca8e0a61942a8e08e28f17fb0ef4c5b018b4e6d1a64038280afa2bf1169202f05f14af24a06ca72f448ccd", + "0xa23c24721d18bc48d5dcf70effcbef89a7ae24e67158d70ae1d8169ee75d9a051d34b14e9cf06488bac324fe58549f26", + "0x92a730e30eb5f3231feb85f6720489dbb1afd42c43f05a1610c6b3c67bb949ec8fde507e924498f4ffc646f7b07d9123", + "0x8dbe5abf4031ec9ba6bb06d1a47dd1121fb9e03b652804069250967fd5e9577d0039e233441b7f837a7c9d67ba18c28e", + "0xaa456bcfef6a21bb88181482b279df260297b3778e84594ebddbdf337e85d9e3d46ca1d0b516622fb0b103df8ec519b7", + "0xa3b31ae621bd210a2b767e0e6f22eb28fe3c4943498a7e91753225426168b9a26da0e02f1dc5264da53a5ad240d9f51b", + "0xaa8d66857127e6e71874ce2202923385a7d2818b84cb73a6c42d71afe70972a70c6bdd2aad1a6e8c5e4ca728382a8ea8", + "0xac7e8e7a82f439127a5e40558d90d17990f8229852d21c13d753c2e97facf077cf59582b603984c3dd3faebd80aff4f5", + "0x93a8bcf4159f455d1baa73d2ef2450dcd4100420de84169bbe28b8b7a5d1746273f870091a87a057e834f754f34204b1", + "0x89d0ebb287c3613cdcae7f5acc43f17f09c0213fc40c074660120b755d664109ffb9902ed981ede79e018ddb0c845698", + "0xa87ccbfad431406aadbee878d9cf7d91b13649d5f7e19938b7dfd32645a43b114eef64ff3a13201398bd9b0337832e5a", + "0x833c51d0d0048f70c3eefb4e70e4ff66d0809c41838e8d2c21c288dd3ae9d9dfaf26d1742bf4976dab83a2b381677011", + "0x8bcd6b1c3b02fffead432e8b1680bad0a1ac5a712d4225e220690ee18df3e7406e2769e1f309e2e803b850bc96f0e768", + "0xb61e3dbd88aaf4ff1401521781e2eea9ef8b66d1fac5387c83b1da9e65c2aa2a56c262dea9eceeb4ad86c90211672db0", + "0x866d3090db944ecf190dd0651abf67659caafd31ae861bab9992c1e3915cb0952da7c561cc7e203560a610f48fae633b", + "0xa5e8971543c14274a8dc892b0be188c1b4fbc75c692ed29f166e0ea80874bc5520c2791342b7c1d2fb5dd454b03b8a5b", + "0x8f2f9fc50471bae9ea87487ebd1bc8576ef844cc42d606af5c4c0969670fdf2189afd643e4de3145864e7773d215f37f", + "0xb1bb0f2527db6d51f42b9224383c0f96048bbc03d469bf01fe1383173ef8b1cc9455d9dd8ba04d46057f46949bfc92b5", + "0xaa7c99d906b4d7922296cfe2520473fc50137c03d68b7865c5bfb8adbc316b1034310ec4b5670c47295f4a80fb8d61e9", + "0xa5d1da4d6aba555919df44cbaa8ff79378a1c9e2cfdfbf9d39c63a4a00f284c5a5724e28ecbc2d9dba27fe4ee5018bd5", + "0xa8db53224f70af4d991b9aae4ffe92d2aa5b618ad9137784b55843e9f16cefbfd25ada355d308e9bbf55f6d2f7976fb3", + "0xb6536c4232bb20e22af1a8bb12de76d5fec2ad9a3b48af1f38fa67e0f8504ef60f305a73d19385095bb6a9603fe29889", + "0x87f7e371a1817a63d6838a8cf4ab3a8473d19ce0d4f40fd013c03d5ddd5f4985df2956531cc9f187928ef54c68f4f9a9", + "0xae13530b1dbc5e4dced9d909ea61286ec09e25c12f37a1ed2f309b0eb99863d236c3b25ed3484acc8c076ad2fa8cd430", + "0x98928d850247c6f7606190e687d5c94a627550198dbdbea0161ef9515eacdb1a0f195cae3bb293112179082daccf8b35", + "0x918528bb8e6a055ad4db6230d3a405e9e55866da15c4721f5ddd1f1f37962d4904aad7a419218fe6d906fe191a991806", + "0xb71e31a06afe065773dd3f4a6e9ef81c3292e27a3b7fdfdd452d03e05af3b6dd654c355f7516b2a93553360c6681a73a", + "0x8870b83ab78a98820866f91ac643af9f3ff792a2b7fda34185a9456a63abdce42bfe8ad4dc67f08a6392f250d4062df4", + "0x91eea1b668e52f7a7a5087fabf1cab803b0316f78d9fff469fbfde2162f660c250e4336a9eea4cb0450bd30ac067bc8b", + "0x8b74990946de7b72a92147ceac1bd9d55999a8b576e8df68639e40ed5dc2062cfcd727903133de482b6dca19d0aaed82", + "0x8ebad537fece090ebbab662bdf2618e21ca30cf6329c50935e8346d1217dcbe3c1fe1ea28efca369c6003ce0a94703c1", + "0xa8640479556fb59ebd1c40c5f368fbd960932fdbb782665e4a0e24e2bdb598fc0164ce8c0726d7759cfc59e60a62e182", + "0xa9a52a6bf98ee4d749f6d38be2c60a6d54b64d5cbe4e67266633dc096cf28c97fe998596707d31968cbe2064b72256bf", + "0x847953c48a4ce6032780e9b39d0ed4384e0be202c2bbe2dfda3910f5d87aa5cd3c2ffbfcfae4dddce16d6ab657599b95", + "0xb6f6e1485d3ec2a06abaecd23028b200b2e4a0096c16144d07403e1720ff8f9ba9d919016b5eb8dc5103880a7a77a1d3", + "0x98dfc2065b1622f596dbe27131ea60bef7a193b12922cecb27f8c571404f483014f8014572e86ae2e341ab738e4887ef", + "0xacb0d205566bacc87bbe2e25d10793f63f7a1f27fd9e58f4f653ceae3ffeba511eaf658e068fad289eeb28f9edbeb35b", + "0xae4411ed5b263673cee894c11fe4abc72a4bf642d94022a5c0f3369380fcdfc1c21e277f2902972252503f91ada3029a", + "0xac4a7a27ba390a75d0a247d93d4a8ef1f0485f8d373a4af4e1139369ec274b91b3464d9738eeaceb19cd6f509e2f8262", + "0x87379c3bf231fdafcf6472a79e9e55a938d851d4dd662ab6e0d95fd47a478ed99e2ad1e6e39be3c0fc4f6d996a7dd833", + "0x81316904b035a8bcc2041199a789a2e6879486ba9fddcba0a82c745cc8dd8374a39e523b91792170cd30be7aa3005b85", + "0xb8206809c6cd027ed019f472581b45f7e12288f89047928ba32b4856b6560ad30395830d71e5e30c556f6f182b1fe690", + "0x88d76c028f534a62e019b4a52967bb8642ede6becfa3807be68fdd36d366fc84a4ac8dc176e80a68bc59eb62caf5dff9", + "0x8c3b8be685b0f8aad131ee7544d0e12f223f08a6f8edaf464b385ac644e0ddc9eff7cc7cb5c1b50ab5d71ea0f41d2213", + "0x8d91410e004f76c50fdc05784157b4d839cb5090022c629c7c97a5e0c3536eeafee17a527b54b1165c3cd81774bb54ce", + "0xb25c2863bc28ec5281ce800ddf91a7e1a53f4c6d5da1e6c86ef4616e93bcf55ed49e297216d01379f5c6e7b3c1e46728", + "0x865f7b09ac3ca03f20be90c48f6975dd2588838c2536c7a3532a6aa5187ed0b709cd03d91ff4048061c10d0aa72b69ce", + "0xb3f7477c90c11596eb4f8bbf34adbcb832638c4ff3cdd090d4d477ee50472ac9ddaf5be9ad7eca3f148960d362bbd098", + "0x8db35fd53fca04faecd1c76a8227160b3ab46ac1af070f2492445a19d8ff7c25bbaef6c9fa0c8c088444561e9f7e4eb2", + "0xa478b6e9d058a2e01d2fc053b739092e113c23a6a2770a16afbef044a3709a9e32f425ace9ba7981325f02667c3f9609", + "0x98caa6bd38916c08cf221722a675a4f7577f33452623de801d2b3429595f988090907a7e99960fff7c076d6d8e877b31", + "0xb79aaaacefc49c3038a14d2ac468cfec8c2161e88bdae91798d63552cdbe39e0e02f9225717436b9b8a40a022c633c6e", + "0x845a31006c680ee6a0cc41d3dc6c0c95d833fcf426f2e7c573fa15b2c4c641fbd6fe5ebb0e23720cc3467d6ee1d80dc4", + "0xa1bc287e272cf8b74dbf6405b3a5190883195806aa351f1dc8e525aa342283f0a35ff687e3b434324dedee74946dd185", + "0xa4fd2dc8db75d3783a020856e2b3aa266dc6926e84f5c491ef739a3bddd46dc8e9e0fc1177937839ef1b18d062ffbb9e", + "0xacbf0d3c697f57c202bb8c5dc4f3fc341b8fc509a455d44bd86acc67cad2a04495d5537bcd3e98680185e8aa286f2587", + "0xa5caf423a917352e1b8e844f5968a6da4fdeae467d10c6f4bbd82b5eea46a660b82d2f5440d3641c717b2c3c9ed0be52", + "0x8a39d763c08b926599ab1233219c49c825368fad14d9afc7c0c039224d37c00d8743293fd21645bf0b91eaf579a99867", + "0xb2b53a496def0ba06e80b28f36530fbe0fb5d70a601a2f10722e59abee529369c1ae8fd0f2db9184dd4a2519bb832d94", + "0xa73980fcef053f1b60ebbb5d78ba6332a475e0b96a0c724741a3abf3b59dd344772527f07203cf4c9cb5155ebed81fa0", + "0xa070d20acce42518ece322c9db096f16aed620303a39d8d5735a0df6e70fbeceb940e8d9f5cc38f3314b2240394ec47b", + "0xa50cf591f522f19ca337b73089557f75929d9f645f3e57d4f241e14cdd1ea3fb48d84bcf05e4f0377afbb789fbdb5d20", + "0x82a5ffce451096aca8eeb0cd2ae9d83db3ed76da3f531a80d9a70a346359bf05d74863ce6a7c848522b526156a5e20cd", + "0x88e0e84d358cbb93755a906f329db1537c3894845f32b9b0b691c29cbb455373d9452fadd1e77e20a623f6eaf624de6f", + "0xaa07ac7b84a6d6838826e0b9e350d8ec75e398a52e9824e6b0da6ae4010e5943fec4f00239e96433f291fef9d1d1e609", + "0xac8887bf39366034bc63f6cc5db0c26fd27307cbc3d6cce47894a8a019c22dd51322fb5096edc018227edfafc053a8f6", + "0xb7d26c26c5b33f77422191dca94977588ab1d4b9ce7d0e19c4a3b4cd1c25211b78c328dbf81e755e78cd7d1d622ad23e", + "0x99a676d5af49f0ba44047009298d8474cabf2d5bca1a76ba21eff7ee3c4691a102fdefea27bc948ccad8894a658abd02", + "0xb0d09a91909ab3620c183bdf1d53d43d39eb750dc7a722c661c3de3a1a5d383ad221f71bae374f8a71867505958a3f76", + "0x84681a883de8e4b93d68ac10e91899c2bbb815ce2de74bb48a11a6113b2a3f4df8aceabda1f5f67bc5aacac8c9da7221", + "0x9470259957780fa9b43521fab3644f555f5343281c72582b56d2efd11991d897b3b481cafa48681c5aeb80c9663b68f7", + "0xab1b29f7ece686e6fa968a4815da1d64f3579fed3bc92e1f3e51cd13a3c076b6cf695ed269d373300a62463dc98a4234", + "0x8ab415bfcd5f1061f7687597024c96dd9c7cb4942b5989379a7a3b5742f7d394337886317659cbeacaf030234a24f972", + "0xb9b524aad924f9acc63d002d617488f31b0016e0f0548f050cada285ce7491b74a125621638f19e9c96eabb091d945be", + "0x8c4c373e79415061837dd0def4f28a2d5d74d21cb13a76c9049ad678ca40228405ab0c3941df49249847ecdefc1a5b78", + "0xa8edf4710b5ab2929d3db6c1c0e3e242261bbaa8bcec56908ddadd7d2dad2dca9d6eb9de630b960b122ebeea41040421", + "0x8d66bb3b50b9df8f373163629f9221b3d4b6980a05ea81dc3741bfe9519cf3ebba7ab98e98390bae475e8ede5821bd5c", + "0x8d3c21bae7f0cfb97c56952bb22084b58e7bb718890935b73103f33adf5e4d99cd262f929c6eeab96209814f0dbae50a", + "0xa5c66cfab3d9ebf733c4af24bebc97070e7989fe3c73e79ac85fb0e4d40ae44fb571e0fad4ad72560e13ed453900d14f", + "0x9362e6b50b43dbefbc3254471372297b5dcce809cd3b60bf74a1268ab68bdb50e46e462cbd78f0d6c056330e982846af", + "0x854630d08e3f0243d570cc2e856234cb4c1a158d9c1883bf028a76525aaa34be897fe918d5f6da9764a3735fa9ebd24a", + "0x8c7d246985469ff252c3f4df6c7c9196fc79f05c1c66a609d84725c78001d0837c7a7049394ba5cf7e863e2d58af8417", + "0xae050271e01b528925302e71903f785b782f7bf4e4e7a7f537140219bc352dc7540c657ed03d3a297ad36798ecdb98cd", + "0x8d2ae9179fcf2b0c69850554580b52c1f4a5bd865af5f3028f222f4acad9c1ad69a8ef6c7dc7b03715ee5c506b74325e", + "0xb8ef8de6ce6369a8851cd36db0ccf00a85077e816c14c4e601f533330af9e3acf0743a95d28962ed8bfcfc2520ef3cfe", + "0xa6ecad6fdfb851b40356a8b1060f38235407a0f2706e7b8bb4a13465ca3f81d4f5b99466ac2565c60af15f022d26732e", + "0x819ff14cdea3ab89d98e133cd2d0379361e2e2c67ad94eeddcdb9232efd509f51d12f4f03ebd4dd953bd262a886281f7", + "0x8561cd0f7a6dbcddd83fcd7f472d7dbcba95b2d4fb98276f48fccf69f76d284e626d7e41314b633352df8e6333fd52a1", + "0xb42557ccce32d9a894d538c48712cb3e212d06ac05cd5e0527ccd2db1078ee6ae399bf6a601ffdab1f5913d35fc0b20c", + "0x89b4008d767aad3c6f93c349d3b956e28307311a5b1cec237e8d74bb0dee7e972c24f347fd56afd915a2342bd7bc32f0", + "0x877487384b207e53f5492f4e36c832c2227f92d1bb60542cfeb35e025a4a7afc2b885fae2528b33b40ab09510398f83e", + "0x8c411050b63c9053dd0cd81dacb48753c3d7f162028098e024d17cd6348482703a69df31ad6256e3d25a8bbf7783de39", + "0xa8506b54a88d17ac10fb1b0d1fe4aa40eae7553a064863d7f6b52ccc4236dd4b82d01dca6ba87da9a239e3069ba879fb", + "0xb1a24caef9df64750c1350789bb8d8a0db0f39474a1c74ea9ba064b1516db6923f00af8d57c632d58844fb8786c3d47a", + "0x959d6e255f212b0708c58a2f75cb1fe932248c9d93424612c1b8d1e640149656059737e4db2139afd5556bcdacf3eda2", + "0x84525af21a8d78748680b6535bbc9dc2f0cf9a1d1740d12f382f6ecb2e73811d6c1da2ad9956070b1a617c61fcff9fe5", + "0xb74417d84597a485d0a8e1be07bf78f17ebb2e7b3521b748f73935b9afbbd82f34b710fb7749e7d4ab55b0c7f9de127d", + "0xa4a9aecb19a6bab167af96d8b9d9aa5308eab19e6bfb78f5a580f9bf89bdf250a7b52a09b75f715d651cb73febd08e84", + "0x9777b30be2c5ffe7d29cc2803a562a32fb43b59d8c3f05a707ab60ec05b28293716230a7d264d7cd9dd358fc031cc13e", + "0x95dce7a3d4f23ac0050c510999f5fbf8042f771e8f8f94192e17bcbfa213470802ebdbe33a876cb621cf42e275cbfc8b", + "0xb0b963ebcbbee847ab8ae740478544350b3ac7e86887e4dfb2299ee5096247cd2b03c1de74c774d9bde94ae2ee2dcd59", + "0xa4ab20bafa316030264e13f7ef5891a2c3b29ab62e1668fcb5881f50a9acac6adbe3d706c07e62f2539715db768f6c43", + "0x901478a297669d608e406fe4989be75264b6c8be12169aa9e0ad5234f459ca377f78484ffd2099a2fe2db5e457826427", + "0x88c76e5c250810c057004a03408b85cd918e0c8903dc55a0dd8bb9b4fc2b25c87f9b8cf5943eb19fbbe99d36490050c5", + "0x91607322bbad4a4f03fc0012d0821eff5f8c516fda45d1ec1133bface6f858bf04b25547be24159cab931a7aa08344d4", + "0x843203e07fce3c6c81f84bc6dc5fb5e9d1c50c8811ace522dc66e8658433a0ef9784c947e6a62c11bf705307ef05212e", + "0x91dd8813a5d6dddcda7b0f87f672b83198cd0959d8311b2b26fb1fae745185c01f796fbd03aad9db9b58482483fdadd8", + "0x8d15911aacf76c8bcd7136e958febd6963104addcd751ce5c06b6c37213f9c4fb0ffd4e0d12c8e40c36d658999724bfd", + "0x8a36c5732d3f1b497ebe9250610605ee62a78eaa9e1a45f329d09aaa1061131cf1d9df00f3a7d0fe8ad614a1ff9caaae", + "0xa407d06affae03660881ce20dab5e2d2d6cddc23cd09b95502a9181c465e57597841144cb34d22889902aff23a76d049", + "0xb5fd856d0578620a7e25674d9503be7d97a2222900e1b4738c1d81ff6483b144e19e46802e91161e246271f90270e6cf", + "0x91b7708869cdb5a7317f88c0312d103f8ce90be14fb4f219c2e074045a2a83636fdc3e69e862049fc7c1ef000e832541", + "0xb64719cc5480709d1dae958f1d3082b32a43376da446c8f9f64cb02a301effc9c34d9102051733315a8179aed94d53cc", + "0x94347a9542ff9d18f7d9eaa2f4d9b832d0e535fe49d52aa2de08aa8192400eddabdb6444a2a78883e27c779eed7fdf5a", + "0x840ef44a733ff1376466698cd26f82cf56bb44811e196340467f932efa3ae1ef9958a0701b3b032f50fd9c1d2aed9ab5", + "0x90ab3f6f67688888a31ffc2a882bb37adab32d1a4b278951a21646f90d03385fc976715fc639a785d015751171016f10", + "0xb56f35d164c24b557dbcbc8a4bfa681ec916f8741ffcb27fb389c164f4e3ed2be325210ef5bdaeae7a172ca9599ab442", + "0xa7921a5a80d7cf6ae81ba9ee05e0579b18c20cd2852762c89d6496aa4c8ca9d1ca2434a67b2c16d333ea8e382cdab1e3", + "0xa506bcfbd7e7e5a92f68a1bd87d07ad5fe3b97aeee40af2bf2cae4efcd77fff03f872732c5b7883aa6584bee65d6f8cb", + "0xa8c46cff58931a1ce9cbe1501e1da90b174cddd6d50f3dfdfb759d1d4ad4673c0a8feed6c1f24c7af32865a7d6c984e5", + "0xb45686265a83bff69e312c5149db7bb70ac3ec790dc92e392b54d9c85a656e2bf58596ce269f014a906eafc97461aa5f", + "0x8d4009a75ccb2f29f54a5f16684b93202c570d7a56ec1a8b20173269c5f7115894f210c26b41e8d54d4072de2d1c75d0", + "0xaef8810af4fc676bf84a0d57b189760ddc3375c64e982539107422e3de2580b89bd27aa6da44e827b56db1b5555e4ee8", + "0x888f0e1e4a34f48eb9a18ef4de334c27564d72f2cf8073e3d46d881853ac1424d79e88d8ddb251914890588937c8f711", + "0xb64b0aa7b3a8f6e0d4b3499fe54e751b8c3e946377c0d5a6dbb677be23736b86a7e8a6be022411601dd75012012c3555", + "0x8d57776f519f0dd912ea14f79fbab53a30624e102f9575c0bad08d2dc754e6be54f39b11278c290977d9b9c7c0e1e0ad", + "0xa018fc00d532ceb2e4de908a15606db9b6e0665dd77190e2338da7c87a1713e6b9b61554e7c1462f0f6d4934b960b15c", + "0x8c932be83ace46f65c78e145b384f58e41546dc0395270c1397874d88626fdeda395c8a289d602b4c312fe98c1311856", + "0x89174838e21639d6bdd91a0621f04dc056907b88e305dd66e46a08f6d65f731dea72ae87ca5e3042d609e8de8de9aa26", + "0xb7b7f508bb74f7a827ac8189daa855598ff1d96fa3a02394891fd105d8f0816224cd50ac4bf2ed1cf469ace516c48184", + "0xb31877ad682583283baadd68dc1bebd83f5748b165aadd7fe9ef61a343773b88bcd3a022f36d6c92f339b7bfd72820a9", + "0xb79d77260b25daf9126dab7a193df2d7d30542786fa1733ffaf6261734770275d3ca8bae1d9915d1181a78510b3439db", + "0x91894fb94cd4c1dd2ceaf9c53a7020c5799ba1217cf2d251ea5bc91ed26e1159dd758e98282ebe35a0395ef9f1ed15a0", + "0xab59895cdafd33934ceedfc3f0d5d89880482cba6c99a6db93245f9e41987efd76e0640e80aef31782c9a8c7a83fccec", + "0xaa22ea63654315e033e09d4d4432331904a6fc5fb1732557987846e3c564668ca67c60a324b4af01663a23af11a9ce4b", + "0xb53ba3ef342601467e1f71aa280e100fbabbd38518fa0193e0099505036ee517c1ac78e96e9baeb549bb6879bb698fb0", + "0x943fd69fd656f37487cca3605dc7e5a215fddd811caf228595ec428751fc1de484a0cb84c667fe4d7c35599bfa0e5e34", + "0x9353128b5ebe0dddc555093cf3e5942754f938173541033e8788d7331fafc56f68d9f97b4131e37963ab7f1c8946f5f1", + "0xa76cd3c566691f65cfb86453b5b31dbaf3cab8f84fe1f795dd1e570784b9b01bdd5f0b3c1e233942b1b5838290e00598", + "0x983d84b2e53ffa4ae7f3ba29ef2345247ea2377686b74a10479a0ef105ecf90427bf53b74c96dfa346d0f842b6ffb25b", + "0x92e0fe9063306894a2c6970c001781cff416c87e87cb5fbac927a3192655c3da4063e6fa93539f6ff58efac6adcc5514", + "0xb00a81f03c2b8703acd4e2e4c21e06973aba696415d0ea1a648ace2b0ea19b242fede10e4f9d7dcd61c546ab878bc8f9", + "0xb0d08d880f3b456a10bf65cff983f754f545c840c413aea90ce7101a66eb0a0b9b1549d6c4d57725315828607963f15a", + "0x90cb64d03534f913b411375cce88a9e8b1329ce67a9f89ca5df8a22b8c1c97707fec727dbcbb9737f20c4cf751359277", + "0x8327c2d42590dfcdb78477fc18dcf71608686ad66c49bce64d7ee874668be7e1c17cc1042a754bbc77c9daf50b2dae07", + "0x8532171ea13aa7e37178e51a6c775da469d2e26ec854eb16e60f3307db4acec110d2155832c202e9ba525fc99174e3b0", + "0x83ca44b15393d021de2a511fa5511c5bd4e0ac7d67259dce5a5328f38a3cce9c3a269405959a2486016bc27bb140f9ff", + "0xb1d36e8ca812be545505c8214943b36cabee48112cf0de369957afa796d37f86bf7249d9f36e8e990f26f1076f292b13", + "0x9803abf45be5271e2f3164c328d449efc4b8fc92dfc1225d38e09630909fe92e90a5c77618daa5f592d23fc3ad667094", + "0xb268ad68c7bf432a01039cd889afae815c3e120f57930d463aece10af4fd330b5bd7d8869ef1bcf6b2e78e4229922edc", + "0xa4c91a0d6f16b1553264592b4cbbbf3ca5da32ab053ffbdd3dbb1aed1afb650fb6e0dc5274f71a51d7160856477228db", + "0xad89d043c2f0f17806277ffdf3ecf007448e93968663f8a0b674254f36170447b7527d5906035e5e56f4146b89b5af56", + "0x8b6964f757a72a22a642e4d69102951897e20c21449184e44717bd0681d75f7c5bfa5ee5397f6e53febf85a1810d6ed1", + "0xb08f5cdaabec910856920cd6e836c830b863eb578423edf0b32529488f71fe8257d90aed4a127448204df498b6815d79", + "0xaf26bb3358be9d280d39b21d831bb53145c4527a642446073fee5a86215c4c89ff49a3877a7a549486262f6f57a0f476", + "0xb4010b37ec4d7c2af20800e272539200a6b623ae4636ecbd0e619484f4ab9240d02bc5541ace3a3fb955dc0a3d774212", + "0x82752ab52bdcc3cc2fc405cb05a2e694d3df4a3a68f2179ec0652536d067b43660b96f85f573f26fbd664a9ef899f650", + "0x96d392dde067473a81faf2d1fea55b6429126b88b160e39b4210d31d0a82833ffd3a80e07d24d495aea2d96be7251547", + "0xa76d8236d6671204d440c33ac5b8deb71fa389f6563d80e73be8b043ec77d4c9b06f9a586117c7f957f4af0331cbc871", + "0xb6c90961f68b5e385d85c9830ec765d22a425f506904c4d506b87d8944c2b2c09615e740ed351df0f9321a7b93979cae", + "0xa6ec5ea80c7558403485b3b1869cdc63bde239bafdf936d9b62a37031628402a36a2cfa5cfbb8e26ac922cb0a209b3ba", + "0x8c3195bbdbf9bc0fc95fa7e3d7f739353c947f7767d1e3cb24d8c8602d8ea0a1790ac30b815be2a2ba26caa5227891e2", + "0xa7f8a63d809f1155722c57f375ea00412b00147776ae4444f342550279ef4415450d6f400000a326bf11fea6c77bf941", + "0x97fa404df48433a00c85793440e89bb1af44c7267588ae937a1f5d53e01e1c4d4fc8e4a6d517f3978bfdd6c2dfde012f", + "0xa984a0a3836de3d8d909c4629a2636aacb85393f6f214a2ef68860081e9db05ad608024762db0dc35e895dc00e2d4cdd", + "0x9526cf088ab90335add1db4d3a4ac631b58cbfbe88fa0845a877d33247d1cfeb85994522e1eb8f8874651bfb1df03e2a", + "0xac83443fd0afe99ad49de9bf8230158c118e2814c9c89db5ac951c240d6c2ce45e7677221279d9e97848ec466b99aafe", + "0xaeeefdbaba612e971697798ceaf63b247949dc823a0ad771ae5b988a5e882b338a98d3d0796230f49d533ec5ba411b39", + "0xae3f248b5a7b0f92b7820a6c5ae21e5bd8f4265d4f6e21a22512079b8ee9be06393fd3133ce8ebac0faf23f4f8517e36", + "0xa64a831b908eee784b8388b45447d2885ec0551b26b0c2b15e5f417d0a12c79e867fb7bd3d008d0af98b44336f8ec1ad", + "0xb242238cd8362b6e440ba21806905714dd55172db25ec7195f3fc4937b2aba146d5cbf3cf691a1384b4752dc3b54d627", + "0x819f97f337eea1ffb2a678cc25f556f1aab751c6b048993a1d430fe1a3ddd8bb411c152e12ca60ec6e057c190cd1db9a", + "0xb9d7d187407380df54ee9fef224c54eec1bfabf17dc8abf60765b7951f538f59aa26fffd5846cfe05546c35f59b573f4", + "0xaa6e3c14efa6a5962812e3f94f8ce673a433f4a82d07a67577285ea0eaa07f8be7115853122d12d6d4e1fdf64c504be1", + "0x82268bee9c1662d3ddb5fb785abfae6fb8b774190f30267f1d47091d2cd4b3874db4372625aa36c32f27b0eee986269b", + "0xb236459565b7b966166c4a35b2fa71030b40321821b8e96879d95f0e83a0baf33fa25721f30af4a631df209e25b96061", + "0x8708d752632d2435d2d5b1db4ad1fa2558d776a013655f88e9a3556d86b71976e7dfe5b8834fdec97682cd94560d0d0d", + "0xae1424a68ae2dbfb0f01211f11773732a50510b5585c1fb005cb892b2c6a58f4a55490b5c5b4483c6fce40e9d3236a52", + "0xb3f5f722af9dddb07293c871ce97abbccba0093ca98c8d74b1318fa21396fc1b45b69c15084f63d728f9908442024506", + "0x9606f3ce5e63886853ca476dc0949e7f1051889d529365c0cb0296fdc02abd088f0f0318ecd2cf36740a3634132d36f6", + "0xb11a833a49fa138db46b25ff8cdda665295226595bc212c0931b4931d0a55c99da972c12b4ef753f7e37c6332356e350", + "0xafede34e7dab0a9e074bc19a7daddb27df65735581ca24ad70c891c98b1349fcebbcf3ba6b32c2617fe06a5818dabc2d", + "0x97993d456e459e66322d01f8eb13918979761c3e8590910453944bdff90b24091bb018ac6499792515c9923be289f99f", + "0x977e3e967eff19290a192cd11df3667d511b398fb3ac9a5114a0f3707e25a0edcb56105648b1b85a8b7519fc529fc6f6", + "0xb873a7c88bf58731fe1bf61ff6828bf114cf5228f254083304a4570e854e83748fc98683ddba62d978fff7909f2c5c47", + "0xad4b2691f6f19da1d123aaa23cca3e876247ed9a4ab23c599afdbc0d3aa49776442a7ceaa996ac550d0313d9b9a36cee", + "0xb9210713c78e19685608c6475bfa974b57ac276808a443f8b280945c5d5f9c39da43effa294bfb1a6c6f7b6b9f85bf6c", + "0xa65152f376113e61a0e468759de38d742caa260291b4753391ee408dea55927af08a4d4a9918600a3bdf1df462dffe76", + "0x8bf8c27ad5140dde7f3d2280fd4cc6b29ab76537e8d7aa7011a9d2796ee3e56e9a60c27b5c2da6c5e14fc866301dc195", + "0x92fde8effc9f61393a2771155812b863cff2a0c5423d7d40aa04d621d396b44af94ddd376c28e7d2f53c930aea947484", + "0x97a01d1dd9ee30553ce676011aea97fa93d55038ada95f0057d2362ae9437f3ed13de8290e2ff21e3167dd7ba10b9c3f", + "0x89affffaa63cb2df3490f76f0d1e1d6ca35c221dd34057176ba739fa18d492355e6d2a5a5ad93a136d3b1fed0bb8aa19", + "0x928b8e255a77e1f0495c86d3c63b83677b4561a5fcbbe5d3210f1e0fc947496e426d6bf3b49394a5df796c9f25673fc4", + "0x842a0af91799c9b533e79ee081efe2a634cac6c584c2f054fb7d1db67dde90ae36de36cbf712ec9cd1a0c7ee79e151ea", + "0xa65b946cf637e090baf2107c9a42f354b390e7316beb8913638130dbc67c918926eb87bec3b1fe92ef72bc77a170fa3b", + "0xaafc0f19bfd71ab5ae4a8510c7861458b70ad062a44107b1b1dbacbfa44ba3217028c2824bd7058e2fa32455f624040b", + "0x95269dc787653814e0be899c95dba8cfa384f575a25e671c0806fd80816ad6797dc819d30ae06e1d0ed9cb01c3950d47", + "0xa1e760f7fa5775a1b2964b719ff961a92083c5c617f637fc46e0c9c20ab233f8686f7f38c3cb27d825c54dd95e93a59b", + "0xac3b8a7c2317ea967f229eddc3e23e279427f665c4705c7532ed33443f1243d33453c1088f57088d2ab1e3df690a9cc9", + "0xb787beeddfbfe36dd51ec4efd9cf83e59e84d354c3353cc9c447be53ae53d366ed1c59b686e52a92f002142c8652bfe0", + "0xb7a64198300cb6716aa7ac6b25621f8bdec46ad5c07a27e165b3f774cdf65bcfdbf31e9bae0c16b44de4b00ada7a4244", + "0xb8ae9f1452909e0c412c7a7fe075027691ea8df1347f65a5507bc8848f1d2c833d69748076db1129e5b4fb912f65c86c", + "0x9682e41872456b9fa67def89e71f06d362d6c8ca85c9c48536615bc401442711e1c9803f10ab7f8ab5feaec0f9df20a6", + "0x88889ff4e271dc1c7e21989cc39f73cde2f0475acd98078281591ff6c944fadeb9954e72334319050205d745d4df73df", + "0x8f79b5b8159e7fd0d93b0645f3c416464f39aec353b57d99ecf24f96272df8a068ad67a6c90c78d82c63b40bb73989bb", + "0x838c01a009a3d8558a3f0bdd5e22de21af71ca1aefc8423c91dc577d50920e9516880e87dce3e6d086e11cd45c9052d9", + "0xb97f1c6eee8a78f137c840667cc288256e39294268a3009419298a04a1d0087c9c9077b33c917c65caf76637702dda8a", + "0x972284ce72f96a61c899260203dfa06fc3268981732bef74060641c1a5068ead723e3399431c247ca034b0dae861e8df", + "0x945a8d52d6d3db6663dbd3110c6587f9e9c44132045eeffba15621576d178315cb52870fa5861669f84f0bee646183fe", + "0xa0a547b5f0967b1c3e5ec6c6a9a99f0578521489180dfdfbb5561f4d166baac43a2f06f950f645ce991664e167537eed", + "0xa0592cda5cdddf1340033a745fd13a6eff2021f2e26587116c61c60edead067e0f217bc2bef4172a3c9839b0b978ab35", + "0xb9c223b65a3281587fa44ec829e609154b32f801fd1de6950e01eafb07a8324243b960d5735288d0f89f0078b2c42b5b", + "0x99ebfc3b8f9f98249f4d37a0023149ed85edd7a5abe062c8fb30c8c84555258b998bdcdd1d400bc0fa2a4aaa8b224466", + "0x955b68526e6cb3937b26843270f4e60f9c6c8ece2fa9308fe3e23afa433309c068c66a4bc16ee2cf04220f095e9afce4", + "0xb766caeafcc00378135ae53397f8a67ed586f5e30795462c4a35853de6681b1f17401a1c40958de32b197c083b7279c1", + "0x921bf87cad947c2c33fa596d819423c10337a76fe5a63813c0a9dc78a728207ae7b339407a402fc4d0f7cba3af6da6fc", + "0xa74ba1f3bc3e6c025db411308f49b347ec91da1c916bda9da61e510ec8d71d25e0ac0f124811b7860e5204f93099af27", + "0xa29b4d144e0bf17a7e8353f2824cef0ce85621396babe8a0b873ca1e8a5f8d508b87866cf86da348470649fceefd735c", + "0xa8040e12ffc3480dd83a349d06741d1572ef91932c46f5cf03aee8454254156ee95786fd013d5654725e674c920cec32", + "0x8c4cf34ca60afd33923f219ffed054f90cd3f253ffeb2204a3b61b0183417e366c16c07fae860e362b0f2bfe3e1a1d35", + "0x8195eede4ddb1c950459df6c396b2e99d83059f282b420acc34220cadeed16ab65c856f2c52568d86d3c682818ed7b37", + "0x91fff19e54c15932260aa990c7fcb3c3c3da94845cc5aa8740ef56cf9f58d19b4c3c55596f8d6c877f9f4d22921d93aa", + "0xa3e0bf7e5d02a80b75cf75f2db7e66cb625250c45436e3c136d86297d652590ec97c2311bafe407ad357c79ab29d107b", + "0x81917ff87e5ed2ae4656b481a63ced9e6e5ff653b8aa6b7986911b8bc1ee5b8ef4f4d7882c3f250f2238e141b227e510", + "0x915fdbe5e7de09c66c0416ae14a8750db9412e11dc576cf6158755fdcaf67abdbf0fa79b554cac4fe91c4ec245be073f", + "0x8df27eafb5c3996ba4dc5773c1a45ca77e626b52e454dc1c4058aa94c2067c18332280630cc3d364821ee53bf2b8c130", + "0x934f8a17c5cbb827d7868f5c8ca00cb027728a841000a16a3428ab16aa28733f16b52f58c9c4fbf75ccc45df72d9c4df", + "0xb83f4da811f9183c25de8958bc73b504cf790e0f357cbe74ef696efa7aca97ad3b7ead1faf76e9f982c65b6a4d888fc2", + "0x87188213c8b5c268dc2b6da413f0501c95749e953791b727450af3e43714149c115b596b33b63a2f006a1a271b87efd0", + "0x83e9e888ab9c3e30761de635d9aabd31248cdd92f7675fc43e4b21fd96a03ec1dc4ad2ec94fec857ffb52683ac98e360", + "0xb4b9a1823fe2d983dc4ec4e3aaea297e581c3fc5ab4b4af5fa1370caa37af2d1cc7fc6bfc5e7da60ad8fdce27dfe4b24", + "0x856388bc78aef465dbcdd1f559252e028c9e9a2225c37d645c138e78f008f764124522705822a61326a6d1c79781e189", + "0xa6431b36db93c3b47353ba22e7c9592c9cdfb9cbdd052ecf2cc3793f5b60c1e89bc96e6bae117bfd047f2308da00dd2f", + "0xb619972d48e7e4291542dcde08f7a9cdc883c892986ded2f23ccb216e245cd8d9ad1d285347b0f9d7611d63bf4cee2bc", + "0x8845cca6ff8595955f37440232f8e61d5351500bd016dfadd182b9d39544db77a62f4e0102ff74dd4173ae2c181d24ef", + "0xb2f5f7fa26dcd3b6550879520172db2d64ee6aaa213cbef1a12befbce03f0973a22eb4e5d7b977f466ac2bf8323dcedd", + "0x858b7f7e2d44bdf5235841164aa8b4f3d33934e8cb122794d90e0c1cac726417b220529e4f896d7b77902ab0ccd35b3a", + "0x80b0408a092dae2b287a5e32ea1ad52b78b10e9c12f49282976cd738f5d834e03d1ad59b09c5ccaccc39818b87d06092", + "0xb996b0a9c6a2d14d984edcd6ab56bc941674102980d65b3ad9733455f49473d3f587c8cbf661228a7e125ddbe07e3198", + "0x90224fcebb36865293bd63af786e0c5ade6b67c4938d77eb0cbae730d514fdd0fe2d6632788e858afd29d46310cf86df", + "0xb71351fdfff7168b0a5ec48397ecc27ac36657a8033d9981e97002dcca0303e3715ce6dd3f39423bc8ef286fa2e9e669", + "0xae2a3f078b89fb753ce4ed87e0c1a58bb19b4f0cfb6586dedb9fcab99d097d659a489fb40e14651741e1375cfc4b6c5f", + "0x8ef476b118e0b868caed297c161f4231bbeb863cdfa5e2eaa0fc6b6669425ce7af50dc374abceac154c287de50c22307", + "0x92e46ab472c56cfc6458955270d3c72b7bde563bb32f7d4ab4d959db6f885764a3d864e1aa19802fefaa5e16b0cb0b54", + "0x96a3f68323d1c94e73d5938a18a377af31b782f56212de3f489d22bc289cf24793a95b37f1d6776edf88114b5c1fa695", + "0x962cc068cfce6faaa27213c4e43e44eeff0dfbb6d25b814e82c7da981fb81d7d91868fa2344f05fb552362f98cfd4a72", + "0x895d4e4c4ad670abf66d43d59675b1add7afad7438ada8f42a0360c704cee2060f9ac15b4d27e9b9d0996bb801276fe3", + "0xb3ad18d7ece71f89f2ef749b853c45dc56bf1c796250024b39a1e91ed11ca32713864049c9aaaea60cde309b47486bbf", + "0x8f05404e0c0258fdbae50e97ccb9b72ee17e0bd2400d9102c0dad981dac8c4c71585f03e9b5d50086d0a2d3334cb55d1", + "0x8bd877e9d4591d02c63c6f9fc9976c109de2d0d2df2bfa5f6a3232bab5b0b8b46e255679520480c2d7a318545efa1245", + "0x8d4c16b5d98957c9da13d3f36c46f176e64e5be879f22be3179a2c0e624fe4758a82bf8c8027410002f973a3b84cd55a", + "0x86e2a8dea86427b424fa8eada881bdff896907084a495546e66556cbdf070b78ba312bf441eb1be6a80006d25d5097a3", + "0x8608b0c117fd8652fdab0495b08fadbeba95d9c37068e570de6fddfef1ba4a1773b42ac2be212836141d1bdcdef11a17", + "0xa13d6febf5fb993ae76cae08423ca28da8b818d6ef0fde32976a4db57839cd45b085026b28ee5795f10a9a8e3098c683", + "0x8e261967fa6de96f00bc94a199d7f72896a6ad8a7bbb1d6187cca8fad824e522880e20f766620f4f7e191c53321d70f9", + "0x8b8e8972ac0218d7e3d922c734302803878ad508ca19f5f012bc047babd8a5c5a53deb5fe7c15a4c00fd6d1cb9b1dbd0", + "0xb5616b233fb3574a2717d125a434a2682ff68546dccf116dd8a3b750a096982f185614b9fb6c7678107ff40a451f56fa", + "0xaa6adf9b0c3334b0d0663f583a4914523b2ac2e7adffdb026ab9109295ff6af003ef8357026dbcf789896d2afded8d73", + "0xacb72df56a0b65496cd534448ed4f62950bb1e11e50873b6ed349c088ee364441821294ce0f7c61bd7d38105bea3b442", + "0xabae12df83e01ec947249fedd0115dc501d2b03ff7232092979eda531dbbca29ace1d46923427c7dde4c17bdf3fd7708", + "0x820b4fc2b63a9fda7964acf5caf19a2fc4965007cb6d6b511fcafcb1f71c3f673a1c0791d3f86e3a9a1eb6955b191cc0", + "0xaf277259d78c6b0f4f030a10c53577555df5e83319ddbad91afbd7c30bc58e7671c56d00d66ec3ab5ef56470cd910cee", + "0xad4a861c59f1f5ca1beedd488fb3d131dea924fffd8e038741a1a7371fad7370ca5cf80dc01f177fbb9576713bb9a5b3", + "0xb67a5162982ce6a55ccfb2f177b1ec26b110043cf18abd6a6c451cf140b5af2d634591eb4f28ad92177d8c7e5cd0a5e8", + "0x96176d0a83816330187798072d449cbfccff682561e668faf6b1220c9a6535b32a6e4f852e8abb00f79abb87493df16b", + "0xb0afe6e7cb672e18f0206e4423f51f8bd0017bf464c4b186d46332c5a5847647f89ff7fa4801a41c1b0b42f6135bcc92", + "0x8fc5e7a95ef20c1278c645892811f6fe3f15c431ebc998a32ec0da44e7213ea934ed2be65239f3f49b8ec471e9914160", + "0xb7793e41adda6c82ba1f2a31f656f6205f65bf8a3d50d836ee631bc7ce77c153345a2d0fc5c60edf8b37457c3729c4ec", + "0xa504dd7e4d6b2f4379f22cc867c65535079c75ccc575955f961677fa63ecb9f74026fa2f60c9fb6323c1699259e5e9c8", + "0xab899d00ae693649cc1afdf30fb80d728973d2177c006e428bf61c7be01e183866614e05410041bc82cb14a33330e69c", + "0x8a3bd8b0b1be570b65c4432a0f6dc42f48a2000e30ab089cf781d38f4090467b54f79c0d472fcbf18ef6a00df69cc6f3", + "0xb4d7028f7f76a96a3d7803fca7f507ae11a77c5346e9cdfccb120a833a59bda1f4264e425aa588e7a16f8e7638061d84", + "0xb9c7511a76ea5fb105de905d44b02edb17008335766ee357ed386b7b3cf19640a98b38785cb14603c1192bee5886c9b6", + "0x8563afb12e53aed71ac7103ab8602bfa8371ae095207cb0d59e8fd389b6ad1aff0641147e53cb6a7ca16c7f37c9c5e6b", + "0x8e108be614604e09974a9ed90960c28c4ea330a3d9a0cb4af6dd6f193f84ab282b243ecdf549b3131036bebc8905690c", + "0xb794d127fbedb9c5b58e31822361706ffac55ce023fbfe55716c3c48c2fd2f2c7660a67346864dfe588812d369cb50b6", + "0xb797a3442fc3b44f41baefd30346f9ac7f96e770d010d53c146ce74ce424c10fb62758b7e108b8abfdc5fafd89d745cb", + "0x993bb71e031e8096442e6205625e1bfddfe6dd6a83a81f3e2f84fafa9e5082ab4cad80a099f21eff2e81c83457c725c3", + "0x8711ab833fc03e37acf2e1e74cfd9133b101ff4144fe30260654398ae48912ab46549d552eb9d15d2ea57760d35ac62e", + "0xb21321fd2a12083863a1576c5930e1aecb330391ef83326d9d92e1f6f0d066d1394519284ddab55b2cb77417d4b0292f", + "0x877d98f731ffe3ee94b0b5b72d127630fa8a96f6ca4f913d2aa581f67732df6709493693053b3e22b0181632ac6c1e3b", + "0xae391c12e0eb8c145103c62ea64f41345973311c3bf7281fa6bf9b7faafac87bcf0998e5649b9ef81e288c369c827e07", + "0xb83a2842f36998890492ab1cd5a088d9423d192681b9a3a90ec518d4c541bce63e6c5f4df0f734f31fbfdd87785a2463", + "0xa21b6a790011396e1569ec5b2a423857b9bec16f543e63af28024e116c1ea24a3b96e8e4c75c6537c3e4611fd265e896", + "0xb4251a9c4aab3a495da7a42e684ba4860dbcf940ad1da4b6d5ec46050cbe8dab0ab9ae6b63b5879de97b905723a41576", + "0x8222f70aebfe6ac037f8543a08498f4cadb3edaac00336fc00437eb09f2cba758f6c38e887cc634b4d5b7112b6334836", + "0x86f05038e060594c46b5d94621a1d9620aa8ba59a6995baf448734e21f58e23c1ea2993d3002ad5250d6edd5ba59b34f", + "0xa7c0c749baef811ab31b973c39ceb1d94750e2bc559c90dc5eeb20d8bb6b78586a2b363c599ba2107d6be65cd435f24e", + "0x861d46a5d70b38d6c1cd72817a2813803d9f34c00320c8b62f8b9deb67f5b5687bc0b37c16d28fd017367b92e05da9ca", + "0xb3365d3dab639bffbe38e35383686a435c8c88b397b717cd4aeced2772ea1053ceb670f811f883f4e02975e5f1c4ac58", + "0xa5750285f61ab8f64cd771f6466e2c0395e01b692fd878f2ef2d5c78bdd8212a73a3b1dfa5e4c8d9e1afda7c84857d3b", + "0x835a10809ccf939bc46cf950a33b36d71be418774f51861f1cd98a016ade30f289114a88225a2c11e771b8b346cbe6ef", + "0xa4f59473a037077181a0a62f1856ec271028546ca9452b45cedfcb229d0f4d1aabfc13062b07e536cc8a0d4b113156a2", + "0x95cd14802180b224d44a73cc1ed599d6c4ca62ddcaa503513ccdc80aaa8be050cc98bd4b4f3b639549beb4587ac6caf9", + "0x973b731992a3e69996253d7f36dd7a0af1982b5ed21624b77a7965d69e9a377b010d6dabf88a8a97eec2a476259859cc", + "0xaf8a1655d6f9c78c8eb9a95051aa3baaf9c811adf0ae8c944a8d3fcba87b15f61021f3baf6996fa0aa51c81b3cb69de1", + "0x835aad5c56872d2a2d6c252507b85dd742bf9b8c211ccb6b25b52d15c07245b6d89b2a40f722aeb5083a47cca159c947", + "0xabf4e970b02bef8a102df983e22e97e2541dd3650b46e26be9ee394a3ea8b577019331857241d3d12b41d4eacd29a3ac", + "0xa13c32449dbedf158721c13db9539ae076a6ce5aeaf68491e90e6ad4e20e20d1cdcc4a89ed9fd49cb8c0dd50c17633c1", + "0x8c8f78f88b7e22dd7e9150ab1c000f10c28e696e21d85d6469a6fe315254740f32e73d81ab1f3c1cf8f544c86df506e8", + "0xb4b77f2acfe945abf81f2605f906c10b88fb4d28628487fb4feb3a09f17f28e9780445dfcee4878349d4c6387a9d17d4", + "0x8d255c235f3812c6ecc646f855fa3832be5cb4dbb9c9e544989fafdf3f69f05bfd370732eaf954012f0044aa013fc9c6", + "0xb982efd3f34b47df37c910148ac56a84e8116647bea24145a49e34e0a6c0176e3284d838dae6230cb40d0be91c078b85", + "0x983f365aa09bd85df2a6a2ad8e4318996b1e27d02090755391d4486144e40d80b1fbfe1c798d626db92f52e33aa634da", + "0x95fd1981271f3ea3a41d654cf497e6696730d9ff7369f26bc4d7d15c7adb4823dd0c42e4a005a810af12d234065e5390", + "0xa9f5219bd4b913c186ef30c02f995a08f0f6f1462614ea5f236964e02bdaa33db9d9b816c4aee5829947840a9a07ba60", + "0x9210e6ceb05c09b46fd09d036287ca33c45124ab86315e5d6911ff89054f1101faaa3e83d123b7805056d388bcec6664", + "0x8ed9cbf69c6ff3a5c62dd9fe0d7264578c0f826a29e614bc2fb4d621d90c8c9992438accdd7a614b1dca5d1bb73dc315", + "0x85cf2a8cca93e00da459e3cecd22c342d697eee13c74d5851634844fc215f60053cf84b0e03c327cb395f48d1c71a8a4", + "0x8818a18e9a2ec90a271b784400c1903089ffb0e0b40bc5abbbe12fbebe0f731f91959d98c5519ef1694543e31e2016d4", + "0x8dabc130f296fa7a82870bf9a8405aaf542b222ed9276bba9bd3c3555a0f473acb97d655ee7280baff766a827a8993f0", + "0xac7952b84b0dc60c4d858f034093b4d322c35959605a3dad2b806af9813a4680cb038c6d7f4485b4d6b2ff502aaeca25", + "0xad65cb6d57b48a2602568d2ec8010baed0eb440eec7638c5ec8f02687d764e9de5b5d42ad5582934e592b48471c22d26", + "0xa02ab8bd4c3d114ea23aebdd880952f9495912817da8c0c08eabc4e6755439899d635034413d51134c72a6320f807f1c", + "0x8319567764b8295402ec1ebef4c2930a138480b37e6d7d01c8b4c9cd1f2fc3f6e9a44ae6e380a0c469b25b06db23305f", + "0xafec53b2301dc0caa8034cd9daef78c48905e6068d692ca23d589b84a6fa9ddc2ed24a39480597e19cb3e83eec213b3f", + "0xac0b4ffdb5ae08e586a9cdb98f9fe56f4712af3a97065e89e274feacfb52b53c839565aee93c4cfaaccfe51432c4fab0", + "0x8972cbf07a738549205b1094c5987818124144bf187bc0a85287c94fdb22ce038c0f11df1aa16ec5992e91b44d1af793", + "0xb7267aa6f9e3de864179b7da30319f1d4cb2a3560f2ea980254775963f1523b44c680f917095879bebfa3dc2b603efcf", + "0x80f68f4bfc337952e29504ee5149f15093824ea7ab02507efd1317a670f6cbc3611201848560312e3e52e9d9af72eccf", + "0x8897fee93ce8fc1e1122e46b6d640bba309384dbd92e46e185e6364aa8210ebf5f9ee7e5e604b6ffba99aa80a10dd7d0", + "0xb58ea6c02f2360be60595223d692e82ee64874fda41a9f75930f7d28586f89be34b1083e03bbc1575bbfdda2d30db1ea", + "0x85a523a33d903280d70ac5938770453a58293480170c84926457ac2df45c10d5ff34322ab130ef4a38c916e70d81af53", + "0xa2cbf045e1bed38937492c1f2f93a5ba41875f1f262291914bc1fc40c60bd0740fb3fea428faf6da38b7c180fe8ac109", + "0x8c09328770ed8eb17afc6ac7ddd87bb476de18ed63cab80027234a605806895959990c47bd10d259d7f3e2ecb50074c9", + "0xb4b9e19edb4a33bde8b7289956568a5b6b6557404e0a34584b5721fe6f564821091013fbb158e2858c6d398293bb4b59", + "0x8a47377df61733a2aa5a0e945fce00267f8e950f37e109d4487d92d878fb8b573317bb382d902de515b544e9e233458d", + "0xb5804c9d97efeff5ca94f3689b8088c62422d92a1506fd1d8d3b1b30e8a866ad0d6dad4abfa051dfc4471250cac4c5d9", + "0x9084a6ee8ec22d4881e9dcc8a9eb3c2513523d8bc141942370fd191ad2601bf9537a0b1e84316f3209b3d8a54368051e", + "0x85447eea2fa26656a649f8519fa67279183044791d61cf8563d0783d46d747d96af31d0a93507bbb2242666aa87d3720", + "0x97566a84481027b60116c751aec552adfff2d9038e68d48c4db9811fb0cbfdb3f1d91fc176a0b0d988a765f8a020bce1", + "0xae87e5c1b9e86c49a23dceda4ecfd1dcf08567f1db8e5b6ec752ebd45433c11e7da4988573cdaebbb6f4135814fc059e", + "0xabee05cf9abdbc52897ac1ce9ed157f5466ed6c383d6497de28616238d60409e5e92619e528af8b62cc552bf09970dc2", + "0xae6d31cd7bf9599e5ee0828bab00ceb4856d829bba967278a73706b5f388465367aa8a6c7da24b5e5f1fdd3256ef8e63", + "0xac33e7b1ee47e1ee4af472e37ab9e9175260e506a4e5ce449788075da1b53c44cb035f3792d1eea2aa24b1f688cc6ed3", + "0x80f65b205666b0e089bb62152251c48c380a831e5f277f11f3ef4f0d52533f0851c1b612267042802f019ec900dc0e8f", + "0x858520ad7aa1c9fed738e3b583c84168f2927837ad0e1d326afe9935c26e9b473d7f8c382e82ef1fe37d2b39bb40a1ee", + "0xb842dd4af8befe00a97c2d0f0c33c93974761e2cb9e5ab8331b25170318ddd5e4bdbc02d8f90cbfdd5f348f4f371c1f7", + "0x8bf2cb79bc783cb57088aae7363320cbeaabd078ffdec9d41bc74ff49e0043d0dad0086a30e5112b689fd2f5a606365d", + "0x982eb03bbe563e8850847cd37e6a3306d298ab08c4d63ab6334e6b8c1fa13fce80cf2693b09714c7621d74261a0ff306", + "0xb143edb113dec9f1e5105d4a93fbe502b859e587640d3db2f628c09a17060e6aec9e900e2c8c411cda99bc301ff96625", + "0xaf472d9befa750dcebc5428fe1a024f18ec1c07bca0f95643ce6b5f4189892a910285afb03fd7ed7068fbe614e80d33c", + "0xa97e3bc57ede73ecd1bbf02de8f51b4e7c1a067da68a3cd719f4ba26a0156cbf1cef2169fd35a18c5a4cced50d475998", + "0xa862253c937cf3d75d7183e5f5be6a4385d526aeda5171c1c60a8381fea79f88f5f52a4fab244ecc70765d5765e6dfd5", + "0x90cb776f8e5a108f1719df4a355bebb04bf023349356382cae55991b31720f0fd03206b895fa10c56c98f52453be8778", + "0xa7614e8d0769dccd520ea4b46f7646e12489951efaef5176bc889e9eb65f6e31758df136b5bf1e9107e68472fa9b46ec", + "0xac3a9b80a3254c42e5ed3a090a0dd7aee2352f480de96ad187027a3bb6c791eddfc3074b6ffd74eea825188f107cda4d", + "0x82a01d0168238ef04180d4b6e0a0e39024c02c2d75b065017c2928039e154d093e1af4503f4d1f3d8a948917abb5d09f", + "0x8fab000a2b0eef851a483aec8d2dd85fe60504794411a2f73ed82e116960547ac58766cb73df71aea71079302630258d", + "0x872451a35c6db61c63e9b8bb9f16b217f985c20be4451c14282c814adb29d7fb13f201367c664435c7f1d4d9375d7a58", + "0x887d9ff54cc96b35d562df4a537ff972d7c4b3fd91ab06354969a4cfede0b9fc68bbffb61d0dbf1a58948dc701e54f5a", + "0x8cb5c2a6bd956875d88f41ae24574434f1308514d44057b55c9c70f13a3366ed054150eed0955a38fda3f757be73d55f", + "0x89ad0163cad93e24129d63f8e38422b7674632a8d0a9016ee8636184cab177659a676c4ee7efba3abe1a68807c656d60", + "0xb9ec01c7cab6d00359b5a0b4a1573467d09476e05ca51a9227cd16b589a9943d161eef62dcc73f0de2ec504d81f4d252", + "0x8031d17635d39dfe9705c485d2c94830b6fc9bc67b91300d9d2591b51e36a782e77ab5904662effa9382d9cca201f525", + "0x8be5a5f6bc8d680e5092d6f9a6585acbaaaa2ddc671da560dcf5cfa4472f4f184b9597b5b539438accd40dda885687cc", + "0xb1fc0f052fae038a2e3de3b3a96b0a1024b009de8457b8b3adb2d315ae68a89af905720108a30038e5ab8d0d97087785", + "0x8b8bdc77bd3a6bc7ca5492b6f8c614852c39a70d6c8a74916eaca0aeb4533b11898b8820a4c2620a97bf35e275480029", + "0xaf35f4dc538d4ad5cdf710caa38fd1eb496c3fa890a047b6a659619c5ad3054158371d1e88e0894428282eed9f47f76b", + "0x8166454a7089cc07758ad78724654f4e7a1a13e305bbf88ddb86f1a4b2904c4fc8ab872d7da364cdd6a6c0365239e2ad", + "0xab287c7d3addce74ce40491871c768abe01daaa0833481276ff2e56926b38a7c6d2681ffe837d2cc323045ad1a4414f9", + "0xb90317f4505793094d89365beb35537f55a6b5618904236258dd04ca61f21476837624a2f45fef8168acf732cab65579", + "0x98ae5ea27448e236b6657ab5ef7b1cccb5372f92ab25f5fa651fbac97d08353a1dae1b280b1cd42b17d2c6a70a63ab9d", + "0xadcf54e752d32cbaa6cb98fbca48d8cd087b1db1d131d465705a0d8042c8393c8f4d26b59006eb50129b21e6240f0c06", + "0xb591a3e4db18a7345fa935a8dd7994bbac5cc270b8ebd84c8304c44484c7a74afb45471fdbe4ab22156a30fae1149b40", + "0x806b53ac049a42f1dcc1d6335505371da0bf27c614f441b03bbf2e356be7b2fb4eed7117eabcce9e427a542eaa2bf7d8", + "0x800482e7a772d49210b81c4a907f5ce97f270b959e745621ee293cf8c71e8989363d61f66a98f2d16914439544ca84c7", + "0x99de9eafdad3617445312341644f2bb888680ff01ce95ca9276b1d2e5ef83fa02dab5e948ebf66c17df0752f1bd37b70", + "0x961ee30810aa4c93ae157fbe9009b8e443c082192bd36a73a6764ff9b2ad8b0948fe9a73344556e01399dd77badb4257", + "0xae0a361067c52efbe56c8adf982c00432cd478929459fc7f74052c8ee9531cd031fe1335418fde53f7c2ef34254eb7ac", + "0xa3503d16b6b27eb20c1b177bcf90d13706169220523a6271b85b2ce35a9a2b9c5bed088540031c0a4ebfdae3a4c6ab04", + "0x909420122c3e723289ca4e7b81c2df5aff312972a2203f4c45821b176e7c862bf9cac7f7df3adf1d59278f02694d06e7", + "0x989f42380ae904b982f85d0c6186c1aef5d6bcba29bcfbb658e811b587eb2749c65c6e4a8cc6409c229a107499a4f5d7", + "0x8037a6337195c8e26a27ea4ef218c6e7d79a9720aaab43932d343192abc2320fe72955f5e431c109093bda074103330a", + "0xb312e168663842099b88445e940249cc508f080ab0c94331f672e7760258dbd86be5267e4cf25ea25facb80bff82a7e9", + "0xaaa3ff8639496864fcdbfdda1ac97edc4f08e3c9288b768f6c8073038c9fbbf7e1c4bea169b4d45c31935cdf0680d45e", + "0x97dbd3df37f0b481a311dfc5f40e59227720f367912200d71908ef6650f32cc985cb05b981e3eea38958f7e48d10a15d", + "0xa89d49d1e267bb452d6cb621b9a90826fe55e9b489c0427b94442d02a16f390eed758e209991687f73f6b5a032321f42", + "0x9530dea4e0e19d6496f536f2e75cf7d814d65fde567055eb20db48fd8d20d501cd2a22fb506db566b94c9ee10f413d43", + "0x81a7009b9e67f1965fa7da6a57591c307de91bf0cd35ab4348dc4a98a4961e096d004d7e7ad318000011dc4342c1b809", + "0x83440a9402b766045d7aca61a58bba2aa29cac1cf718199e472ba086f5d48093d9dda4d135292ba51d049a23964eceae", + "0xa06c9ce5e802df14f6b064a3d1a0735d429b452f0e2e276042800b0a4f16df988fd94cf3945921d5dd3802ab2636f867", + "0xb1359e358b89936dee9e678a187aad3e9ab14ac40e96a0a68f70ee2583cdcf467ae03bef4215e92893f4e12f902adec8", + "0x835304f8619188b4d14674d803103d5a3fa594d48e96d9699e653115dd05fdc2dda6ba3641cf7ad53994d448da155f02", + "0x8327cba5a9ff0d3f5cd0ae55e77167448926d5fcf76550c0ad978092a14122723090c51c415e88e42a2b62eb07cc3981", + "0xb373dcdaea85f85ce9978b1426a7ef4945f65f2d3467a9f1cc551a99766aac95df4a09e2251d3f89ca8c9d1a7cfd7b0e", + "0xab1422dc41af2a227b973a6fd124dfcb2367e2a11a21faa1d381d404f51b7257e5bc82e9cf20cd7fe37d7ae761a2ab37", + "0xa93774a03519d2f20fdf2ef46547b0a5b77c137d6a3434b48d56a2cbef9e77120d1b85d0092cf8842909213826699477", + "0x8eb967a495a38130ea28711580b7e61bcd1d051cd9e4f2dbf62f1380bd86e0d60e978d72f6f31e909eb97b3b9a2b867c", + "0xae8213378da1287ba1fe4242e1acaec19b877b6fe872400013c6eac1084b8d03156792fa3020201725b08228a1e80f49", + "0xb143daf6893d674d607772b3b02d8ac48f294237e2f2c87963c0d4e26d9227d94a2a13512457c3d5883544bbc259f0ef", + "0xb343bd2aca8973888e42542218924e2dda2e938fd1150d06878af76f777546213912b7c7a34a0f94186817d80ffa185c", + "0xb188ebc6a8c3007001aa347ae72cc0b15d09bc6c19a80e386ee4b334734ec0cc2fe8b493c2422f38d1e6d133cc3db6fe", + "0xb795f6a8b9b826aaeee18ccd6baf6c5adeeec85f95eb5b6d19450085ec7217e95a2d9e221d77f583b297d0872073ba0e", + "0xb1c7dbd998ad32ae57bfa95deafa147024afd57389e98992c36b6e52df915d3d5a39db585141ec2423173e85d212fed8", + "0x812bcdeb9fe5f12d0e1df9964798056e1f1c3de3b17b6bd2919b6356c4b86d8e763c01933efbe0224c86a96d5198a4be", + "0xb19ebeda61c23d255cbf472ef0b8a441f4c55b70f0d8ed47078c248b1d3c7c62e076b43b95c00a958ec8b16d5a7cb0d7", + "0xb02adc9aaa20e0368a989c2af14ff48b67233d28ebee44ff3418bb0473592e6b681af1cc45450bd4b175df9051df63d9", + "0x8d87f0714acee522eb58cec00360e762adc411901dba46adc9227124fa70ee679f9a47e91a6306d6030dd4eb8de2f3c1", + "0x8be54cec21e74bcc71de29dc621444263737db15f16d0bb13670f64e42f818154e04b484593d19ef95f2ee17e4b3fe21", + "0xab8e20546c1db38d31493b5d5f535758afb17e459645c1b70813b1cf7d242fd5d1f4354a7c929e8f7259f6a25302e351", + "0x89f035a1ed8a1e302ac893349ba8ddf967580fcb6e73d44af09e3929cde445e97ff60c87dafe489e2c0ab9c9986cfa00", + "0x8b2b0851a795c19191a692af55f7e72ad2474efdc5401bc3733cfdd910e34c918aaebe69d5ea951bdddf3c01cabbfc67", + "0xa4edb52c2b51495ccd1ee6450fc14b7b3ede8b3d106808929d02fb31475bacb403e112ba9c818d2857651e508b3a7dd1", + "0x9569341fded45d19f00bcf3cbf3f20eb2b4d82ef92aba3c8abd95866398438a2387437e580d8b646f17cf6fde8c5af23", + "0xaa4b671c6d20f72f2f18a939a6ff21cc37e0084b44b4a717f1be859a80b39fb1be026b3205adec2a66a608ec2bcd578f", + "0x94902e980de23c4de394ad8aec91b46f888d18f045753541492bfbb92c59d3daa8de37ae755a6853744af8472ba7b72b", + "0xaf651ef1b2a0d30a7884557edfad95b6b5d445a7561caebdc46a485aedd25932c62c0798465c340a76f6feaa196dd712", + "0xb7b669b8e5a763452128846dd46b530dca4893ace5cc5881c7ddcd3d45969d7e73fbebdb0e78aa81686e5f7b22ec5759", + "0x82507fd4ebe9fa656a7f2e084d64a1fa6777a2b0bc106d686e2d9d2edafc58997e58cb6bfd0453b2bf415704aa82ae62", + "0xb40bce2b42b88678400ecd52955bbdadd15f8b9e1b3751a1a3375dc0efb5ca3ee258cf201e1140b3c09ad41217d1d49e", + "0xb0210d0cbb3fbf3b8cdb39e862f036b0ff941cd838e7aaf3a8354e24246e64778d22f3de34572e6b2a580614fb6425be", + "0x876693cba4301b251523c7d034108831df3ce133d8be5a514e7a2ca494c268ca0556fa2ad8310a1d92a16b55bcd99ea9", + "0x8660281406d22a4950f5ef050bf71dd3090edb16eff27fa29ef600cdea628315e2054211ed2cc6eaf8f2a1771ef689fd", + "0xa610e7e41e41ab66955b809ba4ade0330b8e9057d8efc9144753caed81995edeb1a42a53f93ce93540feca1fae708dac", + "0xa49e2c176a350251daef1218efaccc07a1e06203386ede59c136699d25ca5cb2ac1b800c25b28dd05678f14e78e51891", + "0x83e0915aa2b09359604566080d411874af8c993beba97d4547782fdbe1a68e59324b800ff1f07b8db30c71adcbd102a8", + "0xa19e84e3541fb6498e9bb8a099c495cbfcad113330e0262a7e4c6544495bb8a754b2208d0c2d895c93463558013a5a32", + "0x87f2bd49859a364912023aca7b19a592c60214b8d6239e2be887ae80b69ebdeb59742bdebcfa73a586ab23b2c945586c", + "0xb8e8fdddae934a14b57bc274b8dcd0d45ebb95ddbaabef4454e0f6ce7d3a5a61c86181929546b3d60c447a15134d08e1", + "0x87e0c31dcb736ea4604727e92dc1d9a3cf00adcff79df3546e02108355260f3dd171531c3c0f57be78d8b28058fcc8c0", + "0x9617d74e8f808a4165a8ac2e30878c349e1c3d40972006f0787b31ea62d248c2d9f3fc3da83181c6e57e95feedfd0e8c", + "0x8949e2cee582a2f8db86e89785a6e46bc1565c2d8627d5b6bf43ba71ffadfab7e3c5710f88dcb5fb2fc6edf6f4fae216", + "0xad3fa7b0edceb83118972a2935a09f409d09a8db3869f30be3a76f67aa9fb379cabb3a3aff805ba023a331cad7d7eb64", + "0x8c95718a4112512c4efbd496be38bf3ca6cdcaad8a0d128f32a3f9aae57f3a57bdf295a3b372a8c549fda8f4707cffed", + "0x88f3261d1e28a58b2dee3fcc799777ad1c0eb68b3560f9b4410d134672d9533532a91ea7be28a041784872632d3c9d80", + "0xb47472a41d72dd2e8b72f5c4f8ad626737dde3717f63d6bc776639ab299e564cbad0a2ad5452a07f02ff49a359c437e5", + "0x9896d21dc2e8aad87b76d6df1654f10cd7bceed4884159d50a818bea391f8e473e01e14684814c7780235f28e69dca6e", + "0x82d47c332bbd31bbe83b5eb44a23da76d4a7a06c45d7f80f395035822bc27f62f59281d5174e6f8e77cc9b5c3193d6f0", + "0x95c74cd46206e7f70c9766117c34c0ec45c2b0f927a15ea167901a160e1530d8522943c29b61e03568aa0f9c55926c53", + "0xa89d7757825ae73a6e81829ff788ea7b3d7409857b378ebccd7df73fdbe62c8d9073741cf038314971b39af6c29c9030", + "0x8c1cd212d0b010905d560688cfc036ae6535bc334fa8b812519d810b7e7dcf1bb7c5f43deaa40f097158358987324a7f", + "0xb86993c383c015ed8d847c6b795164114dd3e9efd25143f509da318bfba89389ea72a420699e339423afd68b6512fafb", + "0x8d06bd379c6d87c6ed841d8c6e9d2d0de21653a073725ff74be1934301cc3a79b81ef6dd0aad4e7a9dc6eac9b73019bc", + "0x81af4d2d87219985b9b1202d724fe39ef988f14fef07dfe3c3b11714e90ffba2a97250838e8535eb63f107abfe645e96", + "0x8c5e0af6330a8becb787e4b502f34f528ef5756e298a77dc0c7467433454347f3a2e0bd2641fbc2a45b95e231c6e1c02", + "0x8e2a8f0f04562820dc8e7da681d5cad9fe2e85dd11c785fb6fba6786c57a857e0b3bd838fb849b0376c34ce1665e4837", + "0xa39be8269449bfdfc61b1f62077033649f18dae9bef7c6163b9314ca8923691fb832f42776f0160b9e8abd4d143aa4e1", + "0x8c154e665706355e1cc98e0a4cabf294ab019545ba9c4c399d666e6ec5c869ca9e1faf8fb06cd9c0a5c2f51a7d51b70a", + "0xa046a7d4de879d3ebd4284f08f24398e9e3bf006cd4e25b5c67273ade248689c69affff92ae810c07941e4904296a563", + "0xafd94c1cb48758e5917804df03fb38a6da0e48cd9b6262413ea13b26973f9e266690a1b7d9d24bbaf7e82718e0e594b0", + "0x859e21080310c8d6a38e12e2ac9f90a156578cdeb4bb2e324700e97d9a5511cd6045dc39d1d0de3f94aeed043a24119d", + "0xa219fb0303c379d0ab50893264919f598e753aac9065e1f23ef2949abc992577ab43c636a1d2c089203ec9ddb941e27d", + "0xb0fdb639d449588a2ca730afcba59334e7c387342d56defdfb7ef79c493f7fd0e5277eff18e7203e756c7bdda5803047", + "0x87f9c3b7ed01f54368aca6dbcf2f6e06bff96e183c4b2c65f8baa23b377988863a0a125d5cdd41a072da8462ced4c070", + "0x99ef7a5d5ac2f1c567160e1f8c95f2f38d41881850f30c461a205f7b1b9fb181277311333839b13fb3ae203447e17727", + "0xaeaca9b1c2afd24e443326cc68de67b4d9cedb22ad7b501a799d30d39c85bb2ea910d4672673e39e154d699e12d9b3dc", + "0xa11675a1721a4ba24dd3d0e4c3c33a6edf4cd1b9f6b471070b4386c61f77452266eae6e3f566a40cfc885eada9a29f23", + "0xb228334445e37b9b49cb4f2cc56b454575e92173ddb01370a553bba665adadd52df353ad74470d512561c2c3473c7bb9", + "0xa18177087c996572d76f81178d18ed1ceebc8362a396348ce289f1d8bd708b9e99539be6fccd4acb1112381cfc5749b4", + "0x8e7b8bf460f0d3c99abb19803b9e43422e91507a1c0c22b29ee8b2c52d1a384da4b87c292e28eff040db5be7b1f8641f", + "0xb03d038d813e29688b6e6f444eb56fec3abba64c3d6f890a6bcf2e916507091cdb2b9d2c7484617be6b26552ed1c56cb", + "0xa1c88ccd30e934adfc5494b72655f8afe1865a84196abfb376968f22ddc07761210b6a9fb7638f1413d1b4073d430290", + "0x961b714faebf172ad2dbc11902461e286e4f24a99a939152a53406117767682a571057044decbeb3d3feef81f4488497", + "0xa03dc4059b46effdd786a0a03cc17cfee8585683faa35bb07936ded3fa3f3a097f518c0b8e2db92fd700149db1937789", + "0xadf60180c99ca574191cbcc23e8d025b2f931f98ca7dfcebfc380226239b6329347100fcb8b0fcb12db108c6ad101c07", + "0x805d4f5ef24d46911cbf942f62cb84b0346e5e712284f82b0db223db26d51aabf43204755eb19519b00e665c7719fcaa", + "0x8dea7243e9c139662a7fe3526c6c601eee72fd8847c54c8e1f2ad93ef7f9e1826b170afe58817dac212427164a88e87f", + "0xa2ba42356606d651b077983de1ad643650997bb2babb188c9a3b27245bb65d2036e46667c37d4ce02cb1be5ae8547abe", + "0xaf2ae50b392bdc013db2d12ce2544883472d72424fc767d3f5cb0ca2d973fc7d1f425880101e61970e1a988d0670c81b", + "0x98e6bec0568d3939b31d00eb1040e9b8b2a35db46ddf4369bdaee41bbb63cc84423d29ee510a170fb5b0e2df434ba589", + "0x822ff3cd12fbef4f508f3ca813c04a2e0b9b799c99848e5ad3563265979e753ee61a48f6adc2984a850f1b46c1a43d35", + "0x891e8b8b92a394f36653d55725ef514bd2e2a46840a0a2975c76c2a935577f85289026aaa74384da0afe26775cbddfb9", + "0xb2a3131a5d2fe7c8967047aa66e4524babae941d90552171cc109527f345f42aa0df06dcbb2fa01b33d0043917bbed69", + "0x80c869469900431f3eeefafdbe07b8afd8cee7739e659e6d0109b397cacff85a88247698f87dc4e2fe39a592f250ac64", + "0x9091594f488b38f9d2bb5df49fd8b4f8829d9c2f11a197dd1431ed5abbc5c954bbde3387088f9ee3a5a834beb7619bce", + "0xb472e241e6956146cca57b97a8a204668d050423b4e76f857bad5b47f43b203a04c8391ba9d9c3e95093c071f9d376a1", + "0xb7dd2de0284844392f7dfb56fe7ca3ede41e27519753ffc579a0a8d2d65ceb8108d06b6b0d4c3c1a2588951297bd1a1e", + "0x902116ce70d0a079ac190321c1f48701318c05f8e69ee09694754885d33a835a849cafe56f499a2f49f6cda413ddf9a7", + "0xb18105cc736787fafaf7c3c11c448bce9466e683159dff52723b7951dff429565e466e4841d982e3aaa9ee2066838666", + "0x97ab9911f3f659691762d568ae0b7faa1047b0aed1009c319fa79d15d0db8db9f808fc385dc9a68fa388c10224985379", + "0xb2a2cba65f5b927e64d2904ba412e2bac1cf18c9c3eda9c72fb70262497ecf505b640827e2afebecf10eebbcf48ccd3e", + "0xb36a3fd677baa0d3ef0dac4f1548ff50a1730286b8c99d276a0a45d576e17b39b3cbadd2fe55e003796d370d4be43ce3", + "0xa5dfec96ca3c272566e89dc453a458909247e3895d3e44831528130bc47cc9d0a0dac78dd3cad680a4351d399d241967", + "0x8029382113909af6340959c3e61db27392531d62d90f92370a432aec3eb1e4c36ae1d4ef2ba8ec6edb4d7320c7a453f6", + "0x971d85121ea108e6769d54f9c51299b0381ece8b51d46d49c89f65bedc123bab4d5a8bc14d6f67f4f680077529cbae4c", + "0x98ff6afc01d0bec80a278f25912e1b1ebff80117adae72e31d5b9fa4d9624db4ba2065b444df49b489b0607c45e26c4c", + "0x8fa29be10fb3ab30ce25920fec0187e6e91e458947009dabb869aade7136c8ba23602682b71e390c251f3743164cbdaa", + "0xb3345c89eb1653418fe3940cf3e56a9a9c66526389b98f45ca02dd62bfb37baa69a4baaa7132d7320695f8ea6ad1fd94", + "0xb72c7f5541c9ac6b60a7ec9f5415e7fb14da03f7164ea529952a29399f3a071576608dbbcc0d45994f21f92ddbeb1e19", + "0xaa3450bb155a5f9043d0ef95f546a2e6ade167280bfb75c9f09c6f9cdb1fffb7ce8181436161a538433afa3681c7a141", + "0x92a18fecaded7854b349f441e7102b638ababa75b1b0281dd0bded6541abe7aa37d96693595be0b01fe0a2e2133d50f9", + "0x980756ddf9d2253cfe6c94960b516c94889d09e612810935150892627d2ecee9a2517e04968eea295d0106850c04ca44", + "0xae68c6ccc454318cdd92f32b11d89116a3b8350207a36d22a0f626718cad671d960090e054c0c77ac3162ae180ecfd4b", + "0x99f31f66eaaa551749ad91d48a0d4e3ff4d82ef0e8b28f3184c54e852422ba1bdafd53b1e753f3a070f3b55f3c23b6a2", + "0xa44eaeaa6589206069e9c0a45ff9fc51c68da38d4edff1d15529b7932e6f403d12b9387019c44a1488a5d5f27782a51f", + "0xb80b5d54d4b344840e45b79e621bd77a3f83fb4ce6d8796b7d6915107b3f3c34d2e7d95bdafd120f285669e5acf2437a", + "0xb36c069ec085a612b5908314d6b84c00a83031780261d1c77a0384c406867c9847d5b0845deddfa512cc04a8df2046fb", + "0xb09dbe501583220f640d201acea7ee3e39bf9eda8b91aa07b5c50b7641d86d71acb619b38d27835ce97c3759787f08e9", + "0x87403d46a2bf63170fff0b857acacf42ee801afe9ccba8e5b4aea967b68eac73a499a65ca46906c2eb4c8f27bc739faa", + "0x82b93669f42a0a2aa5e250ffe6097269da06a9c02fcd1801abbad415a7729a64f830754bafc702e64600ba47671c2208", + "0x8e3a3029be7edb8dd3ab1f8216664c8dc50d395f603736061d802cef77627db7b859ef287ed850382c13b4d22d6a2d80", + "0x968e9ec7194ff424409d182ce0259acd950c384c163c04463bc8700a40b79beba6146d22b7fa7016875a249b7b31c602", + "0x8b42c984bbe4996e0c20862059167c6bdc5164b1ffcd928f29512664459212d263e89f0f0e30eed4e672ffa5ed0b01b5", + "0x96bac54062110dada905363211133f1f15dc7e4fd80a4c6e4a83bc9a0bcbbaba11cd2c7a13debcf0985e1a954c1da66b", + "0xa16dc8a653d67a7cd7ae90b2fffac0bf1ca587005430fe5ba9403edd70ca33e38ba5661d2ed6e9d2864400d997626a62", + "0xa68ab11a570a27853c8d67e491591dcba746bfbee08a2e75ae0790399130d027ed387f41ef1d7de8df38b472df309161", + "0x92532b74886874447c0300d07eda9bbe4b41ed25349a3da2e072a93fe32c89d280f740d8ff70d5816793d7f2b97373cc", + "0x88e35711b471e89218fd5f4d0eadea8a29405af1cd81974427bc4a5fb26ed60798daaf94f726c96e779b403a2cd82820", + "0xb5c72aa4147c19f8c4f3a0a62d32315b0f4606e0a7025edc5445571eaf4daff64f4b7a585464821574dd50dbe1b49d08", + "0x9305d9b4095258e79744338683fd93f9e657367b3ab32d78080e51d54eec331edbc224fad5093ebf8ee4bd4286757eb8", + "0xb2a17abb3f6a05bcb14dc7b98321fa8b46d299626c73d7c6eb12140bf4c3f8e1795250870947af817834f033c88a59d6", + "0xb3477004837dbd8ba594e4296f960fc91ab3f13551458445e6c232eb04b326da803c4d93e2e8dcd268b4413305ff84da", + "0x924b4b2ebaafdcfdfedb2829a8bf46cd32e1407d8d725a5bd28bdc821f1bafb3614f030ea4352c671076a63494275a3f", + "0x8b81b9ef6125c82a9bece6fdcb9888a767ac16e70527753428cc87c56a1236e437da8be4f7ecfe57b9296dc3ae7ba807", + "0x906e19ec8b8edd58bdf9ae05610a86e4ea2282b1bbc1e8b00b7021d093194e0837d74cf27ac9916bdb8ec308b00da3da", + "0xb41c5185869071760ac786078a57a2ab4e2af60a890037ac0c0c28d6826f15c2cf028fddd42a9b6de632c3d550bfbc14", + "0xa646e5dec1b713ae9dfdf7bdc6cd474d5731a320403c7dfcfd666ffc9ae0cff4b5a79530e8df3f4aa9cb80568cb138e9", + "0xb0efad22827e562bd3c3e925acbd0d9425d19057868608d78c2209a531cccd0f2c43dc5673acf9822247428ffa2bb821", + "0xa94c19468d14b6f99002fc52ac06bbe59e5c472e4a0cdb225144a62f8870b3f10593749df7a2de0bd3c9476ce682e148", + "0x803864a91162f0273d49271dafaab632d93d494d1af935aefa522768af058fce52165018512e8d6774976d52bd797e22", + "0xa08711c2f7d45c68fb340ac23597332e1bcaec9198f72967b9921204b9d48a7843561ff318f87908c05a44fc35e3cc9d", + "0x91c3cad94a11a3197ae4f9461faab91a669e0dddb0371d3cab3ed9aeb1267badc797d8375181130e461eadd05099b2a2", + "0x81bdaaf48aae4f7b480fc13f1e7f4dd3023a41439ba231760409ce9292c11128ab2b0bdbbf28b98af4f97b3551f363af", + "0x8d60f9df9fd303f625af90e8272c4ecb95bb94e6efc5da17b8ab663ee3b3f673e9f6420d890ccc94acf4d2cae7a860d8", + "0xa7b75901520c06e9495ab983f70b61483504c7ff2a0980c51115d11e0744683ce022d76e3e09f4e99e698cbd21432a0d", + "0x82956072df0586562fda7e7738226f694e1c73518dd86e0799d2e820d7f79233667192c9236dcb27637e4c65ef19d493", + "0xa586beb9b6ffd06ad200957490803a7cd8c9bf76e782734e0f55e04a3dc38949de75dc607822ec405736c576cf83bca3", + "0xa179a30d00def9b34a7e85607a447eea0401e32ab5abeee1a281f2acd1cf6ec81a178020666f641d9492b1bdf66f05a3", + "0x83e129705c538787ed8e0fdc1275e6466a3f4ee21a1e6abedd239393b1df72244723b92f9d9d9339a0cab6ebf28f5a16", + "0x811bd8d1e3722b64cd2f5b431167e7f91456e8bba2cc669d3fbbce7d553e29c3c19f629fcedd2498bc26d33a24891d17", + "0xa243c030c858f1f60cccd26b45b024698cc6d9d9e6198c1ed4964a235d9f8d0baf9cde10c8e63dfaa47f8e74e51a6e85", + "0xab839eb82e23ca52663281f863b55b0a3d6d4425c33ffb4eeb1d7979488ab068bf99e2a60e82cea4dc42c56c26cbfebe", + "0x8b896f9bb21d49343e67aec6ad175b58c0c81a3ca73d44d113ae4354a0065d98eb1a5cafedaf232a2bb9cdc62152f309", + "0xaf6230340cc0b66f5bf845540ed4fc3e7d6077f361d60762e488d57834c3e7eb7eacc1b0ed73a7d134f174a01410e50c", + "0x88975e1b1af678d1b5179f72300a30900736af580dd748fd9461ef7afccc91ccd9bed33f9da55c8711a7635b800e831f", + "0xa97486bb9047391661718a54b8dd5a5e363964e495eae6c692730264478c927cf3e66dd3602413189a3699fbeae26e15", + "0xa5973c161ab38732885d1d2785fd74bf156ba34881980cba27fe239caef06b24a533ffe6dbbbeca5e6566682cc00300a", + "0xa24776e9a840afda0003fa73b415d5bd6ecd9b5c2cc842b643ee51b8c6087f4eead4d0bfbd987eb174c489a7b952ff2a", + "0xa8a6ee06e3af053b705a12b59777267c546f33ba8a0f49493af8e6df4e15cf8dd2d4fb4daf7e84c6b5d3a7363118ff03", + "0xa28e59ce6ad02c2ce725067c0123117e12ac5a52c8f5af13eec75f4a9efc4f696777db18a374fa33bcae82e0734ebd16", + "0x86dfc3b78e841c708aff677baa8ee654c808e5d257158715097c1025d46ece94993efe12c9d188252ad98a1e0e331fec", + "0xa88d0275510f242eab11fdb0410ff6e1b9d7a3cbd3658333539815f1b450a84816e6613d15aa8a8eb15d87cdad4b27a2", + "0x8440acea2931118a5b481268ff9f180ee4ede85d14a52c026adc882410825b8275caa44aff0b50c2b88d39f21b1a0696", + "0xa7c3182eab25bd6785bacf12079d0afb0a9b165d6ed327814e2177148539f249eb9b5b2554538f54f3c882d37c0a8abe", + "0x85291fbe10538d7da38efdd55a7acebf03b1848428a2f664c3ce55367aece60039f4f320b1771c9c89a35941797f717c", + "0xa2c6414eeb1234728ab0de94aa98fc06433a58efa646ca3fcbd97dbfb8d98ae59f7ce6d528f669c8149e1e13266f69c9", + "0x840c8462785591ee93aee2538d9f1ec44ba2ca61a569ab51d335ac873f5d48099ae8d7a7efa0725d9ff8f9475bfa4f56", + "0xa7065a9d02fb3673acf7702a488fbc01aa69580964932f6f40b6c2d1c386b19e50b0e104fcac24ea26c4e723611d0238", + "0xb72db6d141267438279e032c95e6106c2ccb3164b842ba857a2018f3a35f4b040da92680881eb17cd61d0920d5b8f006", + "0xa8005d6c5960e090374747307ef0be2871a7a43fa4e76a16c35d2baab808e9777b496e9f57a4218b23390887c33a0b55", + "0x8e152cea1e00a451ca47c20a1e8875873419700af15a5f38ee2268d3fbc974d4bd5f4be38008fa6f404dbdedd6e6e710", + "0xa3391aed1fcd68761f06a7d1008ec62a09b1cb3d0203cd04e300a0c91adfed1812d8bc1e4a3fd7976dc0aae0e99f52f1", + "0x967eb57bf2aa503ee0c6e67438098149eac305089c155f1762cf5e84e31f0fbf27c34a9af05621e34645c1ec96afaec8", + "0x88af97ddc4937a95ec0dcd25e4173127260f91c8db2f6eac84afb789b363705fb3196235af631c70cafd09411d233589", + "0xa32df75b3f2c921b8767638fd289bcfc61e08597170186637a7128ffedd52c798c434485ac2c7de07014f9e895c2c3d8", + "0xb0a783832153650aa0d766a3a73ec208b6ce5caeb40b87177ffc035ab03c7705ecdd1090b6456a29f5fb7e90e2fa8930", + "0xb59c8e803b4c3486777d15fc2311b97f9ded1602fa570c7b0200bada36a49ee9ef4d4c1474265af8e1c38a93eb66b18b", + "0x982f2c85f83e852022998ff91bafbb6ff093ef22cf9d5063e083a48b29175ccbd51b9c6557151409e439096300981a6c", + "0x939e3b5989fefebb9d272a954659a4eb125b98c9da6953f5e628d26266bd0525ec38304b8d56f08d65abc4d6da4a8dbb", + "0x8898212fe05bc8de7d18503cb84a1c1337cc2c09d1eeef2b475aa79185b7322bf1f8e065f1bf871c0c927dd19faf1f6d", + "0x94b0393a41cd00f724aee2d4bc72103d626a5aecb4b5486dd1ef8ac27528398edf56df9db5c3d238d8579af368afeb09", + "0x96ac564450d998e7445dd2ea8e3fc7974d575508fa19e1c60c308d83b645864c029f2f6b7396d4ff4c1b24e92e3bac37", + "0x8adf6638e18aff3eb3b47617da696eb6c4bdfbecbbc3c45d3d0ab0b12cbad00e462fdfbe0c35780d21aa973fc150285e", + "0xb53f94612f818571b5565bbb295e74bada9b5f9794b3b91125915e44d6ddcc4da25510eab718e251a09c99534d6042d9", + "0x8b96462508d77ee083c376cd90807aebad8de96bca43983c84a4a6f196d5faf6619a2351f43bfeec101864c3bf255519", + "0xaeadf34657083fc71df33bd44af73bf5281c9ca6d906b9c745536e1819ea90b56107c55e2178ebad08f3ba75b3f81c86", + "0x9784ba29b2f0057b5af1d3ab2796d439b8753f1f749c73e791037461bdfc3f7097394283105b8ab01788ea5255a96710", + "0x8756241bda159d4a33bf74faba0d4594d963c370fb6a18431f279b4a865b070b0547a6d1613cf45b8cfb5f9236bbf831", + "0xb03ebfd6b71421dfd49a30460f9f57063eebfe31b9ceaa2a05c37c61522b35bdc09d7db3ad75c76c253c00ba282d3cd2", + "0xb34e7e6341fa9d854b2d3153bdda0c4ae2b2f442ab7af6f99a0975d45725aa48e36ae5f7011edd249862e91f499687d4", + "0xb462ee09dc3963a14354244313e3444de5cc37ea5ccfbf14cd9aca8027b59c4cb2a949bc30474497cab8123e768460e6", + "0xaea753290e51e2f6a21a9a0ee67d3a2713f95c2a5c17fe41116c87d3aa77b1683761264d704df1ac34f8b873bc88ef7b", + "0x98430592afd414394f98ddfff9f280fcb1c322dbe3510f45e1e9c4bb8ee306b3e0cf0282c0ee73ebb8ba087d4d9e0858", + "0xb95d3b5aaf54ffca11f4be8d57f76e14afdb20afc859dc7c7471e0b42031e8f3d461b726ecb979bdb2f353498dfe95ea", + "0x984d17f9b11a683132e0b5a9ee5945e3ff7054c2d5c716be73b29078db1d36f54c6e652fd2f52a19da313112e97ade07", + "0xab232f756b3fff3262be418a1af61a7e0c95ceebbc775389622a8e10610508cd6784ab7960441917a83cc191c58829ea", + "0xa28f41678d6e60de76b0e36ab10e4516e53e02e9c77d2b5af3cfeee3ce94cfa30c5797bd1daab20c98e1cad83ad0f633", + "0xb55395fca84dd3ccc05dd480cb9b430bf8631ff06e24cb51d54519703d667268c2f8afcde4ba4ed16bece8cc7bc8c6e0", + "0x8a8a5392a0e2ea3c7a8c51328fab11156004e84a9c63483b64e8f8ebf18a58b6ffa8fe8b9d95af0a2f655f601d096396", + "0xab480000fe194d23f08a7a9ec1c392334e9c687e06851f083845121ce502c06b54dda8c43092bcc1035df45cc752fe9b", + "0xb265644c29f628d1c7e8e25a5e845cabb21799371814730a41a363e1bda8a7be50fee7c3996a365b7fcba4642add10db", + "0xb8a915a3c685c2d4728f6931c4d29487cad764c5ce23c25e64b1a3259ac27235e41b23bfe7ae982921b4cb84463097df", + "0x8efa7338442a4b6318145a5440fc213b97869647eeae41b9aa3c0a27ee51285b73e3ae3b4a9423df255e6add58864aa9", + "0x9106d65444f74d217f4187dfc8fcf3810b916d1e4275f94f6a86d1c4f3565b131fd6cde1fa708bc05fe183c49f14941a", + "0x948252dac8026bbbdb0a06b3c9d66ec4cf9532163bab68076fda1bd2357b69e4b514729c15aaa83b5618b1977bbc60c4", + "0xae6596ccfdf5cbbc5782efe3bb0b101bb132dbe1d568854ca24cacc0b2e0e9fabcb2ca7ab42aecec412efd15cf8cb7a2", + "0x84a0b6c198ff64fd7958dfd1b40eac9638e8e0b2c4cd8cf5d8cdf80419baee76a05184bce6c5b635f6bf2d30055476a7", + "0x8893118be4a055c2b3da593dbca51b1ae2ea2469911acfb27ee42faf3e6c3ad0693d3914c508c0b05b36a88c8b312b76", + "0xb097479e967504deb6734785db7e60d1d8034d6ca5ba9552887e937f5e17bb413fccac2c1d1082154ed76609127860ad", + "0xa0294e6b9958f244d29943debf24b00b538b3da1116269b6e452bb12dc742226712fd1a15b9c88195afeb5d2415f505c", + "0xb3cc15f635080bc038f61b615f62b5b5c6f2870586191f59476e8368a73641d6ac2f7d0c1f54621982defdb318020230", + "0x99856f49b9fe1604d917c94d09cc0ed753d13d015d30587a94e6631ffd964b214e607deb8a69a8b5e349a7edf4309206", + "0xa8571e113ea22b4b4fce41a094da8c70de37830ae32e62c65c2fa5ad06a9bc29e884b945e73d448c72b176d6ecebfb58", + "0xa9e9c6e52beb0013273c29844956b3ce291023678107cdc785f7b44eff5003462841ad8780761b86aefc6b734adde7cf", + "0x80a784b0b27edb51ef2bad3aee80e51778dcaa0f3f5d3dcb5dc5d4f4b2cf7ae35b08de6680ea9dac53f8438b92eb09ef", + "0x827b543e609ea328e97e373f70ad72d4915a2d1daae0c60d44ac637231070e164c43a2a58db80a64df1c624a042b38f9", + "0xb449c65e8195202efdcb9bdb4e869a437313b118fef8b510cbbf8b79a4e99376adb749b37e9c20b51b31ed3310169e27", + "0x8ea3028f4548a79a94c717e1ed28ad4d8725b8d6ab18b021063ce46f665c79da3c49440c6577319dab2d036b7e08f387", + "0x897798431cfb17fe39f08f5f854005dc37b1c1ec1edba6c24bc8acb3b88838d0534a75475325a5ea98b326ad47dbad75", + "0x89cf232e6303b0751561960fd4dea5754a28c594daf930326b4541274ffb03c7dd75938e411eb9a375006a70ce38097f", + "0x9727c6ae7f0840f0b6c8bfb3a1a5582ceee705e0b5c59b97def7a7a2283edd4d3f47b7971e902a3a2079e40b53ff69b8", + "0xb76ed72b122c48679d221072efc0eeea063cb205cbf5f9ef0101fd10cb1075b8628166c83577cced654e1c001c7882f7", + "0xae908c42d208759da5ee9b405df85a6532ea35c6f0f6a1288d22870f59d98edc896841b8ac890a538e6c8d1e8b02d359", + "0x809d12fe4039a0ec80dc9be6a89acaab7797e5f7f9b163378f52f9a75a1d73b2e9ae6e3dd49e32ced439783c1cabbef5", + "0xa4149530b7f85d1098ba534d69548c6c612c416e8d35992fc1f64f4deeb41e09e49c6cf7aadbed7e846b91299358fe2d", + "0xa49342eacd1ec1148b8df1e253b1c015f603c39de11fa0a364ccb86ea32d69c34fd7aa6980a1fadcd8e785a57fa46f60", + "0x87d43eff5a006dc4dddcf76cc96c656a1f3a68f19f124181feab86c6cc9a52cb9189cdbb423414defdd9bb0ca8ff1ddc", + "0x861367e87a9aa2f0f68296ba50aa5dbc5713008d260cc2c7e62d407c2063064749324c4e8156dc21b749656cfebce26b", + "0xb5303c2f72e84e170e66ae1b0fbd51b8c7a6f27476eaf5694b64e8737d5c84b51fe90100b256465a4c4156dd873cddb0", + "0xb62849a4f891415d74f434cdc1d23c4a69074487659ca96e1762466b2b7a5d8525b056b891d0feea6fe6845cba8bc7fb", + "0x923dd9e0d6590a9307e8c4c23f13bae3306b580e297a937711a8b13e8de85e41a61462f25b7d352b682e8437bf2b4ab3", + "0x9147379860cd713cd46c94b8cdf75125d36c37517fbecf81ace9680b98ce6291cd1c3e472f84249cc3b2b445e314b1b6", + "0xa808a4f17ac21e3fb5cfef404e61fae3693ca3e688d375f99b6116779696059a146c27b06de3ac36da349b0649befd56", + "0x87787e9322e1b75e66c1f0d9ea0915722a232770930c2d2a95e9478c4b950d15ab767e30cea128f9ed65893bfc2d0743", + "0x9036a6ee2577223be105defe1081c48ea7319e112fff9110eb9f61110c319da25a6cea0464ce65e858635b079691ef1f", + "0xaf5548c7c24e1088c23b57ee14d26c12a83484c9fd9296edf1012d8dcf88243f20039b43c8c548c265ef9a1ffe9c1c88", + "0xa0fff520045e14065965fb8accd17e878d3fcaf9e0af2962c8954e50be6683d31fa0bf4816ab68f08630dbac6bfce52a", + "0xb4c1b249e079f6ae1781af1d97a60b15855f49864c50496c09c91fe1946266915b799f0406084d7783f5b1039116dd8b", + "0x8b0ffa5e7c498cb3879dddca34743b41eee8e2dea3d4317a6e961b58adb699ef0c92400c068d5228881a2b08121226bf", + "0x852ae8b19a1d80aa8ae5382e7ee5c8e7670ceb16640871c56b20b96b66b3b60e00015a3dde039446972e57b49a999ddd", + "0xa49942f04234a7d8492169da232cfff8051df86e8e1ba3db46aede02422c689c87dc1d99699c25f96cb763f5ca0983e5", + "0xb04b597b7760cf5dcf411ef896d1661e6d5b0db3257ac2cf64b20b60c6cc18fa10523bb958a48d010b55bac7b02ab3b1", + "0xa494591b51ea8285daecc194b5e5bd45ae35767d0246ac94fae204d674ee180c8e97ff15f71f28b7aeb175b8aea59710", + "0x97d2624919e78406e7460730680dea8e71c8571cf988e11441aeea54512b95bd820e78562c99372d535d96f7e200d20d", + "0xac693ddb00e48f76e667243b9b6a7008424043fb779e4f2252330285232c3fccac4da25cbd6d95fe9ad959ff305a91f6", + "0x8d20ca0a71a64a3f702a0825bb46bd810d03bebfb227683680d474a52f965716ff99e19a165ebaf6567987f4f9ee3c94", + "0xa5c516a438f916d1d68ca76996404792e0a66e97b7f18fc54c917bf10cf3211b62387932756e39e67e47b0bd6e88385a", + "0xb089614d830abc0afa435034cec7f851f2f095d479cacf1a3fb57272da826c499a52e7dcbc0eb85f4166fb94778e18e9", + "0xa8dacc943765d930848288192f4c69e2461c4b9bc6e79e30eeef9a543318cf9ae9569d6986c65c5668a89d49993f8e07", + "0xab5a9361fa339eec8c621bdad0a58078983abd8942d4282b22835d7a3a47e132d42414b7c359694986f7db39386c2e19", + "0x94230517fb57bd8eb26c6f64129b8b2abd0282323bf7b94b8bac7fab27b4ecc2c4290c294275e1a759de19f2216134f3", + "0xb8f158ea5006bc3b90b285246625faaa6ac9b5f5030dc69701b12f3b79a53ec7e92eeb5a63bbd1f9509a0a3469ff3ffc", + "0x8b6944fd8cb8540957a91a142fdcda827762aa777a31e8810ca6d026e50370ee1636fc351724767e817ca38804ebe005", + "0x82d1ee40fe1569c29644f79fa6c4033b7ed45cd2c3b343881f6eb0de2e79548fded4787fae19bed6ee76ed76ff9f2f11", + "0xa8924c7035e99eaed244ca165607e7e568b6c8085510dcdbaf6ebdbed405af2e6c14ee27d94ffef10d30aa52a60bf66d", + "0x956f82a6c2ae044635e85812581e4866c5fa2f427b01942047d81f6d79a14192f66fbbe77c9ffeaef4e6147097fdd2b5", + "0xb1100255a1bcf5e05b6aff1dfeb6e1d55b5d68d43a7457ba10cc76b61885f67f4d0d5179abda786e037ae95deb8eea45", + "0x99510799025e3e5e8fbf06dedb14c060c6548ba2bda824f687d3999dc395e794b1fb6514b9013f3892b6cf65cb0d65aa", + "0x8f9091cebf5e9c809aab415942172258f894e66e625d7388a05289183f01b8d994d52e05a8e69f784fba41db9ea357f0", + "0xa13d2eeb0776bdee9820ecb6693536720232848c51936bb4ef4fe65588d3f920d08a21907e1fdb881c1ad70b3725e726", + "0xa68b8f18922d550284c5e5dc2dda771f24c21965a6a4d5e7a71678178f46df4d8a421497aad8fcb4c7e241aba26378a0", + "0x8b7601f0a3c6ad27f03f2d23e785c81c1460d60100f91ea9d1cab978aa03b523150206c6d52ce7c7769c71d2c8228e9e", + "0xa8e02926430813caa851bb2b46de7f0420f0a64eb5f6b805401c11c9091d3b6d67d841b5674fa2b1dce0867714124cd8", + "0xb7968ecba568b8193b3058400af02c183f0a6df995a744450b3f7e0af7a772454677c3857f99c140bbdb2a09e832e8e0", + "0x8f20b1e9ba87d0a3f35309b985f3c18d2e8800f1ca7f0c52cadef773f1496b6070c936eea48c4a1cae83fd2524e9d233", + "0x88aef260042db0d641a51f40639dbeeefa9e9811df30bee695f3791f88a2f84d318f04e8926b7f47bf25956cb9e3754f", + "0x9725345893b647e9ba4e6a29e12f96751f1ae25fcaec2173e9a259921a1a7edb7a47159b3c8767e44d9e2689f5aa0f72", + "0x8c281e6f72752cb11e239e4df9341c45106eb7993c160e54423c2bffe10bc39d42624b45a1f673936ef2e1a02fc92f1a", + "0x90aba2f68bddb2fcce6c51430dacdfeec43ea8dc379660c99095df11017691ccf5faa27665cf4b9f0eea7728ae53c327", + "0xb7022695c16521c5704f49b7ddbdbec9b5f57ce0ceebe537bc0ebb0906d8196cc855a9afeb8950a1710f6a654464d93f", + "0x8fe1b9dd3c6a258116415d36e08374e094b22f0afb104385a5da48be17123e86fb8327baacc4f0d9ebae923d55d99bb5", + "0x817e85d8e3d19a4cbc1dec31597142c2daa4871bda89c2177fa719c00eda3344eb08b82eb92d4aa91a9eaacb3fc09783", + "0xb59053e1081d2603f1ca0ba553804d6fa696e1fd996631db8f62087b26a40dfef02098b0326bb75f99ec83b9267ca738", + "0x990a173d857d3ba81ff3789b931bfc9f5609cde0169b7f055fa3cb56451748d593d62d46ba33f80f9cafffe02b68dd14", + "0xb0c538dbba4954b809ab26f9f94a3cf1dcb77ce289eaec1d19f556c0ae4be1fa03af4a9b7057837541c3cc0a80538736", + "0xac3ba42f5f44f9e1fc453ce49c4ab79d0e1d5c42d3b30b1e098f3ab3f414c4c262fa12fb2be249f52d4aaf3c5224beb9", + "0xaf47467eb152e59870e21f0d4da2f43e093daf40180ab01438030684b114d025326928eaab12c41b81a066d94fce8436", + "0x98d1b58ba22e7289b1c45c79a24624f19b1d89e00f778eef327ec4856a9a897278e6f1a9a7e673844b31dde949153000", + "0x97ccb15dfadc7c59dca08cfe0d22df2e52c684cf97de1d94bc00d7ba24e020025130b0a39c0f4d46e4fc872771ee7875", + "0xb699e4ed9a000ff96ca296b2f09dce278832bc8ac96851ff3cff99ed3f6f752cfc0fea8571be28cd9b5a7ec36f1a08ee", + "0xb9f49f0edb7941cc296435ff0a912e3ad16848ee8765ab5f60a050b280d6ea585e5b34051b15f6b8934ef01ceb85f648", + "0xac3893df7b4ceab23c6b9054e48e8ba40d6e5beda8fbe90b814f992f52494186969b35d8c4cdc3c99890a222c9c09008", + "0xa41293ad22fae81dea94467bc1488c3707f3d4765059173980be93995fa4fcc3c9340796e3eed0beeb0ba0d9bb4fa3aa", + "0xa0543e77acd2aeecde13d18d258aeb2c7397b77f17c35a1992e8666ea7abcd8a38ec6c2741bd929abba2f766138618cc", + "0x92e79b22bc40e69f6527c969500ca543899105837b6b1075fa1796755c723462059b3d1b028e0b3df2559fa440e09175", + "0xa1fa1eac8f41a5197a6fb4aa1eae1a031c89f9c13ff9448338b222780cf9022e0b0925d930c37501a0ef7b2b00fdaf83", + "0xb3cb29ff73229f0637335f28a08ad8c5f166066f27c6c175164d0f26766a927f843b987ee9b309ed71cbf0a65d483831", + "0x84d4ab787f0ac00f104f4a734dc693d62d48c2aeb03913153da62c2ae2c27d11b1110dcef8980368dd84682ea2c1a308", + "0xab6a8e4bbc78d4a7b291ad3e9a8fe2d65f640524ba3181123b09d2d18a9e300e2509ccf7000fe47e75b65f3e992a2e7e", + "0xb7805ebe4f1a4df414003dc10bca805f2ab86ca75820012653e8f9b79c405196b0e2cab099f2ab953d67f0d60d31a0f9", + "0xb12c582454148338ea605d22bd00a754109063e22617f1f8ac8ddf5502c22a181c50c216c3617b9852aa5f26af56b323", + "0x86333ad9f898947e31ce747728dc8c887479e18d36ff3013f69ebef807d82c6981543b5c3788af93c4d912ba084d3cba", + "0xb514efa310dc4ad1258add138891e540d8c87142a881b5f46563cc58ecd1488e6d3a2fca54c0b72a929f3364ca8c333e", + "0xaa0a30f92843cf2f484066a783a1d75a7aa6f41f00b421d4baf20a6ac7886c468d0eea7ca8b17dd22f4f74631b62b640", + "0xb3b7dc63baec9a752e8433c0cdee4d0f9bc41f66f2b8d132faf925eef9cf89aae756fc132c45910f057122462605dc10", + "0xb9b8190dac5bfdeb59fd44f4da41a57e7f1e7d2c21faba9da91fa45cbeca06dcf299c9ae22f0c89ece11ac46352d619f", + "0x89f8cf36501ad8bdfeab863752a9090e3bfda57cf8fdeca2944864dc05925f501e252c048221bcc57136ab09a64b64b2", + "0xb0cbfaf317f05f97be47fc9d69eda2dd82500e00d42612f271a1fe24626408c28881f171e855bd5bd67409f9847502b4", + "0xa7c21a8fcede581bfd9847b6835eda62ba250bea81f1bb17372c800a19c732abe03064e64a2f865d974fb636cab4b859", + "0x95f9df524ba7a4667351696c4176b505d8ea3659f5ff2701173064acc624af69a0fad4970963736383b979830cb32260", + "0x856a74fe8b37a2e3afeac858c8632200485d438422a16ae3b29f359e470e8244995c63ad79c7e007ed063f178d0306fd", + "0xb37faa4d78fdc0bb9d403674dbea0176c2014a171c7be8527b54f7d1a32a76883d3422a3e7a5f5fcc5e9b31b57822eeb", + "0x8d37234d8594ec3fe75670b5c9cc1ec3537564d4739b2682a75b18b08401869a4264c0f264354219d8d896cded715db4", + "0xb5289ee5737f0e0bde485d32096d23387d68dab8f01f47821ab4f06cc79a967afe7355e72dc0c751d96b2747b26f6255", + "0x9085e1fdf9f813e9c3b8232d3c8863cd84ab30d45e8e0d3d6a0abd9ebc6fd70cdf749ff4d04390000e14c7d8c6655fc7", + "0x93a388c83630331eca4da37ea4a97b3b453238af474817cc0a0727fd3138dcb4a22de38c04783ec829c22cb459cb4e8e", + "0xa5377116027c5d061dbe24c240b891c08cdd8cd3f0899e848d682c873aff5b8132c1e7cfe76d2e5ed97ee0eb1d42cb68", + "0xa274c84b04338ed28d74683e2a7519c2591a3ce37c294d6f6e678f7d628be2db8eff253ede21823e2df7183e6552f622", + "0x8bc201147a842453a50bec3ac97671397bc086d6dfc9377fa38c2124cdc286abda69b7324f47d64da094ae011d98d9d9", + "0x9842d0c066c524592b76fbec5132bc628e5e1d21c424bec4555efca8619cc1fd8ea3161febcb8b9e8ab54702f4e815e2", + "0xa19191b713a07efe85c266f839d14e25660ee74452e6c691cd9997d85ae4f732052d802d3deb018bdd847caa298a894b", + "0xa24f71fc0db504da4e287dd118a4a74301cbcd16033937ba2abc8417956fcb4ae19b8e63b931795544a978137eff51cb", + "0xa90eec4a6a3a4b8f9a5b93d978b5026fcf812fe65585b008d7e08c4aaf21195a1d0699f12fc16f79b6a18a369af45771", + "0x8b551cf89737d7d06d9b3b9c4c1c73b41f2ea0af4540999c70b82dabff8580797cf0a3caf34c86c59a7069eb2e38f087", + "0xb8d312e6c635e7a216a1cda075ae77ba3e1d2fd501dc31e83496e6e81ed5d9c7799f8e578869c2e0e256fb29f5de10a7", + "0x8d144bdb8cae0b2cdb5b33d44bbc96984a5925202506a8cc65eb67ac904b466f5a7fe3e1cbf04aa785bbb7348c4bb73c", + "0xa101b3d58b7a98659244b88de0b478b3fb87dc5fc6031f6e689b99edf498abd43e151fd32bd4bbd240e0b3e59c440359", + "0x907453abca7d8e7151a05cc3d506c988007692fe7401395dc93177d0d07d114ab6cca0cc658eb94c0223fe8658295cad", + "0x825329ffbe2147ddb68f63a0a67f32d7f309657b8e5d9ab5bb34b3730bfa2c77a23eaaadb05def7d9f94a9e08fdc1e96", + "0x88ee923c95c1dac99ae7ed6067906d734d793c5dc5d26339c1bb3314abe201c5dccb33b9007351885eb2754e9a8ea06c", + "0x98bc9798543f5f1adc9f2cfcfa72331989420e9c3f6598c45269f0dc9b7c8607bbeaf03faa0aea2ddde2b8f17fdceff5", + "0x8ee87877702a79aef923ab970db6fa81561b3c07d5bf1a072af0a7bad765b4cbaec910afe1a91703feacc7822fa38a94", + "0x8060b9584aa294fe8adc2b22f67e988bc6da768eae91e429dcc43ddc53cfcc5d6753fdc1b420b268c7eb2fb50736a970", + "0xb344a5524d80a2f051870c7001f74fcf348a70fcf78dbd20c6ff9ca85d81567d2318c8b8089f2c4f195d6aec9fc15fa6", + "0x8f5a5d893e1936ed062149d20eb73d98b62b7f50ab5d93a6429c03656b36688d1c80cb5010e4977491e51fa0d7dd35d5", + "0x86fa32ebbf97328c5f5f15564e1238297e289ec3219b9a741724e9f3ae8d5c15277008f555863a478b247ba5dc601d44", + "0x9557e55377e279f4b6b5e0ffe01eca037cc13aac242d67dfcd0374a1e775c5ed5cb30c25fe21143fee54e3302d34a3ea", + "0x8cb6bcbc39372d23464a416ea7039f57ba8413cf3f00d9a7a5b356ab20dcb8ed11b3561f7bce372b8534d2870c7ee270", + "0xb5d59075cb5abde5391f64b6c3b8b50adc6e1f654e2a580b6d6d6eff3f4fbdd8fffc92e06809c393f5c8eab37f774c4b", + "0xafcfb6903ef13e493a1f7308675582f15af0403b6553e8c37afb8b2808ad21b88b347dc139464367dc260df075fea1ad", + "0x810fbbe808375735dd22d5bc7fc3828dc49fdd22cc2d7661604e7ac9c4535c1df578780affb3b895a0831640a945bcad", + "0x8056b0c678803b416f924e09a6299a33cf9ad7da6fe1ad7accefe95c179e0077da36815fde3716711c394e2c5ea7127f", + "0x8b67403702d06979be19f1d6dc3ec73cc2e81254d6b7d0cc49cd4fdda8cd51ab0835c1d2d26fc0ecab5df90585c2f351", + "0x87f97f9e6d4be07e8db250e5dd2bffdf1390665bc5709f2b631a6fa69a7fca958f19bd7cc617183da1f50ee63e9352b5", + "0xae151310985940471e6803fcf37600d7fa98830613e381e00dab943aec32c14162d51c4598e8847148148000d6e5af5c", + "0x81eb537b35b7602c45441cfc61b27fa9a30d3998fad35a064e05bc9479e9f10b62eba2b234b348219eea3cadcaac64bb", + "0x8a441434934180ab6f5bc541f86ebd06eadbee01f438836d797e930fa803a51510e005c9248cecc231a775b74d12b5e9", + "0x81f3c250a27ba14d8496a5092b145629eb2c2e6a5298438670375363f57e2798207832c8027c3e9238ad94ecdadfc4df", + "0xa6217c311f2f3db02ceaa5b6096849fe92b6f4b6f1491535ef8525f6ccee6130bed2809e625073ecbaddd4a3eb3df186", + "0x82d1c396f0388b942cf22b119d7ef1ad03d3dad49a74d9d01649ee284f377c8daddd095d596871669e16160299a210db", + "0xa40ddf7043c5d72a7246bd727b07f7fff1549f0e443d611de6f9976c37448b21664c5089c57f20105102d935ab82f27b", + "0xb6c03c1c97adf0c4bf4447ec71366c6c1bff401ba46236cd4a33d39291e7a1f0bb34bd078ba3a18d15c98993b153a279", + "0x8a94f5f632068399c359c4b3a3653cb6df2b207379b3d0cdace51afdf70d6d5cce6b89a2b0fee66744eba86c98fb21c2", + "0xb2f19e78ee85073f680c3bba1f07fd31b057c00b97040357d97855b54a0b5accb0d3b05b2a294568fcd6a4be6f266950", + "0xa74632d13bbe2d64b51d7a9c3ae0a5a971c19f51cf7596a807cea053e6a0f3719700976d4e394b356c0329a2dced9aa2", + "0xafef616d341a9bc94393b8dfba68ff0581436aa3a3adb7c26a1bbf2cf19fa877066191681f71f17f3cd6f9cf6bf70b5a", + "0x8ce96d93ae217408acf7eb0f9cbb9563363e5c7002e19bbe1e80760bc9d449daee2118f3878b955163ed664516b97294", + "0x8414f79b496176bc8b8e25f8e4cfee28f4f1c2ddab099d63d2aca1b6403d26a571152fc3edb97794767a7c4686ad557c", + "0xb6c61d01fd8ce087ef9f079bf25bf10090db483dd4f88c4a786d31c1bdf52065651c1f5523f20c21e75cea17df69ab73", + "0xa5790fd629be70545093631efadddc136661f63b65ec682609c38ef7d3d7fa4e56bdf94f06e263bc055b90cb1c6bcefe", + "0xb515a767e95704fb7597bca9e46f1753abacdc0e56e867ee3c6f4cd382643c2a28e65312c05ad040eaa3a8cbe7217a65", + "0x8135806a02ead6aa92e9adb6fefb91349837ab73105aaa7be488ef966aa8dfaafdfa64bbae30fcbfa55dd135a036a863", + "0x8f22435702716d76b1369750694540742d909d5e72b54d0878245fab7c269953b1c6f2b29c66f08d5e0263ca3a731771", + "0x8e0f8a8e8753e077dac95848212aeffd51c23d9b6d611df8b102f654089401954413ecbedc6367561ca599512ae5dda7", + "0x815a9084e3e2345f24c5fa559deec21ee1352fb60f4025c0779be65057f2d528a3d91593bd30d3a185f5ec53a9950676", + "0x967e6555ccba395b2cc1605f8484c5112c7b263f41ce8439a99fd1c71c5ed14ad02684d6f636364199ca48afbbde13be", + "0x8cd0ccf17682950b34c796a41e2ea7dd5367aba5e80a907e01f4cdc611e4a411918215e5aebf4292f8b24765d73314a6", + "0xa58bf1bbb377e4b3915df6f058a0f53b8fb8130fdec8c391f6bc82065694d0be59bb67ffb540e6c42cc8b380c6e36359", + "0x92af3151d9e6bfb3383d85433e953c0160859f759b0988431ec5893542ba40288f65db43c78a904325ef8d324988f09d", + "0x8011bbb05705167afb47d4425065630f54cb86cd462095e83b81dfebf348f846e4d8fbcf1c13208f5de1931f81da40b9", + "0x81c743c104fc3cb047885c9fa0fb9705c3a83ee24f690f539f4985509c3dafd507af3f6a2128276f45d5939ef70c167f", + "0xa2c9679b151c041aaf5efeac5a737a8f70d1631d931609fca16be1905682f35e291292874cb3b03f14994f98573c6f44", + "0xa4949b86c4e5b1d5c82a337e5ce6b2718b1f7c215148c8bfb7e7c44ec86c5c9476048fc5c01f57cb0920876478c41ad6", + "0x86c2495088bd1772152e527a1da0ef473f924ea9ab0e5b8077df859c28078f73c4e22e3a906b507fdf217c3c80808b5c", + "0x892e0a910dcf162bcea379763c3e2349349e4cda9402949255ac4a78dd5a47e0bf42f5bd0913951576b1d206dc1e536a", + "0xa7009b2c6b396138afe4754b7cc10dee557c51c7f1a357a11486b3253818531f781ea8107360c8d4c3b1cd96282353c0", + "0x911763ef439c086065cc7b4e57484ed6d693ea44acee4b18c9fd998116da55fbe7dcb8d2a0f0f9b32132fca82d73dff6", + "0xa722000b95a4a2d40bed81870793f15ba2af633f9892df507f2842e52452e02b5ea8dea6a043c2b2611d82376e33742a", + "0x9387ac49477bd719c2f92240d0bdfcf9767aad247ca93dc51e56106463206bc343a8ec855eb803471629a66fffb565d6", + "0x92819a1fa48ab4902939bb72a0a4e6143c058ea42b42f9bc6cea5df45f49724e2530daf3fc4f097cceefa2a8b9db0076", + "0x98eac7b04537653bc0f4941aae732e4b1f84bd276c992c64a219b8715eb1fb829b5cbd997d57feb15c7694c468f95f70", + "0xb275e7ba848ce21bf7996e12dbeb8dadb5d0e4f1cb5a0248a4f8f9c9fe6c74e3c93f4b61edbcb0a51af5a141e1c14bc7", + "0x97243189285aba4d49c53770c242f2faf5fd3914451da4931472e3290164f7663c726cf86020f8f181e568c72fd172d1", + "0x839b0b3c25dd412bee3dc24653b873cc65454f8f16186bb707bcd58259c0b6765fa4c195403209179192a4455c95f3b8", + "0x8689d1a870514568a074a38232e2ceb4d7df30fabeb76cff0aed5b42bf7f02baea12c5fadf69f4713464dbd52aafa55f", + "0x8958ae7b290f0b00d17c3e9fdb4dbf168432b457c7676829299dd428984aba892de1966fc106cfc58a772862ecce3976", + "0xa422bc6bd68b8870cfa5bc4ce71781fd7f4368b564d7f1e0917f6013c8bbb5b240a257f89ecfdbecb40fe0f3aa31d310", + "0xaa61f78130cebe09bc9a2c0a37f0dd57ed2d702962e37d38b1df7f17dc554b1d4b7a39a44182a452ce4c5eb31fa4cfcc", + "0xb7918bd114f37869bf1a459023386825821bfadce545201929d13ac3256d92a431e34f690a55d944f77d0b652cefeffc", + "0x819bba35fb6ace1510920d4dcff30aa682a3c9af9022e287751a6a6649b00c5402f14b6309f0aeef8fce312a0402915e", + "0x8b7c9ad446c6f63c11e1c24e24014bd570862b65d53684e107ba9ad381e81a2eaa96731b4b33536efd55e0f055071274", + "0x8fe79b53f06d33386c0ec7d6d521183c13199498594a46d44a8a716932c3ec480c60be398650bbfa044fa791c4e99b65", + "0x9558e10fb81250b9844c99648cf38fa05ec1e65d0ccbb18aa17f2d1f503144baf59d802c25be8cc0879fff82ed5034ad", + "0xb538a7b97fbd702ba84645ca0a63725be1e2891c784b1d599e54e3480e4670d0025526674ef5cf2f87dddf2290ba09f0", + "0x92eafe2e869a3dd8519bbbceb630585c6eb21712b2f31e1b63067c0acb5f9bdbbcbdb612db4ea7f9cc4e7be83d31973f", + "0xb40d21390bb813ab7b70a010dff64c57178418c62685761784e37d327ba3cb9ef62df87ecb84277c325a637fe3709732", + "0xb349e6fbf778c4af35fbed33130bd8a7216ed3ba0a79163ebb556e8eb8e1a7dad3456ddd700dad9d08d202491c51b939", + "0xa8fdaedecb251f892b66c669e34137f2650509ade5d38fbe8a05d9b9184bb3b2d416186a3640429bd1f3e4b903c159dd", + "0xac6167ebfee1dbab338eff7642f5e785fc21ef0b4ddd6660333fe398068cbd6c42585f62e81e4edbb72161ce852a1a4f", + "0x874b1fbf2ebe140c683bd7e4e0ab017afa5d4ad38055aaa83ee6bbef77dbc88a6ce8eb0dcc48f0155244af6f86f34c2d", + "0x903c58e57ddd9c446afab8256a6bb6c911121e6ccfb4f9b4ed3e2ed922a0e500a5cb7fa379d5285bc16e11dac90d1fda", + "0x8dae7a0cffa2fd166859cd1bf10ff82dd1932e488af377366b7efc0d5dec85f85fe5e8150ff86a79a39cefc29631733a", + "0xaa047857a47cc4dfc08585f28640420fcf105b881fd59a6cf7890a36516af0644d143b73f3515ab48faaa621168f8c31", + "0x864508f7077c266cc0cb3f7f001cb6e27125ebfe79ab57a123a8195f2e27d3799ff98413e8483c533b46a816a3557f1f", + "0x8bcd45ab1f9cbab36937a27e724af819838f66dfeb15923f8113654ff877bd8667c54f6307aaf0c35027ca11b6229bfd", + "0xb21aa34da9ab0a48fcfdd291df224697ce0c1ebc0e9b022fdee8750a1a4b5ba421c419541ed5c98b461eecf363047471", + "0xa9a18a2ab2fae14542dc336269fe612e9c1af6cf0c9ac933679a2f2cb77d3c304114f4d219ca66fe288adde30716775b", + "0xb5205989b92c58bdda71817f9a897e84100b5c4e708de1fced5c286f7a6f01ae96b1c8d845f3a320d77c8e2703c0e8b1", + "0xa364059412bbcc17b8907d43ac8e5df90bc87fd1724b5f99832d0d24559fae6fa76a74cff1d1eac8cbac6ec80b44af20", + "0xae709f2c339886b31450834cf29a38b26eb3b0779bd77c9ac269a8a925d1d78ea3837876c654b61a8fe834b3b6940808", + "0x8802581bba66e1952ac4dab36af371f66778958f4612901d95e5cac17f59165e6064371d02de8fb6fccf89c6dc8bd118", + "0xa313252df653e29c672cbcfd2d4f775089cb77be1077381cf4dc9533790e88af6cedc8a119158e7da5bf6806ad9b91a1", + "0x992a065b4152c7ef11515cd54ba9d191fda44032a01aed954acff3443377ee16680c7248d530b746b8c6dee2d634e68c", + "0xb627b683ee2b32c1ab4ccd27b9f6cce2fe097d96386fa0e5c182ad997c4c422ab8dfc03870cd830b8c774feb66537282", + "0xb823cf8a9aee03dadd013eb9efe40a201b4b57ef67efaae9f99683005f5d1bf55e950bf4af0774f50859d743642d3fea", + "0xb8a7449ffac0a3f206677097baf7ce00ca07a4d2bd9b5356fbcb83f3649b0fda07cfebad220c1066afba89e5a52abf4b", + "0xb2dd1a2f986395bb4e3e960fbbe823dbb154f823284ebc9068502c19a7609790ec0073d08bfa63f71e30c7161b6ef966", + "0x98e5236de4281245234f5d40a25b503505af140b503a035fc25a26159a9074ec81512b28f324c56ea2c9a5aa7ce90805", + "0x89070847dc8bbf5bc4ed073aa2e2a1f699cf0c2ca226f185a0671cecc54e7d3e14cd475c7752314a7a8e7476829da4bc", + "0xa9402dc9117fdb39c4734c0688254f23aed3dce94f5f53f5b7ef2b4bf1b71a67f85ab1a38ec224a59691f3bee050aeb3", + "0x957288f9866a4bf56a4204218ccc583f717d7ce45c01ea27142a7e245ad04a07f289cc044f8cf1f21d35e67e39299e9c", + "0xb2fb31ccb4e69113763d7247d0fc8edaae69b550c5c56aecacfd780c7217dc672f9fb7496edf4aba65dacf3361268e5b", + "0xb44a4526b2f1d6eb2aa8dba23bfa385ff7634572ab2afddd0546c3beb630fbfe85a32f42dd287a7fec069041411537f7", + "0x8db5a6660c3ac7fd7a093573940f068ee79a82bc17312af900b51c8c439336bc86ca646c6b7ab13aaaa008a24ca508ab", + "0x8f9899a6d7e8eb4367beb5c060a1f8e94d8a21099033ae582118477265155ba9e72176a67f7f25d7bad75a152b56e21a", + "0xa67de0e91ade8d69a0e00c9ff33ee2909b8a609357095fa12319e6158570c232e5b6f4647522efb7345ce0052aa9d489", + "0x82eb2414898e9c3023d57907a2b17de8e7eea5269029d05a94bfd7bf5685ac4a799110fbb375eb5e0e2bd16acf6458ae", + "0x94451fc7fea3c5a89ba701004a9693bab555cb622caf0896b678faba040409fdfd14a978979038b2a81e8f0abc4994d2", + "0xac879a5bb433998e289809a4a966bd02b4bf6a9c1cc276454e39c886efcf4fc68baebed575826bde577ab5aa71d735a9", + "0x880c0f8f49c875dfd62b4ddedde0f5c8b19f5687e693717f7e5c031bc580e58e13ab497d48b4874130a18743c59fdce3", + "0xb582af8d8ff0bf76f0a3934775e0b54c0e8fed893245d7d89cae65b03c8125b7237edc29dc45b4fe1a3fe6db45d280ee", + "0x89f337882ed3ae060aaee98efa20d79b6822bde9708c1c5fcee365d0ec9297f694cae37d38fd8e3d49717c1e86f078e7", + "0x826d2c1faea54061848b484e288a5f4de0d221258178cf87f72e14baaa4acc21322f8c9eab5dde612ef497f2d2e1d60b", + "0xa5333d4f227543e9cd741ccf3b81db79f2f03ca9e649e40d6a6e8ff9073e06da83683566d3b3c8d7b258c62970fb24d1", + "0xa28f08c473db06aaf4c043a2fae82b3c8cfaa160bce793a4c208e4e168fb1c65115ff8139dea06453c5963d95e922b94", + "0x8162546135cc5e124e9683bdfaa45833c18553ff06a0861c887dc84a5b12ae8cd4697f6794c7ef6230492c32faba7014", + "0xb23f0d05b74c08d6a7df1760792be83a761b36e3f8ae360f3c363fb196e2a9dd2de2e492e49d36561366e14daa77155c", + "0xb6f70d6c546722d3907c708d630dbe289771d2c8bf059c2e32b77f224696d750b4dda9b3a014debda38e7d02c9a77585", + "0x83bf4c4a9f3ca022c631017e7a30ea205ba97f7f5927cba8fc8489a4646eac6712cb821c5668c9ffe94d69d524374a27", + "0xb0371475425a8076d0dd5f733f55aabbe42d20a7c8ea7da352e736d4d35a327b2beb370dfcb05284e22cfd69c5f6c4cc", + "0xa0031ba7522c79211416c2cca3aa5450f96f8fee711552a30889910970ba13608646538781a2c08b834b140aadd7166f", + "0x99d273c80c7f2dc6045d4ed355d9fc6f74e93549d961f4a3b73cd38683f905934d359058cd1fc4da8083c7d75070487f", + "0xb0e4b0efa3237793e9dcce86d75aafe9879c5fa23f0d628649aef2130454dcf72578f9bf227b9d2b9e05617468e82588", + "0xa5ab076fa2e1c5c51f3ae101afdd596ad9d106bba7882b359c43d8548b64f528af19afa76cd6f40da1e6c5fca4def3fa", + "0x8ce2299e570331d60f6a6eff1b271097cd5f1c0e1113fc69b89c6a0f685dabea3e5bc2ac6bd789aa492ab189f89be494", + "0x91b829068874d911a310a5f9dee001021f97471307b5a3de9ec336870ec597413e1d92010ce320b619f38bed7c4f7910", + "0xb14fe91f4b07bf33b046e9285b66cb07927f3a8da0af548ac2569b4c4fb1309d3ced76d733051a20814e90dd5b75ffd1", + "0xabaab92ea6152d40f82940277c725aa768a631ee0b37f5961667f82fb990fc11e6d3a6a2752b0c6f94563ed9bb28265c", + "0xb7fe28543eca2a716859a76ab9092f135337e28109544f6bd2727728d0a7650428af5713171ea60bfc273d1c821d992c", + "0x8a4917b2ab749fc7343fc64bdf51b6c0698ff15d740cc7baf248c030475c097097d5a473bcc00d8c25817563fe0447b4", + "0xaa96156d1379553256350a0a3250166add75948fb9cde62aa555a0a9dc0a9cb7f2f7b8428aff66097bf6bfedaf14bbe2", + "0xae4ffeb9bdc76830d3eca2b705f30c1bdede6412fa064260a21562c8850c7fb611ec62bc68479fe48f692833e6f66d8d", + "0xb96543caaba9d051600a14997765d49e4ab10b07c7a92cccf0c90b309e6da334fdd6d18c96806cbb67a7801024fbd3c7", + "0x97b2b9ad76f19f500fcc94ca8e434176249f542ac66e5881a3dccd07354bdab6a2157018b19f8459437a68d8b86ba8e0", + "0xa8d206f6c5a14c80005849474fde44b1e7bcf0b2d52068f5f97504c3c035b09e65e56d1cf4b5322791ae2c2fdbd61859", + "0x936bad397ad577a70cf99bf9056584a61bd7f02d2d5a6cf219c05d770ae30a5cd902ba38366ce636067fc1dd10108d31", + "0xa77e30195ee402b84f3882e2286bf5380c0ed374a112dbd11e16cef6b6b61ab209d4635e6f35cdaaa72c1a1981d5dabe", + "0xa46ba4d3947188590a43c180757886a453a0503f79cc435322d92490446f37419c7b999fdf868a023601078070e03346", + "0x80d8d4c5542f223d48240b445d4d8cf6a75d120b060bc08c45e99a13028b809d910b534d2ac47fb7068930c54efd8da9", + "0x803be9c68c91b42b68e1f55e58917a477a9a6265e679ca44ee30d3eb92453f8c89c64eafc04c970d6831edd33d066902", + "0xb14b2b3d0dfe2bb57cee4cd72765b60ac33c1056580950be005790176543826c1d4fbd737f6cfeada6c735543244ab57", + "0xa9e480188bba1b8fb7105ff12215706665fd35bf1117bacfb6ab6985f4dbc181229873b82e5e18323c2b8f5de03258e0", + "0xa66a0f0779436a9a3999996d1e6d3000f22c2cac8e0b29cddef9636393c7f1457fb188a293b6c875b05d68d138a7cc4a", + "0x848397366300ab40c52d0dbbdafbafef6cd3dadf1503bb14b430f52bb9724188928ac26f6292a2412bc7d7aa620763c8", + "0x95466cc1a78c9f33a9aaa3829a4c8a690af074916b56f43ae46a67a12bb537a5ac6dbe61590344a25b44e8512355a4a7", + "0x8b5f7a959f818e3baf0887f140f4575cac093d0aece27e23b823cf421f34d6e4ff4bb8384426e33e8ec7b5eed51f6b5c", + "0x8d5e1368ec7e3c65640d216bcc5d076f3d9845924c734a34f3558ac0f16e40597c1a775a25bf38b187213fbdba17c93b", + "0xb4647c1b823516880f60d20c5cc38c7f80b363c19d191e8992226799718ee26b522a12ecb66556ed3d483aa4824f3326", + "0xac3abaea9cd283eb347efda4ed9086ea3acf495043e08d0d19945876329e8675224b685612a6badf8fd72fb6274902b1", + "0x8eae1ce292d317aaa71bcf6e77e654914edd5090e2e1ebab78b18bb41b9b1bc2e697439f54a44c0c8aa0d436ebe6e1a9", + "0x94dc7d1aec2c28eb43d93b111fa59aaa0d77d5a09501220bd411768c3e52208806abf973c6a452fd8292ff6490e0c9e2", + "0x8fd8967f8e506fef27d17b435d6b86b232ec71c1036351f12e6fb8a2e12daf01d0ee04451fb944d0f1bf7fd20e714d02", + "0x824e6865be55d43032f0fec65b3480ea89b0a2bf860872237a19a54bc186a85d2f8f9989cc837fbb325b7c72d9babe2c", + "0x8bd361f5adb27fd6f4e3f5de866e2befda6a8454efeb704aacc606f528c03f0faae888f60310e49440496abd84083ce2", + "0xb098a3c49f2aaa28b6b3e85bc40ce6a9cdd02134ee522ae73771e667ad7629c8d82c393fba9f27f5416986af4c261438", + "0xb385f5ca285ff2cfe64dcaa32dcde869c28996ed091542600a0b46f65f3f5a38428cca46029ede72b6cf43e12279e3d3", + "0x8196b03d011e5be5288196ef7d47137d6f9237a635ab913acdf9c595fa521d9e2df722090ec7eb0203544ee88178fc5f", + "0x8ed1270211ef928db18e502271b7edf24d0bbd11d97f2786aee772d70c2029e28095cf8f650b0328cc8a4c38d045316d", + "0xa52ab60e28d69b333d597a445884d44fd2a7e1923dd60f763951e1e45f83e27a4dac745f3b9eff75977b3280e132c15d", + "0x91e9fe78cdac578f4a4687f71b800b35da54b824b1886dafec073a3c977ce7a25038a2f3a5b1e35c2c8c9d1a7312417c", + "0xa42832173f9d9491c7bd93b21497fbfa4121687cd4d2ab572e80753d7edcbb42cfa49f460026fbde52f420786751a138", + "0x97b947126d84dcc70c97be3c04b3de3f239b1c4914342fa643b1a4bb8c4fe45c0fcb585700d13a7ed50784790c54bef9", + "0x860e407d353eac070e2418ef6cb80b96fc5f6661d6333e634f6f306779651588037be4c2419562c89c61f9aa2c4947f5", + "0xb2c9d93c3ba4e511b0560b55d3501bf28a510745fd666b3cb532db051e6a8617841ea2f071dda6c9f15619c7bfd2737f", + "0x8596f4d239aeeac78311207904d1bd863ef68e769629cc379db60e019aaf05a9d5cd31dc8e630b31e106a3a93e47cbc5", + "0x8b26e14e2e136b65c5e9e5c2022cee8c255834ea427552f780a6ca130a6446102f2a6f334c3f9a0308c53df09e3dba7e", + "0xb54724354eb515a3c8bed0d0677ff1db94ac0a07043459b4358cb90e3e1aa38ac23f2caa3072cf9647275d7cd61d0e80", + "0xb7ce9fe0e515e7a6b2d7ddcb92bc0196416ff04199326aea57996eef8c5b1548bd8569012210da317f7c0074691d01b7", + "0xa1a13549c82c877253ddefa36a29ea6a23695ee401fdd48e65f6f61e5ebd956d5e0edeff99484e9075cb35071fec41e2", + "0x838ba0c1e5bd1a6da05611ff1822b8622457ebd019cb065ece36a2d176bd2d889511328120b8a357e44569e7f640c1e6", + "0xb916eccff2a95519400bbf76b5f576cbe53cf200410370a19d77734dc04c05b585cfe382e8864e67142d548cd3c4c2f4", + "0xa610447cb7ca6eea53a6ff1f5fe562377dcb7f4aaa7300f755a4f5e8eba61e863c51dc2aa9a29b35525b550fbc32a0fe", + "0x9620e8f0f0ee9a4719aa9685eeb1049c5c77659ba6149ec4c158f999cfd09514794b23388879931fe26fea03fa471fd3", + "0xa9dcf8b679e276583cf5b9360702a185470d09aea463dc474ee9c8aee91ef089dacb073e334e47fbc78ec5417c90465c", + "0x8c9adee8410bdd99e5b285744cee61e2593b6300ff31a8a83b0ec28da59475a5c6fb9346fe43aadea2e6c3dad2a8e30a", + "0x97d5afe9b3897d7b8bb628b7220cf02d8ee4e9d0b78f5000d500aaf4c1df9251aaaabfd1601626519f9d66f00a821d4e", + "0x8a382418157b601ce4c3501d3b8409ca98136a4ef6abcbf62885e16e215b76b035c94d149cc41ff92e42ccd7c43b9b3d", + "0xb64b8d11fb3b01abb2646ac99fdb9c02b804ce15d98f9fe0fbf1c9df8440c71417487feb6cdf51e3e81d37104b19e012", + "0x849d7d044f9d8f0aab346a9374f0b3a5d14a9d1faa83dbacccbdc629ad1ef903a990940255564770537f8567521d17f0", + "0x829dbb0c76b996c2a91b4cbbe93ba455ca0d5729755e5f0c92aaee37dff7f36fcdc06f33aca41f1b609c784127b67d88", + "0x85a7c0069047b978422d264d831ab816435f63938015d2e977222b6b5746066c0071b7f89267027f8a975206ed25c1b0", + "0x84b9fbc1cfb302df1acdcf3dc5d66fd1edfe7839f7a3b2fb3a0d5548656249dd556104d7c32b73967bccf0f5bdcf9e3b", + "0x972220ac5b807f53eac37dccfc2ad355d8b21ea6a9c9b011c09fe440ddcdf7513e0b43d7692c09ded80d7040e26aa28f", + "0x855885ed0b21350baeca890811f344c553cf9c21024649c722453138ba29193c6b02c4b4994cd414035486f923472e28", + "0x841874783ae6d9d0e59daea03e96a01cbbe4ecaced91ae4f2c8386e0d87b3128e6d893c98d17c59e4de1098e1ad519dd", + "0x827e50fc9ce56f97a4c3f2f4cbaf0b22f1c3ce6f844ff0ef93a9c57a09b8bf91ebfbd2ba9c7f83c442920bffdaf288cc", + "0xa441f9136c7aa4c08d5b3534921b730e41ee91ab506313e1ba5f7c6f19fd2d2e1594e88c219834e92e6fb95356385aa7", + "0x97d75b144471bf580099dd6842b823ec0e6c1fb86dd0da0db195e65524129ea8b6fd4a7a9bbf37146269e938a6956596", + "0xa4b6fa87f09d5a29252efb2b3aaab6b3b6ea9fab343132a651630206254a25378e3e9d6c96c3d14c150d01817d375a8e", + "0xa31a671876d5d1e95fe2b8858dc69967231190880529d57d3cab7f9f4a2b9b458ac9ee5bdaa3289158141bf18f559efb", + "0x90bee6fff4338ba825974021b3b2a84e36d617e53857321f13d2b3d4a28954e6de3b3c0e629d61823d18a9763313b3bf", + "0x96b622a63153f393bb419bfcf88272ea8b3560dbd46b0aa07ada3a6223990d0abdd6c2adb356ef4be5641688c8d83941", + "0x84c202adeaff9293698022bc0381adba2cd959f9a35a4e8472288fd68f96f6de8be9da314c526d88e291c96b1f3d6db9", + "0x8ca01a143b8d13809e5a8024d03e6bc9492e22226073ef6e327edf1328ef4aff82d0bcccee92cb8e212831fa35fe1204", + "0xb2f970dbad15bfbefb38903c9bcc043d1367055c55dc1100a850f5eb816a4252c8c194b3132c929105511e14ea10a67d", + "0xa5e36556472a95ad57eb90c3b6623671b03eafd842238f01a081997ffc6e2401f76e781d049bb4aa94d899313577a9cf", + "0x8d1057071051772f7c8bedce53a862af6fd530dd56ae6321eaf2b9fc6a68beff5ed745e1c429ad09d5a118650bfd420a", + "0x8aadc4f70ace4fcb8d93a78610779748dcffc36182d45b932c226dc90e48238ea5daa91f137c65ed532352c4c4d57416", + "0xa2ea05ae37e673b4343232ae685ee14e6b88b867aef6dfac35db3589cbcd76f99540fed5c2641d5bb5a4a9f808e9bf0d", + "0x947f1abad982d65648ae4978e094332b4ecb90f482c9be5741d5d1cf5a28acf4680f1977bf6e49dd2174c37f11e01296", + "0xa27b144f1565e4047ba0e3f4840ef19b5095d1e281eaa463c5358f932114cbd018aa6dcf97546465cf2946d014d8e6d6", + "0x8574e1fc3acade47cd4539df578ce9205e745e161b91e59e4d088711a7ab5aa3b410d517d7304b92109924d9e2af8895", + "0xa48ee6b86b88015d6f0d282c1ae01d2a5b9e8c7aa3d0c18b35943dceb1af580d08a65f54dc6903cde82fd0d73ce94722", + "0x8875650cec543a7bf02ea4f2848a61d167a66c91ffaefe31a9e38dc8511c6a25bde431007eefe27a62af3655aca208dc", + "0x999b0a6e040372e61937bf0d68374e230346b654b5a0f591a59d33a4f95bdb2f3581db7c7ccb420cd7699ed709c50713", + "0x878c9e56c7100c5e47bbe77dc8da5c5fe706cec94d37fa729633bca63cace7c40102eee780fcdabb655f5fa47a99600e", + "0x865006fb5b475ada5e935f27b96f9425fc2d5449a3c106aa366e55ebed3b4ee42adc3c3f0ac19fd129b40bc7d6bc4f63", + "0xb7a7da847f1202e7bc1672553e68904715e84fd897d529243e3ecda59faa4e17ba99c649a802d53f6b8dfdd51f01fb74", + "0x8b2fb4432c05653303d8c8436473682933a5cb604da10c118ecfcd2c8a0e3132e125afef562bdbcc3df936164e5ce4f2", + "0x808d95762d33ddfa5d0ee3d7d9f327de21a994d681a5f372e2e3632963ea974da7f1f9e5bac8ccce24293509d1f54d27", + "0x932946532e3c397990a1df0e94c90e1e45133e347a39b6714c695be21aeb2d309504cb6b1dde7228ff6f6353f73e1ca2", + "0x9705e7c93f0cdfaa3fa96821f830fe53402ad0806036cd1b48adc2f022d8e781c1fbdab60215ce85c653203d98426da3", + "0xaa180819531c3ec1feb829d789cb2092964c069974ae4faad60e04a6afcce5c3a59aec9f11291e6d110a788d22532bc6", + "0x88f755097f7e25cb7dd3c449520c89b83ae9e119778efabb54fbd5c5714b6f37c5f9e0346c58c6ab09c1aef2483f895d", + "0x99fc03ab7810e94104c494f7e40b900f475fde65bdec853e60807ffd3f531d74de43335c3b2646b5b8c26804a7448898", + "0xaf2dea9683086bed1a179110efb227c9c00e76cd00a2015b089ccbcee46d1134aa18bda5d6cab6f82ae4c5cd2461ac21", + "0xa500f87ba9744787fdbb8e750702a3fd229de6b8817594348dec9a723b3c4240ddfa066262d002844b9e38240ce55658", + "0x924d0e45c780f5bc1c1f35d15dfc3da28036bdb59e4c5440606750ecc991b85be18bc9a240b6c983bc5430baa4c68287", + "0x865b11e0157b8bf4c5f336024b016a0162fc093069d44ac494723f56648bc4ded13dfb3896e924959ea11c96321afefc", + "0x93672d8607d4143a8f7894f1dcca83fb84906dc8d6dd7dd063bb0049cfc20c1efd933e06ca7bd03ea4cb5a5037990bfe", + "0x826891efbdff0360446825a61cd1fa04326dd90dae8c33dfb1ed97b045e165766dd070bd7105560994d0b2044bdea418", + "0x93c4a4a8bcbc8b190485cc3bc04175b7c0ed002c28c98a540919effd6ed908e540e6594f6db95cd65823017258fb3b1c", + "0xaeb2a0af2d2239fda9aa6b8234b019708e8f792834ff0dd9c487fa09d29800ddceddd6d7929faa9a3edcb9e1b3aa0d6b", + "0x87f11de7236d387863ec660d2b04db9ac08143a9a2c4dfff87727c95b4b1477e3bc473a91e5797313c58754905079643", + "0x80dc1db20067a844fe8baceca77f80db171a5ca967acb24e2d480eae9ceb91a3343c31ad1c95b721f390829084f0eae6", + "0x9825c31f1c18da0de3fa84399c8b40f8002c3cae211fb6a0623c76b097b4d39f5c50058f57a16362f7a575909d0a44a2", + "0xa99fc8de0c38dbf7b9e946de83943a6b46a762167bafe2a603fb9b86f094da30d6de7ed55d639aafc91936923ee414b3", + "0xad594678b407db5d6ea2e90528121f84f2b96a4113a252a30d359a721429857c204c1c1c4ff71d8bb5768c833f82e80e", + "0xb33d985e847b54510b9b007e31053732c8a495e43be158bd2ffcea25c6765bcbc7ca815f7c60b36ad088b955dd6e9350", + "0x815f8dfc6f90b3342ca3fbd968c67f324dae8f74245cbf8bc3bef10e9440c65d3a2151f951e8d18959ba01c1b50b0ec1", + "0x94c608a362dd732a1abc56e338637c900d59013db8668e49398b3c7a0cae3f7e2f1d1bf94c0299eeafe6af7f76c88618", + "0x8ebd8446b23e5adfcc393adc5c52fe172f030a73e63cd2d515245ca0dd02782ceed5bcdd9ccd9c1b4c5953dfac9c340c", + "0x820437f3f6f9ad0f5d7502815b221b83755eb8dc56cd92c29e9535eb0b48fb8d08c9e4fcc26945f9c8cca60d89c44710", + "0x8910e4e8a56bf4be9cc3bbf0bf6b1182a2f48837a2ed3c2aaec7099bfd7f0c83e14e608876b17893a98021ff4ab2f20d", + "0x9633918fde348573eec15ce0ad53ac7e1823aac86429710a376ad661002ae6d049ded879383faaa139435122f64047c6", + "0xa1f5e3fa558a9e89318ca87978492f0fb4f6e54a9735c1b8d2ecfb1d1c57194ded6e0dd82d077b2d54251f3bee1279e1", + "0xb208e22d04896abfd515a95c429ff318e87ff81a5d534c8ac2c33c052d6ffb73ef1dccd39c0bbe0734b596c384014766", + "0x986d5d7d2b5bde6d16336f378bd13d0e671ad23a8ec8a10b3fc09036faeeb069f60662138d7a6df3dfb8e0d36180f770", + "0xa2d4e6c5f5569e9cef1cddb569515d4b6ace38c8aed594f06da7434ba6b24477392cc67ba867c2b079545ca0c625c457", + "0xb5ac32b1d231957d91c8b7fc43115ce3c5c0d8c13ca633374402fa8000b6d9fb19499f9181844f0c10b47357f3f757ce", + "0x96b8bf2504b4d28fa34a4ec378e0e0b684890c5f44b7a6bb6e19d7b3db2ab27b1e2686389d1de9fbd981962833a313ea", + "0x953bfd7f6c3a0469ad432072b9679a25486f5f4828092401eff494cfb46656c958641a4e6d0d97d400bc59d92dba0030", + "0x876ab3cea7484bbfd0db621ec085b9ac885d94ab55c4bb671168d82b92e609754b86aaf472c55df3d81421d768fd108a", + "0x885ff4e67d9ece646d02dd425aa5a087e485c3f280c3471b77532b0db6145b69b0fbefb18aa2e3fa5b64928b43a94e57", + "0xb91931d93f806d0b0e6cc62a53c718c099526140f50f45d94b8bbb57d71e78647e06ee7b42aa5714aed9a5c05ac8533f", + "0xa0313eeadd39c720c9c27b3d671215331ab8d0a794e71e7e690f06bcd87722b531d6525060c358f35f5705dbb7109ccb", + "0x874c0944b7fedc6701e53344100612ddcb495351e29305c00ec40a7276ea5455465ffb7bded898886c1853139dfb1fc7", + "0x8dc31701a01ee8137059ca1874a015130d3024823c0576aa9243e6942ec99d377e7715ed1444cd9b750a64b85dcaa3e5", + "0x836d2a757405e922ec9a2dfdcf489a58bd48b5f9683dd46bf6047688f778c8dee9bc456de806f70464df0b25f3f3d238", + "0xb30b0a1e454a503ea3e2efdec7483eaf20b0a5c3cefc42069e891952b35d4b2c955cf615f3066285ed8fafd9fcfbb8f6", + "0x8e6d4044b55ab747e83ec8762ea86845f1785cc7be0279c075dadf08aca3ccc5a096c015bb3c3f738f647a4eadea3ba5", + "0xad7735d16ab03cbe09c029610aa625133a6daecfc990b297205b6da98eda8c136a7c50db90f426d35069708510d5ae9c", + "0x8d62d858bbb59ec3c8cc9acda002e08addab4d3ad143b3812098f3d9087a1b4a1bb255dcb1635da2402487d8d0249161", + "0x805beec33238b832e8530645a3254aeef957e8f7ea24bcfc1054f8b9c69421145ebb8f9d893237e8a001c857fedfc77e", + "0xb1005644be4b085e3f5775aa9bd3e09a283e87ddada3082c04e7a62d303dcef3b8cf8f92944c200c7ae6bb6bdf63f832", + "0xb4ba0e0790dc29063e577474ffe3b61f5ea2508169f5adc1e394934ebb473e356239413a17962bc3e5d3762d72cce8c2", + "0xa157ba9169c9e3e6748d9f1dd67fbe08b9114ade4c5d8fc475f87a764fb7e6f1d21f66d7905cd730f28a1c2d8378682a", + "0x913e52b5c93989b5d15e0d91aa0f19f78d592bc28bcfdfddc885a9980c732b1f4debb8166a7c4083c42aeda93a702898", + "0x90fbfc1567e7cd4e096a38433704d3f96a2de2f6ed3371515ccc30bc4dd0721a704487d25a97f3c3d7e4344472702d8d", + "0x89646043028ffee4b69d346907586fd12c2c0730f024acb1481abea478e61031966e72072ff1d5e65cb8c64a69ad4eb1", + "0xb125a45e86117ee11d2fb42f680ab4a7894edd67ff927ae2c808920c66c3e55f6a9d4588eee906f33a05d592e5ec3c04", + "0xaad47f5b41eae9be55fb4f67674ff1e4ae2482897676f964a4d2dcb6982252ee4ff56aac49578b23f72d1fced707525e", + "0xb9ddff8986145e33851b4de54d3e81faa3352e8385895f357734085a1616ef61c692d925fe62a5ed3be8ca49f5d66306", + "0xb3cb0963387ed28c0c0adf7fe645f02606e6e1780a24d6cecef5b7c642499109974c81a7c2a198b19862eedcea2c2d8c", + "0xac9c53c885457aaf5cb36c717a6f4077af701e0098eebd7aa600f5e4b14e6c1067255b3a0bc40e4a552025231be7de60", + "0x8e1a8d823c4603f6648ec21d064101094f2a762a4ed37dd2f0a2d9aa97b2d850ce1e76f4a4b8cae58819b058180f7031", + "0xb268b73bf7a179b6d22bd37e5e8cb514e9f5f8968c78e14e4f6d5700ca0d0ca5081d0344bb73b028970eebde3cb4124e", + "0xa7f57d71940f0edbd29ed8473d0149cae71d921dd15d1ff589774003e816b54b24de2620871108cec1ab9fa956ad6ce6", + "0x8053e6416c8b120e2b999cc2fc420a6a55094c61ac7f2a6c6f0a2c108a320890e389af96cbe378936132363c0d551277", + "0xb3823f4511125e5aa0f4269e991b435a0d6ceb523ebd91c04d7add5534e3df5fc951c504b4fd412a309fd3726b7f940b", + "0xae6eb04674d04e982ca9a6add30370ab90e303c71486f43ed3efbe431af1b0e43e9d06c11c3412651f304c473e7dbf39", + "0x96ab55e641ed2e677591f7379a3cd126449614181fce403e93e89b1645d82c4af524381ff986cae7f9cebe676878646d", + "0xb52423b4a8c37d3c3e2eca8f0ddbf7abe0938855f33a0af50f117fab26415fb0a3da5405908ec5fdc22a2c1f2ca64892", + "0x82a69ce1ee92a09cc709d0e3cd22116c9f69d28ea507fe5901f5676000b5179b9abe4c1875d052b0dd42d39925e186bb", + "0xa84c8cb84b9d5cfb69a5414f0a5283a5f2e90739e9362a1e8c784b96381b59ac6c18723a4aa45988ee8ef5c1f45cc97d", + "0xafd7efce6b36813082eb98257aae22a4c1ae97d51cac7ea9c852d4a66d05ef2732116137d8432e3f117119725a817d24", + "0xa0f5fe25af3ce021b706fcff05f3d825384a272284d04735574ce5fb256bf27100fad0b1f1ba0e54ae9dcbb9570ecad3", + "0x8751786cb80e2e1ff819fc7fa31c2833d25086534eb12b373d31f826382430acfd87023d2a688c65b5e983927e146336", + "0x8cf5c4b17fa4f3d35c78ce41e1dc86988fd1135cd5e6b2bb0c108ee13538d0d09ae7102609c6070f39f937b439b31e33", + "0xa9108967a2fedd7c322711eca8159c533dd561bedcb181b646de98bf5c3079449478eab579731bee8d215ae8852c7e21", + "0xb54c5171704f42a6f0f4e70767cdb3d96ffc4888c842eece343a01557da405961d53ffdc34d2f902ea25d3e1ed867cad", + "0xae8d4b764a7a25330ba205bf77e9f46182cd60f94a336bbd96773cf8064e3d39caf04c310680943dc89ed1fbad2c6e0d", + "0xaa5150e911a8e1346868e1b71c5a01e2a4bb8632c195861fb6c3038a0e9b85f0e09b3822e9283654a4d7bb17db2fc5f4", + "0x9685d3756ce9069bf8bb716cf7d5063ebfafe37e15b137fc8c3159633c4e006ff4887ddd0ae90360767a25c3f90cba7f", + "0x82155fd70f107ab3c8e414eadf226c797e07b65911508c76c554445422325e71af8c9a8e77fd52d94412a6fc29417cd3", + "0xabfae52f53a4b6e00760468d973a267f29321997c3dbb5aee36dc1f20619551229c0c45b9d9749f410e7f531b73378e8", + "0x81a76d921f8ef88e774fd985e786a4a330d779b93fad7def718c014685ca0247379e2e2a007ad63ee7f729cd9ed6ce1b", + "0x81947c84bc5e28e26e2e533af5ae8fe10407a7b77436dbf8f1d5b0bbe86fc659eae10f974659dc7c826c6dabd03e3a4b", + "0x92b8c07050d635b8dd4fd09df9054efe4edae6b86a63c292e73cc819a12a21dd7d104ce51fa56af6539dedf6dbe6f7b6", + "0xb44c579e3881f32b32d20c82c207307eca08e44995dd2aac3b2692d2c8eb2a325626c80ac81c26eeb38c4137ff95add5", + "0x97efab8941c90c30860926dea69a841f2dcd02980bf5413b9fd78d85904588bf0c1021798dbc16c8bbb32cce66c82621", + "0x913363012528b50698e904de0588bf55c8ec5cf6f0367cfd42095c4468fcc64954fbf784508073e542fee242d0743867", + "0x8ed203cf215148296454012bd10fddaf119203db1919a7b3d2cdc9f80e66729464fdfae42f1f2fc5af1ed53a42b40024", + "0xab84312db7b87d711e9a60824f4fe50e7a6190bf92e1628688dfcb38930fe87b2d53f9e14dd4de509b2216856d8d9188", + "0x880726def069c160278b12d2258eac8fa63f729cd351a710d28b7e601c6712903c3ac1e7bbd0d21e4a15f13ca49db5aa", + "0x980699cd51bac6283959765f5174e543ed1e5f5584b5127980cbc2ef18d984ecabba45042c6773b447b8e694db066028", + "0xaeb019cb80dc4cb4207430d0f2cd24c9888998b6f21d9bf286cc638449668d2eec0018a4cf3fe6448673cd6729335e2b", + "0xb29852f6aa6c60effdffe96ae88590c88abae732561d35cc19e82d3a51e26cb35ea00986193e07f90060756240f5346e", + "0xa0fa855adc5ba469f35800c48414b8921455950a5c0a49945d1ef6e8f2a1881f2e2dfae47de6417270a6bf49deeb091d", + "0xb6c7332e3b14813641e7272d4f69ecc7e09081df0037d6dab97ce13a9e58510f5c930d300633f208181d9205c5534001", + "0x85a6c050f42fce560b5a8d54a11c3bbb8407abbadd859647a7b0c21c4b579ec65671098b74f10a16245dc779dff7838e", + "0x8f3eb34bb68759d53c6677de4de78a6c24dd32c8962a7fb355ed362572ef8253733e6b52bc21c9f92ecd875020a9b8de", + "0xa17dd44181e5dab4dbc128e1af93ec22624b57a448ca65d2d9e246797e4af7d079e09c6e0dfb62db3a9957ce92f098d5", + "0xa56a1b854c3183082543a8685bb34cae1289f86cfa8123a579049dbd059e77982886bfeb61bf6e05b4b1fe4e620932e7", + "0xaedae3033cb2fb7628cb4803435bdd7757370a86f808ae4cecb9a268ad0e875f308c048c80cbcac523de16b609683887", + "0x9344905376aa3982b1179497fac5a1d74b14b7038fd15e3b002db4c11c8bfc7c39430db492cdaf58b9c47996c9901f28", + "0xa3bfafdae011a19f030c749c3b071f83580dee97dd6f949e790366f95618ca9f828f1daaeabad6dcd664fcef81b6556d", + "0x81c03d8429129e7e04434dee2c529194ddb01b414feda3adee2271eb680f6c85ec872a55c9fa9d2096f517e13ed5abcc", + "0x98205ef3a72dff54c5a9c82d293c3e45d908946fa74bb749c3aabe1ab994ea93c269bcce1a266d2fe67a8f02133c5985", + "0x85a70aeed09fda24412fadbafbbbf5ba1e00ac92885df329e147bfafa97b57629a3582115b780d8549d07d19b7867715", + "0xb0fbe81c719f89a57d9ea3397705f898175808c5f75f8eb81c2193a0b555869ba7bd2e6bc54ee8a60cea11735e21c68c", + "0xb03a0bd160495ee626ff3a5c7d95bc79d7da7e5a96f6d10116600c8fa20bedd1132f5170f25a22371a34a2d763f2d6d0", + "0xa90ab04091fbca9f433b885e6c1d60ab45f6f1daf4b35ec22b09909d493a6aab65ce41a6f30c98239cbca27022f61a8b", + "0xb66f92aa3bf2549f9b60b86f99a0bd19cbdd97036d4ae71ca4b83d669607f275260a497208f6476cde1931d9712c2402", + "0xb08e1fdf20e6a9b0b4942f14fa339551c3175c1ffc5d0ab5b226b6e6a322e9eb0ba96adc5c8d59ca4259e2bdd04a7eb0", + "0xa2812231e92c1ce74d4f5ac3ab6698520288db6a38398bb38a914ac9326519580af17ae3e27cde26607e698294022c81", + "0xabfcbbcf1d3b9e84c02499003e490a1d5d9a2841a9e50c7babbef0b2dd20d7483371d4dc629ba07faf46db659459d296", + "0xb0fe9f98c3da70927c23f2975a9dc4789194d81932d2ad0f3b00843dd9cbd7fb60747a1da8fe5a79f136a601becf279d", + "0xb130a6dba7645165348cb90f023713bed0eefbd90a976b313521c60a36d34f02032e69a2bdcf5361e343ed46911297ec", + "0x862f0cffe3020cea7a5fd4703353aa1eb1be335e3b712b29d079ff9f7090d1d8b12013011e1bdcbaa80c44641fd37c9f", + "0x8c6f11123b26633e1abb9ed857e0bce845b2b3df91cc7b013b2fc77b477eee445da0285fc6fc793e29d5912977f40916", + "0x91381846126ea819d40f84d3005e9fb233dc80071d1f9bb07f102bf015f813f61e5884ffffb4f5cd333c1b1e38a05a58", + "0x8add7d908de6e1775adbd39c29a391f06692b936518db1f8fde74eb4f533fc510673a59afb86e3a9b52ade96e3004c57", + "0x8780e086a244a092206edcde625cafb87c9ab1f89cc3e0d378bc9ee776313836160960a82ec397bc3800c0a0ec3da283", + "0xa6cb4cd9481e22870fdd757fae0785edf4635e7aacb18072fe8dc5876d0bab53fb99ce40964a7d3e8bcfff6f0ab1332f", + "0xaf30ff47ecc5b543efba1ba4706921066ca8bb625f40e530fb668aea0551c7647a9d126e8aba282fbcce168c3e7e0130", + "0x91b0bcf408ce3c11555dcb80c4410b5bc2386d3c05caec0b653352377efdcb6bab4827f2018671fc8e4a0e90d772acc1", + "0xa9430b975ef138b6b2944c7baded8fe102d31da4cfe3bd3d8778bda79189c99d38176a19c848a19e2d1ee0bddd9a13c1", + "0xaa5a4eef849d7c9d2f4b018bd01271c1dd83f771de860c4261f385d3bdcc130218495860a1de298f14b703ec32fa235f", + "0xb0ce79e7f9ae57abe4ff366146c3b9bfb38b0dee09c28c28f5981a5d234c6810ad4d582751948affb480d6ae1c8c31c4", + "0xb75122748560f73d15c01a8907d36d06dc068e82ce22b84b322ac1f727034493572f7907dec34ebc3ddcc976f2f89ed7", + "0xb0fc7836369a3e4411d34792d6bd5617c14f61d9bba023dda64e89dc5fb0f423244e9b48ee64869258931daa9753a56f", + "0x8956d7455ae9009d70c6e4a0bcd7610e55f37494cf9897a8f9e1b904cc8febc3fd2d642ebd09025cfff4609ad7e3bc52", + "0xad741efe9e472026aa49ae3d9914cb9c1a6f37a54f1a6fe6419bebd8c7d68dca105a751c7859f4389505ede40a0de786", + "0xb52f418797d719f0d0d0ffb0846788b5cba5d0454a69a2925de4b0b80fa4dd7e8c445e5eac40afd92897ed28ca650566", + "0xa0ab65fb9d42dd966cd93b1de01d7c822694669dd2b7a0c04d99cd0f3c3de795f387b9c92da11353412f33af5c950e9a", + "0xa0052f44a31e5741a331f7cac515a08b3325666d388880162d9a7b97598fde8b61f9ff35ff220df224eb5c4e40ef0567", + "0xa0101cfdc94e42b2b976c0d89612a720e55d145a5ef6ef6f1f78cf6de084a49973d9b5d45915349c34ce712512191e3c", + "0xa0dd99fcf3f5cead5aaf08e82212df3a8bb543c407a4d6fab88dc5130c1769df3f147e934a46f291d6c1a55d92b86917", + "0xa5939153f0d1931bbda5cf6bdf20562519ea55fbfa978d6dbc6828d298260c0da7a50c37c34f386e59431301a96c2232", + "0x9568269f3f5257200f9ca44afe1174a5d3cf92950a7f553e50e279c239e156a9faaa2a67f288e3d5100b4142efe64856", + "0xb746b0832866c23288e07f24991bbf687cad794e7b794d3d3b79367566ca617d38af586cdc8d6f4a85a34835be41d54f", + "0xa871ce28e39ab467706e32fec1669fda5a4abba2f8c209c6745df9f7a0fa36bbf1919cf14cb89ea26fa214c4c907ae03", + "0xa08dacdd758e523cb8484f6bd070642c0c20e184abdf8e2a601f61507e93952d5b8b0c723c34fcbdd70a8485eec29db2", + "0x85bdb78d501382bb95f1166b8d032941005661aefd17a5ac32df9a3a18e9df2fc5dc2c1f07075f9641af10353cecc0c9", + "0x98d730c28f6fa692a389e97e368b58f4d95382fad8f0baa58e71a3d7baaea1988ead47b13742ce587456f083636fa98e", + "0xa557198c6f3d5382be9fb363feb02e2e243b0c3c61337b3f1801c4a0943f18e38ce1a1c36b5c289c8fa2aa9d58742bab", + "0x89174f79201742220ac689c403fc7b243eed4f8e3f2f8aba0bf183e6f5d4907cb55ade3e238e3623d9885f03155c4d2b", + "0xb891d600132a86709e06f3381158db300975f73ea4c1f7c100358e14e98c5fbe792a9af666b85c4e402707c3f2db321e", + "0xb9e5b2529ef1043278c939373fc0dbafe446def52ddd0a8edecd3e4b736de87e63e187df853c54c28d865de18a358bb6", + "0x8589b2e9770340c64679062c5badb7bbef68f55476289b19511a158a9a721f197da03ece3309e059fc4468b15ac33aa3", + "0xaad8c6cd01d785a881b446f06f1e9cd71bca74ba98674c2dcddc8af01c40aa7a6d469037498b5602e76e9c91a58d3dbd", + "0xabaccb1bd918a8465f1bf8dbe2c9ad4775c620b055550b949a399f30cf0d9eb909f3851f5b55e38f9e461e762f88f499", + "0xae62339d26db46e85f157c0151bd29916d5cc619bd4b832814b3fd2f00af8f38e7f0f09932ffe5bba692005dab2d9a74", + "0x93a6ff30a5c0edf8058c89aba8c3259e0f1b1be1b80e67682de651e5346f7e1b4b4ac3d87cbaebf198cf779524aff6bf", + "0x8980a2b1d8f574af45b459193c952400b10a86122b71fca2acb75ee0dbd492e7e1ef5b959baf609a5172115e371f3177", + "0x8c2f49f3666faee6940c75e8c7f6f8edc3f704cca7a858bbb7ee5e96bba3b0cf0993996f781ba6be3b0821ef4cb75039", + "0xb14b9e348215b278696018330f63c38db100b0542cfc5be11dc33046e3bca6a13034c4ae40d9cef9ea8b34fef0910c4e", + "0xb59bc3d0a30d66c16e6a411cb641f348cb1135186d5f69fda8b0a0934a5a2e7f6199095ba319ec87d3fe8f1ec4a06368", + "0x8874aca2a3767aa198e4c3fec2d9c62d496bc41ff71ce242e9e082b7f38cdf356089295f80a301a3cf1182bde5308c97", + "0xb1820ebd61376d91232423fc20bf008b2ba37e761199f4ef0648ea2bd70282766799b4de814846d2f4d516d525c8daa7", + "0xa6b202e5dedc16a4073e04a11af3a8509b23dfe5a1952f899adeb240e75c3f5bde0c424f811a81ea48d343591faffe46", + "0xa69becee9c93734805523b92150a59a62eed4934f66056b645728740d42223f2925a1ad38359ba644da24d9414f4cdda", + "0xad72f0f1305e37c7e6b48c272323ee883320994cb2e0d850905d6655fafc9f361389bcb9c66b3ff8d2051dbb58c8aa96", + "0xb563600bd56fad7c8853af21c6a02a16ed9d8a8bbeea2c31731d63b976d83cb05b9779372d898233e8fd597a75424797", + "0xb0abb78ce465bf7051f563c62e8be9c57a2cc997f47c82819300f36e301fefd908894bb2053a9d27ce2d0f8c46d88b5b", + "0xa071a85fb8274bac2202e0cb8e0e2028a5e138a82d6e0374d39ca1884a549c7c401312f00071b91f455c3a2afcfe0cda", + "0xb931c271513a0f267b9f41444a5650b1918100b8f1a64959c552aff4e2193cc1b9927906c6fa7b8a8c68ef13d79aaa52", + "0xa6a1bb9c7d32cb0ca44d8b75af7e40479fbce67d216b48a2bb680d3f3a772003a49d3cd675fc64e9e0f8fabeb86d6d61", + "0xb98d609858671543e1c3b8564162ad828808bb50ded261a9f8690ded5b665ed8368c58f947365ed6e84e5a12e27b423d", + "0xb3dca58cd69ec855e2701a1d66cad86717ff103ef862c490399c771ad28f675680f9500cb97be48de34bcdc1e4503ffd", + "0xb34867c6735d3c49865e246ddf6c3b33baf8e6f164db3406a64ebce4768cb46b0309635e11be985fee09ab7a31d81402", + "0xacb966c554188c5b266624208f31fab250b3aa197adbdd14aee5ab27d7fb886eb4350985c553b20fdf66d5d332bfd3fe", + "0x943c36a18223d6c870d54c3b051ef08d802b85e9dd6de37a51c932f90191890656c06adfa883c87b906557ae32d09da0", + "0x81bca7954d0b9b6c3d4528aadf83e4bc2ef9ea143d6209bc45ae9e7ae9787dbcd8333c41f12c0b6deee8dcb6805e826a", + "0xaba176b92256efb68f574e543479e5cf0376889fb48e3db4ebfb7cba91e4d9bcf19dcfec444c6622d9398f06de29e2b9", + "0xb9f743691448053216f6ece7cd699871fff4217a1409ceb8ab7bdf3312d11696d62c74b0664ba0a631b1e0237a8a0361", + "0xa383c2b6276fa9af346b21609326b53fb14fdf6f61676683076e80f375b603645f2051985706d0401e6fbed7eb0666b6", + "0xa9ef2f63ec6d9beb8f3d04e36807d84bda87bdd6b351a3e4a9bf7edcb5618c46c1f58cfbf89e64b40f550915c6988447", + "0xa141b2d7a82f5005eaea7ae7d112c6788b9b95121e5b70b7168d971812f3381de8b0082ac1f0a82c7d365922ebd2d26a", + "0xb1b76ef8120e66e1535c17038b75255a07849935d3128e3e99e56567b842fb1e8d56ef932d508d2fb18b82f7868fe1a9", + "0x8e2e234684c81f21099f5c54f6bbe2dd01e3b172623836c77668a0c49ce1fe218786c3827e4d9ae2ea25c50a8924fb3c", + "0xa5caf5ff948bfd3c4ca3ffbdfcd91eec83214a6c6017235f309a0bbf7061d3b0b466307c00b44a1009cf575163898b43", + "0x986415a82ca16ebb107b4c50b0c023c28714281db0bcdab589f6cb13d80e473a3034b7081b3c358e725833f6d845cb14", + "0xb94836bf406ac2cbacb10e6df5bcdfcc9d9124ae1062767ca4e322d287fd5e353fdcebd0e52407cb3cd68571258a8900", + "0x83c6d70a640b33087454a4788dfd9ef3ed00272da084a8d36be817296f71c086b23b576f98178ab8ca6a74f04524b46b", + "0xad4115182ad784cfe11bcfc5ce21fd56229cc2ce77ac82746e91a2f0aa53ca6593a22efd2dc4ed8d00f84542643d9c58", + "0xab1434c5e5065da826d10c2a2dba0facccab0e52b506ce0ce42fbe47ced5a741797151d9ecc99dc7d6373cfa1779bbf6", + "0x8a8b591d82358d55e6938f67ea87a89097ab5f5496f7260adb9f649abb289da12b498c5b2539c2f9614fb4e21b1f66b0", + "0x964f355d603264bc1f44c64d6d64debca66f37dff39c971d9fc924f2bc68e6c187b48564a6dc82660a98b035f8addb5d", + "0xb66235eaaf47456bc1dc4bde454a028e2ce494ece6b713a94cd6bf27cf18c717fd0c57a5681caaa2ad73a473593cdd7a", + "0x9103e3bb74304186fa4e3e355a02da77da4aca9b7e702982fc2082af67127ebb23a455098313c88465bc9b7d26820dd5", + "0xb6a42ff407c9dd132670cdb83cbad4b20871716e44133b59a932cd1c3f97c7ac8ff7f61acfaf8628372508d8dc8cad7c", + "0x883a9c21c16a167a4171b0f084565c13b6f28ba7c4977a0de69f0a25911f64099e7bbb4da8858f2e93068f4155d04e18", + "0x8dbb3220abc6a43220adf0331e3903d3bfd1d5213aadfbd8dfcdf4b2864ce2e96a71f35ecfb7a07c3bbabf0372b50271", + "0xb4ad08aee48e176bda390b7d9acf2f8d5eb008f30d20994707b757dc6a3974b2902d29cd9b4d85e032810ad25ac49e97", + "0x865bb0f33f7636ec501bb634e5b65751c8a230ae1fa807a961a8289bbf9c7fe8c59e01fbc4c04f8d59b7f539cf79ddd5", + "0x86a54d4c12ad1e3605b9f93d4a37082fd26e888d2329847d89afa7802e815f33f38185c5b7292293d788ad7d7da1df97", + "0xb26c8615c5e47691c9ff3deca3021714662d236c4d8401c5d27b50152ce7e566266b9d512d14eb63e65bc1d38a16f914", + "0x827639d5ce7db43ba40152c8a0eaad443af21dc92636cc8cc2b35f10647da7d475a1e408901cd220552fddad79db74df", + "0xa2b79a582191a85dbe22dc384c9ca3de345e69f6aa370aa6d3ff1e1c3de513e30b72df9555b15a46586bd27ea2854d9d", + "0xae0d74644aba9a49521d3e9553813bcb9e18f0b43515e4c74366e503c52f47236be92dfbd99c7285b3248c267b1de5a0", + "0x80fb0c116e0fd6822a04b9c25f456bdca704e2be7bdc5d141dbf5d1c5eeb0a2c4f5d80db583b03ef3e47517e4f9a1b10", + "0xac3a1fa3b4a2f30ea7e0a114cdc479eb51773573804c2a158d603ad9902ae8e39ffe95df09c0d871725a5d7f9ba71a57", + "0xb56b2b0d601cba7f817fa76102c68c2e518c6f20ff693aad3ff2e07d6c4c76203753f7f91686b1801e8c4659e4d45c48", + "0x89d50c1fc56e656fb9d3915964ebce703cb723fe411ab3c9eaa88ccc5d2b155a9b2e515363d9c600d3c0cee782c43f41", + "0xb24207e61462f6230f3cd8ccf6828357d03e725769f7d1de35099ef9ee4dca57dbce699bb49ed994462bee17059d25ce", + "0xb886f17fcbcbfcd08ac07f04bb9543ef58510189decaccea4b4158c9174a067cb67d14b6be3c934e6e2a18c77efa9c9c", + "0xb9c050ad9cafd41c6e2e192b70d080076eed59ed38ea19a12bd92fa17b5d8947d58d5546aaf5e8e27e1d3b5481a6ce51", + "0xaaf7a34d3267e3b1ddbc54c641e3922e89303f7c86ebebc7347ebca4cffad5b76117dac0cbae1a133053492799cd936f", + "0xa9ee604ada50adef82e29e893070649d2d4b7136cc24fa20e281ce1a07bd736bf0de7c420369676bcbcecff26fb6e900", + "0x9855315a12a4b4cf80ab90b8bd13003223ba25206e52fd4fe6a409232fbed938f30120a3db23eab9c53f308bd8b9db81", + "0x8cd488dd7a24f548a3cf03c54dec7ff61d0685cb0f6e5c46c2d728e3500d8c7bd6bba0156f4bf600466fda53e5b20444", + "0x890ad4942ebac8f5b16c777701ab80c68f56fa542002b0786f8fea0fb073154369920ac3dbfc07ea598b82f4985b8ced", + "0x8de0cf9ddc84c9b92c59b9b044387597799246b30b9f4d7626fc12c51f6e423e08ee4cbfe9289984983c1f9521c3e19d", + "0xb474dfb5b5f4231d7775b3c3a8744956b3f0c7a871d835d7e4fd9cc895222c7b868d6c6ce250de568a65851151fac860", + "0x86433b6135d9ed9b5ee8cb7a6c40e5c9d30a68774cec04988117302b8a02a11a71a1e03fd8e0264ef6611d219f103007", + "0x80b9ed4adbe9538fb1ef69dd44ec0ec5b57cbfea820054d8d445b4261962624b4c70ac330480594bc5168184378379c3", + "0x8b2e83562ccd23b7ad2d17f55b1ab7ef5fbef64b3a284e6725b800f3222b8bdf49937f4a873917ada9c4ddfb090938c2", + "0xabe78cebc0f5a45d754140d1f685e387489acbfa46d297a8592aaa0d676a470654f417a4f7d666fc0b2508fab37d908e", + "0xa9c5f8ff1f8568e252b06d10e1558326db9901840e6b3c26bbd0cd5e850cb5fb3af3f117dbb0f282740276f6fd84126f", + "0x975f8dc4fb55032a5df3b42b96c8c0ffecb75456f01d4aef66f973cb7270d4eff32c71520ceefc1adcf38d77b6b80c67", + "0xb043306ed2c3d8a5b9a056565afd8b5e354c8c4569fda66b0d797a50a3ce2c08cffbae9bbe292da69f39e89d5dc7911e", + "0x8d2afc36b1e44386ba350c14a6c1bb31ff6ea77128a0c5287584ac3584282d18516901ce402b4644a53db1ed8e7fa581", + "0x8c294058bed53d7290325c363fe243f6ec4f4ea2343692f4bac8f0cb86f115c069ccb8334b53d2e42c067691ad110dba", + "0xb92157b926751aaf7ef82c1aa8c654907dccab6376187ee8b3e8c0c82811eae01242832de953faa13ebaff7da8698b3e", + "0xa780c4bdd9e4ba57254b09d745075cecab87feda78c88ffee489625c5a3cf96aa6b3c9503a374a37927d9b78de9bd22b", + "0x811f548ef3a2e6a654f7dcb28ac9378de9515ed61e5a428515d9594a83e80b35c60f96a5cf743e6fab0d3cb526149f49", + "0x85a4dccf6d90ee8e094731eec53bd00b3887aec6bd81a0740efddf812fd35e3e4fe4f983afb49a8588691c202dabf942", + "0xb152c2da6f2e01c8913079ae2b40a09b1f361a80f5408a0237a8131b429677c3157295e11b365b1b1841924b9efb922e", + "0x849b9efee8742502ffd981c4517c88ed33e4dd518a330802caff168abae3cd09956a5ee5eda15900243bc2e829016b74", + "0x955a933f3c18ec0f1c0e38fa931e4427a5372c46a3906ebe95082bcf878c35246523c23f0266644ace1fa590ffa6d119", + "0x911989e9f43e580c886656377c6f856cdd4ff1bd001b6db3bbd86e590a821d34a5c6688a29b8d90f28680e9fdf03ba69", + "0xb73b8b4f1fd6049fb68d47cd96a18fcba3f716e0a1061aa5a2596302795354e0c39dea04d91d232aec86b0bf2ba10522", + "0x90f87456d9156e6a1f029a833bf3c7dbed98ca2f2f147a8564922c25ae197a55f7ea9b2ee1f81bf7383197c4bad2e20c", + "0x903cba8b1e088574cb04a05ca1899ab00d8960580c884bd3c8a4c98d680c2ad11410f2b75739d6050f91d7208cac33a5", + "0x9329987d42529c261bd15ecedd360be0ea8966e7838f32896522c965adfc4febf187db392bd441fb43bbd10c38fdf68b", + "0x8178ee93acf5353baa349285067b20e9bb41aa32d77b5aeb7384fe5220c1fe64a2461bd7a83142694fe673e8bbf61b7c", + "0xa06a8e53abcff271b1394bcc647440f81fb1c1a5f29c27a226e08f961c3353f4891620f2d59b9d1902bf2f5cc07a4553", + "0xaaf5fe493b337810889e777980e6bbea6cac39ac66bc0875c680c4208807ac866e9fda9b5952aa1d04539b9f4a4bec57", + "0xaa058abb1953eceac14ccfa7c0cc482a146e1232905dcecc86dd27f75575285f06bbae16a8c9fe8e35d8713717f5f19f", + "0x8f15dd732799c879ca46d2763453b359ff483ca33adb1d0e0a57262352e0476c235987dc3a8a243c74bc768f93d3014c", + "0xa61cc8263e9bc03cce985f1663b8a72928a607121005a301b28a278e9654727fd1b22bc8a949af73929c56d9d3d4a273", + "0x98d6dc78502d19eb9f921225475a6ebcc7b44f01a2df6f55ccf6908d65b27af1891be2a37735f0315b6e0f1576c1f8d8", + "0x8bd258b883f3b3793ec5be9472ad1ff3dc4b51bc5a58e9f944acfb927349ead8231a523cc2175c1f98e7e1e2b9f363b8", + "0xaeacc2ecb6e807ad09bedd99654b097a6f39840e932873ace02eabd64ccfbb475abdcb62939a698abf17572d2034c51e", + "0xb8ccf78c08ccd8df59fd6eda2e01de328bc6d8a65824d6f1fc0537654e9bc6bf6f89c422dd3a295cce628749da85c864", + "0x8f91fd8cb253ba2e71cc6f13da5e05f62c2c3b485c24f5d68397d04665673167fce1fc1aec6085c69e87e66ec555d3fd", + "0xa254baa10cb26d04136886073bb4c159af8a8532e3fd36b1e9c3a2e41b5b2b6a86c4ebc14dbe624ee07b7ccdaf59f9ab", + "0x94e3286fe5cd68c4c7b9a7d33ae3d714a7f265cf77cd0e9bc19fc51015b1d1c34ad7e3a5221c459e89f5a043ee84e3a9", + "0xa279da8878af8d449a9539bec4b17cea94f0242911f66fab275b5143ab040825f78c89cb32a793930609415cfa3a1078", + "0xac846ceb89c9e5d43a2991c8443079dc32298cd63e370e64149cec98cf48a6351c09c856f2632fd2f2b3d685a18bbf8b", + "0xa847b27995c8a2e2454aaeb983879fb5d3a23105c33175839f7300b7e1e8ec3efd6450e9fa3f10323609dee7b98c6fd5", + "0xa2f432d147d904d185ff4b2de8c6b82fbea278a2956bc406855b44c18041854c4f0ecccd472d1d0dff1d8aa8e281cb1d", + "0x94a48ad40326f95bd63dff4755f863a1b79e1df771a1173b17937f9baba57b39e651e7695be9f66a472f098b339364fc", + "0xa12a0ccd8f96e96e1bc6494341f7ebce959899341b3a084aa1aa87d1c0d489ac908552b7770b887bb47e7b8cbc3d8e66", + "0x81a1f1681bda923bd274bfe0fbb9181d6d164fe738e54e25e8d4849193d311e2c4253614ed673c98af2c798f19a93468", + "0xabf71106a05d501e84cc54610d349d7d5eae21a70bd0250f1bebbf412a130414d1c8dbe673ffdb80208fd72f1defa4d4", + "0x96266dc2e0df18d8136d79f5b59e489978eee0e6b04926687fe389d4293c14f36f055c550657a8e27be4118b64254901", + "0x8df5dcbefbfb4810ae3a413ca6b4bf08619ca53cd50eb1dde2a1c035efffc7b7ac7dff18d403253fd80104bd83dc029e", + "0x9610b87ff02e391a43324a7122736876d5b3af2a137d749c52f75d07b17f19900b151b7f439d564f4529e77aa057ad12", + "0xa90a5572198b40fe2fcf47c422274ff36c9624df7db7a89c0eb47eb48a73a03c985f4ac5016161c76ca317f64339bce1", + "0x98e5e61a6ab6462ba692124dba7794b6c6bde4249ab4fcc98c9edd631592d5bc2fb5e38466691a0970a38e48d87c2e43", + "0x918cefb8f292f78d4db81462c633daf73b395e772f47b3a7d2cea598025b1d8c3ec0cbff46cdb23597e74929981cde40", + "0xa98918a5dc7cf610fe55f725e4fd24ce581d594cb957bb9b4e888672e9c0137003e1041f83e3f1d7b9caab06462c87d4", + "0xb92b74ac015262ca66c33f2d950221e19d940ba3bf4cf17845f961dc1729ae227aa9e1f2017829f2135b489064565c29", + "0xa053ee339f359665feb178b4e7ee30a85df37debd17cacc5a27d6b3369d170b0114e67ad1712ed26d828f1df641bcd99", + "0x8c3c8bad510b35da5ce5bd84b35c958797fbea024ad1c97091d2ff71d9b962e9222f65a9b776e5b3cc29c36e1063d2ee", + "0xaf99dc7330fe7c37e850283eb47cc3257888e7c197cb0d102edf94439e1e02267b6a56306d246c326c4c79f9dc8c6986", + "0xafecb2dc34d57a725efbd7eb93d61eb29dbe8409b668ab9ea040791f5b796d9be6d4fc10d7f627bf693452f330cf0435", + "0x93334fedf19a3727a81a6b6f2459db859186227b96fe7a391263f69f1a0884e4235de64d29edebc7b99c44d19e7c7d7a", + "0x89579c51ac405ad7e9df13c904061670ce4b38372492764170e4d3d667ed52e5d15c7cd5c5991bbfa3a5e4e3fa16363e", + "0x9778f3e8639030f7ef1c344014f124e375acb8045bd13d8e97a92c5265c52de9d1ffebaa5bc3e1ad2719da0083222991", + "0x88f77f34ee92b3d36791bdf3326532524a67d544297dcf1a47ff00b47c1b8219ff11e34034eab7d23b507caa2fd3c6b9", + "0xa699c1e654e7c484431d81d90657892efeb4adcf72c43618e71ca7bd7c7a7ebbb1db7e06e75b75dc4c74efd306b5df3f", + "0x81d13153baebb2ef672b5bdb069d3cd669ce0be96b742c94e04038f689ff92a61376341366b286eee6bf3ae85156f694", + "0x81efb17de94400fdacc1deec2550cbe3eecb27c7af99d8207e2f9be397e26be24a40446d2a09536bb5172c28959318d9", + "0x989b21ebe9ceab02488992673dc071d4d5edec24bff0e17a4306c8cb4b3c83df53a2063d1827edd8ed16d6e837f0d222", + "0x8d6005d6536825661b13c5fdce177cb37c04e8b109b7eb2b6d82ea1cb70efecf6a0022b64f84d753d165edc2bba784a3", + "0xa32607360a71d5e34af2271211652d73d7756d393161f4cf0da000c2d66a84c6826e09e759bd787d4fd0305e2439d342", + "0xaaad8d6f6e260db45d51b2da723be6fa832e76f5fbcb77a9a31e7f090dd38446d3b631b96230d78208cae408c288ac4e", + "0xabcfe425255fd3c5cffd3a818af7650190c957b6b07b632443f9e33e970a8a4c3bf79ac9b71f4d45f238a04d1c049857", + "0xaeabf026d4c783adc4414b5923dbd0be4b039cc7201219f7260d321f55e9a5b166d7b5875af6129c034d0108fdc5d666", + "0xaf49e740c752d7b6f17048014851f437ffd17413c59797e5078eaaa36f73f0017c3e7da020310cfe7d3c85f94a99f203", + "0x8854ca600d842566e3090040cd66bb0b3c46dae6962a13946f0024c4a8aca447e2ccf6f240045f1ceee799a88cb9210c", + "0xb6c03b93b1ab1b88ded8edfa1b487a1ed8bdce8535244dddb558ffb78f89b1c74058f80f4db2320ad060d0c2a9c351cc", + "0xb5bd7d17372faff4898a7517009b61a7c8f6f0e7ed4192c555db264618e3f6e57fb30a472d169fea01bf2bf0362a19a8", + "0x96eb1d38319dc74afe7e7eb076fcd230d19983f645abd14a71e6103545c01301b31c47ae931e025f3ecc01fb3d2f31fa", + "0xb55a8d30d4403067def9b65e16f867299f8f64c9b391d0846d4780bc196569622e7e5b64ce799b5aefac8f965b2a7a7b", + "0x8356d199a991e5cbbff608752b6291731b6b6771aed292f8948b1f41c6543e4ab1bedc82dd26d10206c907c03508df06", + "0x97f4137445c2d98b0d1d478049de952610ad698c91c9d0f0e7227d2aae690e9935e914ec4a2ea1fbf3fc1dddfeeacebb", + "0xaf5621707e0938320b15ddfc87584ab325fbdfd85c30efea36f8f9bd0707d7ec12c344eff3ec21761189518d192df035", + "0x8ac7817e71ea0825b292687928e349da7140285d035e1e1abff0c3704fa8453faaae343a441b7143a74ec56539687cc4", + "0x8a5e0a9e4758449489df10f3386029ada828d1762e4fb0a8ffe6b79e5b6d5d713cb64ed95960e126398b0cdb89002bc9", + "0x81324be4a71208bbb9bca74b77177f8f1abb9d3d5d9db195d1854651f2cf333cd618d35400da0f060f3e1b025124e4b2", + "0x849971d9d095ae067525b3cbc4a7dfae81f739537ade6d6cec1b42fb692d923176197a8770907c58069754b8882822d6", + "0x89f830825416802477cc81fdf11084885865ee6607aa15aa4eb28e351c569c49b8a1b9b5e95ddc04fa0ebafe20071313", + "0x9240aeeaff37a91af55f860b9badd466e8243af9e8c96a7aa8cf348cd270685ab6301bc135b246dca9eda696f8b0e350", + "0xacf74db78cc33138273127599eba35b0fb4e7b9a69fe02dae18fc6692d748ca332bd00b22afa8e654ed587aab11833f3", + "0xb091e6d37b157b50d76bd297ad752220cd5c9390fac16dc838f8557aed6d9833fc920b61519df21265406216315e883f", + "0xa6446c429ebf1c7793c622250e23594c836b2fbcaf6c5b3d0995e1595a37f50ea643f3e549b0be8bbdadd69044d72ab9", + "0x93e675353bd60e996bf1c914d5267eeaa8a52fc3077987ccc796710ef9becc6b7a00e3d82671a6bdfb8145ee3c80245a", + "0xa2f731e43251d04ed3364aa2f072d05355f299626f2d71a8a38b6f76cf08c544133f7d72dd0ab4162814b674b9fc7fa6", + "0x97a8b791a5a8f6e1d0de192d78615d73d0c38f1e557e4e15d15adc663d649e655bc8da3bcc499ef70112eafe7fb45c7a", + "0x98cd624cbbd6c53a94469be4643c13130916b91143425bcb7d7028adbbfede38eff7a21092af43b12d4fab703c116359", + "0x995783ce38fd5f6f9433027f122d4cf1e1ff3caf2d196ce591877f4a544ce9113ead60de2de1827eaff4dd31a20d79a8", + "0x8cf251d6f5229183b7f3fe2f607a90b4e4b6f020fb4ba2459d28eb8872426e7be8761a93d5413640a661d73e34a5b81f", + "0xb9232d99620652a3aa7880cad0876f153ff881c4ed4c0c2e7b4ea81d5d42b70daf1a56b869d752c3743c6d4c947e6641", + "0x849716f938f9d37250cccb1bf77f5f9fde53096cdfc6f2a25536a6187029a8f1331cdbed08909184b201f8d9f04b792f", + "0x80c7c4de098cbf9c6d17b14eba1805e433b5bc905f6096f8f63d34b94734f2e4ebf4bce8a177efd1186842a61204a062", + "0xb790f410cf06b9b8daadceeb4fd5ff40a2deda820c8df2537e0a7554613ae3948e149504e3e79aa84889df50c8678eeb", + "0x813aab8bd000299cd37485b73cd7cba06e205f8efb87f1efc0bae8b70f6db2bc7702eb39510ad734854fb65515fe9d0f", + "0x94f0ab7388ac71cdb67f6b85dfd5945748afb2e5abb622f0b5ad104be1d4d0062b651f134ba22385c9e32c2dfdcccce1", + "0xab6223dca8bd6a4f969e21ccd9f8106fc5251d321f9e90cc42cea2424b3a9c4e5060a47eeef6b23c7976109b548498e8", + "0x859c56b71343fce4d5c5b87814c47bf55d581c50fd1871a17e77b5e1742f5af639d0e94d19d909ec7dfe27919e954e0c", + "0xaae0d632b6191b8ad71b027791735f1578e1b89890b6c22e37de0e4a6074886126988fe8319ae228ac9ef3b3bcccb730", + "0x8ca9f32a27a024c3d595ecfaf96b0461de57befa3b331ab71dc110ec3be5824fed783d9516597537683e77a11d334338", + "0xa061df379fb3f4b24816c9f6cd8a94ecb89b4c6dc6cd81e4b8096fa9784b7f97ab3540259d1de9c02eb91d9945af4823", + "0x998603102ac63001d63eb7347a4bb2bf4cf33b28079bb48a169076a65c20d511ccd3ef696d159e54cc8e772fb5d65d50", + "0x94444d96d39450872ac69e44088c252c71f46be8333a608a475147752dbb99db0e36acfc5198f158509401959c12b709", + "0xac1b51b6c09fe055c1d7c9176eea9adc33f710818c83a1fbfa073c8dc3a7eb3513cbdd3f5960b7845e31e3e83181e6ba", + "0x803d530523fc9e1e0f11040d2412d02baef3f07eeb9b177fa9bfa396af42eea898a4276d56e1db998dc96ae47b644cb2", + "0x85a3c9fc7638f5bf2c3e15ba8c2fa1ae87eb1ceb44c6598c67a2948667a9dfa41e61f66d535b4e7fda62f013a5a8b885", + "0xa961cf5654c46a1a22c29baf7a4e77837a26b7f138f410e9d1883480ed5fa42411d522aba32040b577046c11f007388e", + "0xad1154142344f494e3061ef45a34fab1aaacf5fdf7d1b26adbb5fbc3d795655fa743444e39d9a4119b4a4f82a6f30441", + "0xb1d6c30771130c77806e7ab893b73d4deb590b2ff8f2f8b5e54c2040c1f3e060e2bd99afc668cf706a2df666a508bbf6", + "0xa00361fd440f9decabd98d96c575cd251dc94c60611025095d1201ef2dedde51cb4de7c2ece47732e5ed9b3526c2012c", + "0xa85c5ab4d17d328bda5e6d839a9a6adcc92ff844ec25f84981e4f44a0e8419247c081530f8d9aa629c7eb4ca21affba6", + "0xa4ddd3eab4527a2672cf9463db38bc29f61460e2a162f426b7852b7a7645fbd62084fd39a8e4d60e1958cce436dd8f57", + "0x811648140080fe55b8618f4cf17f3c5a250adb0cd53d885f2ddba835d2b4433188e41fc0661faac88e4ff910b16278c0", + "0xb85c7f1cfb0ed29addccf7546023a79249e8f15ac2d14a20accbfef4dd9dc11355d599815fa09d2b6b4e966e6ea8cff1", + "0xa10b5d8c260b159043b020d5dd62b3467df2671afea6d480ca9087b7e60ed170c82b121819d088315902842d66c8fb45", + "0x917e191df1bcf3f5715419c1e2191da6b8680543b1ba41fe84ed07ef570376e072c081beb67b375fca3565a2565bcabb", + "0x881fd967407390bfd7badc9ab494e8a287559a01eb07861f527207c127eadea626e9bcc5aa9cca2c5112fbac3b3f0e9c", + "0x959fd71149af82cc733619e0e5bf71760ca2650448c82984b3db74030d0e10f8ab1ce1609a6de6f470fe8b5bd90df5b3", + "0xa3370898a1c5f33d15adb4238df9a6c945f18b9ada4ce2624fc32a844f9ece4c916a64e9442225b6592afa06d2e015f2", + "0x817efb8a791435e4236f7d7b278181a5fa34587578c629dbc14fbf9a5c26772290611395eecd20222a4c58649fc256d8", + "0xa04c9876acf2cfdc8ef96de4879742709270fa1d03fe4c8511fbef2d59eb0aaf0336fa2c7dfe41a651157377fa217813", + "0x81e15875d7ea7f123e418edf14099f2e109d4f3a6ce0eb65f67fe9fb10d2f809a864a29f60ad3fc949f89e2596b21783", + "0xb49f529975c09e436e6bc202fdc16e3fdcbe056db45178016ad6fdece9faad4446343e83aed096209690b21a6910724f", + "0x879e8eda589e1a279f7f49f6dd0580788c040d973748ec4942dbe51ea8fbd05983cc919b78f0c6b92ef3292ae29db875", + "0x81a2b74b2118923f34139a102f3d95e7eee11c4c2929c2576dee200a5abfd364606158535a6c9e4178a6a83dbb65f3c4", + "0x8913f281d8927f2b45fc815d0f7104631cb7f5f7278a316f1327d670d15868daadd2a64e3eb98e1f53fe7e300338cc80", + "0xa6f815fba7ef9af7fbf45f93bc952e8b351f5de6568a27c7c47a00cb39a254c6b31753794f67940fc7d2e9cc581529f4", + "0xb3722a15c66a0014ce4d082de118def8d39190c15678a472b846225585f3a83756ae1b255b2e3f86a26168878e4773b2", + "0x817ae61ab3d0dd5b6e24846b5a5364b1a7dc2e77432d9fed587727520ae2f307264ea0948c91ad29f0aea3a11ff38624", + "0xb3db467464415fcad36dc1de2d6ba7686772a577cc2619242ac040d6734881a45d3b40ed4588db124e4289cfeec4bbf6", + "0xad66a14f5a54ac69603b16e5f1529851183da77d3cc60867f10aea41339dd5e06a5257982e9e90a352cdd32750f42ee4", + "0xadafa3681ef45d685555601a25a55cf23358319a17f61e2179e704f63df83a73bdd298d12cf6cef86db89bd17119e11d", + "0xa379dc44cb6dd3b9d378c07b2ec654fec7ca2f272de6ba895e3d00d20c9e4c5550498a843c8ac67e4221db2115bedc1c", + "0xb7bf81c267a78efc6b9e5a904574445a6487678d7ef70054e3e93ea6a23f966c2b68787f9164918e3b16d2175459ed92", + "0xb41d66a13a4afafd5760062b77f79de7e6ab8ccacde9c6c5116a6d886912fb491dc027af435b1b44aacc6af7b3c887f2", + "0x9904d23a7c1c1d2e4bab85d69f283eb0a8e26d46e8b7b30224438015c936729b2f0af7c7c54c03509bb0500acb42d8a4", + "0xae30d65e9e20c3bfd603994ae2b175ff691d51f3e24b2d058b3b8556d12ca4c75087809062dddd4aaac81c94d15d8a17", + "0x9245162fab42ac01527424f6013310c3eb462982518debef6c127f46ba8a06c705d7dc9f0a41e796ba8d35d60ae6cc64", + "0x87fab853638d7a29a20f3ba2b1a7919d023e9415bfa78ebb27973d8cbc7626f584dc5665d2e7ad71f1d760eba9700d88", + "0x85aac46ecd330608e5272430970e6081ff02a571e8ea444f1e11785ea798769634a22a142d0237f67b75369d3c484a8a", + "0x938c85ab14894cc5dfce3d80456f189a2e98eddbc8828f4ff6b1df1dcb7b42b17ca2ff40226a8a1390a95d63dca698dd", + "0xa18ce1f846e3e3c4d846822f60271eecf0f5d7d9f986385ac53c5ace9589dc7c0188910448c19b91341a1ef556652fa9", + "0x8611608a9d844f0e9d7584ad6ccf62a5087a64f764caf108db648a776b5390feb51e5120f0ef0e9e11301af3987dd7dc", + "0x8106333ba4b4de8d1ae43bc9735d3fea047392e88efd6a2fa6f7b924a18a7a265ca6123c3edc0f36307dd7fb7fe89257", + "0xa91426fa500951ff1b051a248c050b7139ca30dde8768690432d597d2b3c4357b11a577be6b455a1c5d145264dcf81fc", + "0xb7f9f90e0e450f37b081297f7f651bad0496a8b9afd2a4cf4120a2671aaaa8536dce1af301258bfbfdb122afa44c5048", + "0x84126da6435699b0c09fa4032dec73d1fca21d2d19f5214e8b0bea43267e9a8dd1fc44f8132d8315e734c8e2e04d7291", + "0xaff064708103884cb4f1a3c1718b3fc40a238d35cf0a7dc24bdf9823693b407c70da50df585bf5bc4e9c07d1c2d203e8", + "0xa8b40fc6533752983a5329c31d376c7a5c13ce6879cc7faee648200075d9cd273537001fb4c86e8576350eaac6ba60c2", + "0xa02db682bdc117a84dcb9312eb28fcbde12d49f4ce915cc92c610bb6965ec3cc38290f8c5b5ec70afe153956692cda95", + "0x86decd22b25d300508472c9ce75d3e465b737e7ce13bc0fcce32835e54646fe12322ba5bc457be18bfd926a1a6ca4a38", + "0xa18666ef65b8c2904fd598791f5627207165315a85ee01d5fb0e6b2e10bdd9b00babc447da5bd63445e3337de33b9b89", + "0x89bb0c06effadefdaf34ffe4b123e1678a90d4451ee856c863df1e752eef41fd984689ded8f0f878bf8916d5dd8e8024", + "0x97cfcba08ebec05d0073992a66b1d7d6fb9d95871f2cdc36db301f78bf8069294d1c259efef5c93d20dc937eedae3a1a", + "0xac2643b14ece79dcb2e289c96776a47e2bebd40dd6dc74fd035df5bb727b5596f40e3dd2d2202141e69b0993717ede09", + "0xa5e6fd88a2f9174d9bd4c6a55d9c30974be414992f22aa852f552c7648f722ed8077acf5aba030abd47939bb451b2c60", + "0x8ad40a612824a7994487731a40b311b7349038c841145865539c6ada75c56de6ac547a1c23df190e0caaafecddd80ccc", + "0x953a7cea1d857e09202c438c6108060961f195f88c32f0e012236d7a4b39d840c61b162ec86436e8c38567328bea0246", + "0x80d8b47a46dae1868a7b8ccfe7029445bbe1009dad4a6c31f9ef081be32e8e1ac1178c3c8fb68d3e536c84990cc035b1", + "0x81ecd99f22b3766ce0aca08a0a9191793f68c754fdec78b82a4c3bdc2db122bbb9ebfd02fc2dcc6e1567a7d42d0cc16a", + "0xb1dd0446bccc25846fb95d08c1c9cc52fb51c72c4c5d169ffde56ecfe800f108dc1106d65d5c5bd1087c656de3940b63", + "0xb87547f0931e164e96de5c550ca5aa81273648fe34f6e193cd9d69cf729cb432e17aa02e25b1c27a8a0d20a3b795e94e", + "0x820a94e69a927e077082aae66f6b292cfbe4589d932edf9e68e268c9bd3d71ef76cf7d169dd445b93967c25db11f58f1", + "0xb0d07ddf2595270c39adfa0c8cf2ab1322979b0546aa4d918f641be53cd97f36c879bb75d205e457c011aca3bbd9f731", + "0x8700b876b35b4b10a8a9372c5230acecd39539c1bb87515640293ad4464a9e02929d7d6a6a11112e8a29564815ac0de4", + "0xa61a601c5bb27dcb97e37c8e2b9ce479c6b192a5e04d9ed5e065833c5a1017ee5f237b77d1a17be5d48f8e7cc0bcacf6", + "0x92fb88fe774c1ba1d4a08cae3c0e05467ad610e7a3f1d2423fd47751759235fe0a3036db4095bd6404716aa03820f484", + "0xb274f140d77a3ce0796f5e09094b516537ccaf27ae1907099bff172e6368ba85e7c3ef8ea2a07457cac48ae334da95b3", + "0xb2292d9181f16581a9a9142490b2bdcdfb218ca6315d1effc8592100d792eb89d5356996c890441f04f2b4a95763503e", + "0x8897e73f576d86bc354baa3bd96e553107c48cf5889dcc23c5ba68ab8bcd4e81f27767be2233fdfa13d39f885087e668", + "0xa29eac6f0829791c728d71abc49569df95a4446ecbfc534b39f24f56c88fe70301838dfc1c19751e7f3c5c1b8c6af6a0", + "0x9346dc3720adc5df500a8df27fd9c75ef38dc5c8f4e8ed66983304750e66d502c3c59b8e955be781b670a0afc70a2167", + "0x9566d534e0e30a5c5f1428665590617e95fd05d45f573715f58157854ad596ece3a3cfec61356aee342308d623e029d5", + "0xa464fb8bffe6bd65f71938c1715c6e296cc6d0311a83858e4e7eb5873b7f2cf0c584d2101e3407b85b64ca78b2ac93ce", + "0xb54088f7217987c87e9498a747569ac5b2f8afd5348f9c45bf3fd9fbf713a20f495f49c8572d087efe778ac7313ad6d3", + "0x91fa9f5f8000fe050f5b224d90b59fcce13c77e903cbf98ded752e5b3db16adb2bc1f8c94be48b69f65f1f1ad81d6264", + "0x92d04a5b0ac5d8c8e313709b432c9434ecd3e73231f01e9b4e7952b87df60cbfa97b5dedd2200bd033b4b9ea8ba45cc1", + "0xa94b90ad3c3d6c4bbe169f8661a790c40645b40f0a9d1c7220f01cf7fc176e04d80bab0ced9323fcafb93643f12b2760", + "0x94d86149b9c8443b46196f7e5a3738206dd6f3be7762df488bcbb9f9ee285a64c997ed875b7b16b26604fa59020a8199", + "0x82efe4ae2c50a2d7645240c173a047f238536598c04a2c0b69c96e96bd18e075a99110f1206bc213f39edca42ba00cc1", + "0xab8667685f831bc14d4610f84a5da27b4ea5b133b4d991741a9e64dceb22cb64a3ce8f1b6e101d52af6296df7127c9ad", + "0x83ba433661c05dcc5d562f4a9a261c8110dac44b8d833ae1514b1fc60d8b4ee395b18804baea04cb10adb428faf713c3", + "0xb5748f6f660cc5277f1211d2b8649493ed8a11085b871cd33a5aea630abd960a740f08c08be5f9c21574600ac9bf5737", + "0xa5c8dd12af48fb710642ad65ebb97ca489e8206741807f7acfc334f8035d3c80593b1ff2090c9bb7bd138f0c48714ca8", + "0xa2b382fd5744e3babf454b1d806cc8783efeb4761bc42b6914ea48a46a2eae835efbe0a18262b6bc034379e03cf1262b", + "0xb3145ffaf603f69f15a64936d32e3219eea5ed49fdfd2f5bf40ea0dfd974b36fb6ff12164d4c2282d892db4cf3ff3ce1", + "0x87a316fb213f4c5e30c5e3face049db66be4f28821bd96034714ec23d3e97849d7b301930f90a4323c7ccf53de23050c", + "0xb9de09a919455070fed6220fc179c8b7a4c753062bcd27acf28f5b9947a659c0b364298daf7c85c4ca6fca7f945add1f", + "0x806fbd98d411b76979464c40ad88bc07a151628a27fcc1012ba1dfbaf5b5cc9d962fb9b3386008978a12515edce934bc", + "0xa15268877fae0d21610ae6a31061ed7c20814723385955fac09fdc9693a94c33dea11db98bb89fdfe68f933490f5c381", + "0x8d633fb0c4da86b2e0b37d8fad5972d62bff2ac663c5ec815d095cd4b7e1fe66ebef2a2590995b57eaf941983c7ad7a4", + "0x8139e5dd9cf405e8ef65f11164f0440827d98389ce1b418b0c9628be983a9ddd6cf4863036ccb1483b40b8a527acd9ed", + "0x88b15fa94a08eac291d2b94a2b30eb851ff24addf2cc30b678e72e32cfcb3424cf4b33aa395d741803f3e578ddf524de", + "0xb5eaf0c8506e101f1646bcf049ee38d99ea1c60169730da893fd6020fd00a289eb2f415947e44677af49e43454a7b1be", + "0x8489822ad0647a7e06aa2aa5595960811858ddd4542acca419dd2308a8c5477648f4dd969a6740bb78aa26db9bfcc555", + "0xb1e9a7b9f3423c220330d45f69e45fa03d7671897cf077f913c252e3e99c7b1b1cf6d30caad65e4228d5d7b80eb86e5e", + "0xb28fe9629592b9e6a55a1406903be76250b1c50c65296c10c5e48c64b539fb08fe11f68cf462a6edcbba71b0cee3feb2", + "0xa41acf96a02c96cd8744ff6577c244fc923810d17ade133587e4c223beb7b4d99fa56eae311a500d7151979267d0895c", + "0x880798938fe4ba70721be90e666dfb62fcab4f3556fdb7b0dc8ec5bc34f6b4513df965eae78527136eb391889fe2caf9", + "0x98d4d89d358e0fb7e212498c73447d94a83c1b66e98fc81427ab13acddb17a20f52308983f3a5a8e0aaacec432359604", + "0x81430b6d2998fc78ba937a1639c6020199c52da499f68109da227882dc26d005b73d54c5bdcac1a04e8356a8ca0f7017", + "0xa8d906a4786455eb74613aba4ce1c963c60095ffb8658d368df9266fdd01e30269ce10bf984e7465f34b4fd83beba26a", + "0xaf54167ac1f954d10131d44a8e0045df00d581dd9e93596a28d157543fbe5fb25d213806ed7fb3cba6b8f5b5423562db", + "0x8511e373a978a12d81266b9afbd55035d7bc736835cfa921903a92969eeba3624437d1346b55382e61415726ab84a448", + "0x8cf43eea93508ae586fa9a0f1354a1e16af659782479c2040874a46317f9e8d572a23238efa318fdfb87cc63932602b7", + "0xb0bdd3bacff077173d302e3a9678d1d37936188c7ecc34950185af6b462b7c679815176f3cce5db19aac8b282f2d60ad", + "0xa355e9b87f2f2672052f5d4d65b8c1c827d24d89b0d8594641fccfb69aef1b94009105f3242058bb31c8bf51caae5a41", + "0xb8baa9e4b950b72ff6b88a6509e8ed1304bc6fd955748b2e59a523a1e0c5e99f52aec3da7fa9ff407a7adf259652466c", + "0x840bc3dbb300ea6f27d1d6dd861f15680bd098be5174f45d6b75b094d0635aced539fa03ddbccb453879de77fb5d1fe9", + "0xb4bc7e7e30686303856472bae07e581a0c0bfc815657c479f9f5931cff208d5c12930d2fd1ff413ebd8424bcd7a9b571", + "0x89b5d514155d7999408334a50822508b9d689add55d44a240ff2bdde2eee419d117031f85e924e2a2c1ca77db9b91eea", + "0xa8604b6196f87a04e1350302e8aa745bba8dc162115d22657b37a1d1a98cb14876ddf7f65840b5dbd77e80cd22b4256c", + "0x83cb7acdb9e03247515bb2ce0227486ccf803426717a14510f0d59d45e998b245797d356f10abca94f7a14e1a2f0d552", + "0xaeb3266a9f16649210ab2df0e1908ac259f34ce1f01162c22b56cf1019096ee4ea5854c36e30bb2feb06c21a71e8a45c", + "0x89e72e86edf2aa032a0fc9acf4d876a40865fbb2c8f87cb7e4d88856295c4ac14583e874142fd0c314a49aba68c0aa3c", + "0x8c3576eba0583c2a7884976b4ed11fe1fda4f6c32f6385d96c47b0e776afa287503b397fa516a455b4b8c3afeedc76db", + "0xa31e5b633bda9ffa174654fee98b5d5930a691c3c42fcf55673d927dbc8d91c58c4e42e615353145431baa646e8bbb30", + "0x89f2f3f7a8da1544f24682f41c68114a8f78c86bd36b066e27da13acb70f18d9f548773a16bd8e24789420e17183f137", + "0xada27fa4e90a086240c9164544d2528621a415a5497badb79f8019dc3dce4d12eb6b599597e47ec6ac39c81efda43520", + "0x90dc1eb21bf21c0187f359566fc4bf5386abea52799306a0e5a1151c0817c5f5bc60c86e76b1929c092c0f3ff48cedd2", + "0xb702a53ebcc17ae35d2e735a347d2c700e9cbef8eadbece33cac83df483b2054c126593e1f462cfc00a3ce9d737e2af5", + "0x9891b06455ec925a6f8eafffba05af6a38cc5e193acaaf74ffbf199df912c5197106c5e06d72942bbb032ce277b6417f", + "0x8c0ee71eb01197b019275bcf96cae94e81d2cdc3115dbf2d8e3080074260318bc9303597e8f72b18f965ad601d31ec43", + "0x8aaf580aaf75c1b7a5f99ccf60503506e62058ef43b28b02f79b8536a96be3f019c9f71caf327b4e6730134730d1bef5", + "0xae6f9fc21dd7dfa672b25a87eb0a41644f7609fab5026d5cedb6e43a06dbbfd6d6e30322a2598c8dedde88c52eaed626", + "0x8159b953ffece5693edadb2e906ebf76ff080ee1ad22698950d2d3bfc36ac5ea78f58284b2ca180664452d55bd54716c", + "0xab7647c32ca5e9856ac283a2f86768d68de75ceeba9e58b74c5324f8298319e52183739aba4340be901699d66ac9eb3f", + "0xa4d85a5701d89bcfaf1572db83258d86a1a0717603d6f24ac2963ffcf80f1265e5ab376a4529ca504f4396498791253c", + "0x816080c0cdbfe61b4d726c305747a9eb58ac26d9a35f501dd32ba43c098082d20faf3ccd41aad24600aa73bfa453dfac", + "0x84f3afac024f576b0fd9acc6f2349c2fcefc3f77dbe5a2d4964d14b861b88e9b1810334b908cf3427d9b67a8aee74b18", + "0x94b390655557b1a09110018e9b5a14490681ade275bdc83510b6465a1218465260d9a7e2a6e4ec700f58c31dc3659962", + "0xa8c66826b1c04a2dd4c682543242e7a57acae37278bd09888a3d17747c5b5fec43548101e6f46d703638337e2fd3277b", + "0x86e6f4608a00007fa533c36a5b054c5768ccafe41ad52521d772dcae4c8a4bcaff8f7609be30d8fab62c5988cbbb6830", + "0x837da4cf09ae8aa0bceb16f8b3bfcc3b3367aecac9eed6b4b56d7b65f55981ef066490764fb4c108792623ecf8cad383", + "0x941ff3011462f9b5bf97d8cbdb0b6f5d37a1b1295b622f5485b7d69f2cb2bcabc83630dae427f0259d0d9539a77d8424", + "0xb99e5d6d82aa9cf7d5970e7f710f4039ac32c2077530e4c2779250c6b9b373bc380adb0a03b892b652f649720672fc8c", + "0xa791c78464b2d65a15440b699e1e30ebd08501d6f2720adbc8255d989a82fcded2f79819b5f8f201bed84a255211b141", + "0x84af7ad4a0e31fcbb3276ab1ad6171429cf39adcf78dc03750dc5deaa46536d15591e26d53e953dfb31e1622bc0743ab", + "0xa833e62fe97e1086fae1d4917fbaf09c345feb6bf1975b5cb863d8b66e8d621c7989ab3dbecda36bc9eaffc5eaa6fa66", + "0xb4ef79a46a2126f53e2ebe62770feb57fd94600be29459d70a77c5e9cc260fa892be06cd60f886bf48459e48eb50d063", + "0xb43b8f61919ea380bf151c294e54d3a3ff98e20d1ee5efbfe38aa2b66fafbc6a49739793bd5cb1c809f8b30466277c3a", + "0xab37735af2412d2550e62df9d8b3b5e6f467f20de3890bf56faf1abf2bf3bd1d98dc3fa0ad5e7ab3fce0fa20409eb392", + "0x82416b74b1551d484250d85bb151fabb67e29cce93d516125533df585bc80779ab057ea6992801a3d7d5c6dcff87a018", + "0x8145d0787f0e3b5325190ae10c1d6bee713e6765fb6a0e9214132c6f78f4582bb2771aaeae40d3dad4bafb56bf7e36d8", + "0xb6935886349ecbdd5774e12196f4275c97ec8279fdf28ccf940f6a022ebb6de8e97d6d2173c3fe402cbe9643bed3883b", + "0x87ef9b4d3dc71ac86369f8ed17e0dd3b91d16d14ae694bc21a35b5ae37211b043d0e36d8ff07dcc513fb9e6481a1f37f", + "0xae1d0ded32f7e6f1dc8fef495879c1d9e01826f449f903c1e5034aeeabc5479a9e323b162b688317d46d35a42d570d86", + "0xa40d16497004db4104c6794e2f4428d75bdf70352685944f3fbe17526df333e46a4ca6de55a4a48c02ecf0bde8ba03c0", + "0x8d45121efba8cc308a498e8ee39ea6fa5cae9fb2e4aab1c2ff9d448aa8494ccbec9a078f978a86fcd97b5d5e7be7522a", + "0xa8173865c64634ba4ac2fa432740f5c05056a9deaf6427cb9b4b8da94ca5ddbc8c0c5d3185a89b8b28878194de9cdfcd", + "0xb6ec06a74d690f6545f0f0efba236e63d1fdfba54639ca2617408e185177ece28901c457d02b849fd00f1a53ae319d0a", + "0xb69a12df293c014a40070e3e760169b6f3c627caf9e50b35a93f11ecf8df98b2bc481b410eecb7ab210bf213bbe944de", + "0x97e7dc121795a533d4224803e591eef3e9008bab16f12472210b73aaf77890cf6e3877e0139403a0d3003c12c8f45636", + "0xacdfa6fdd4a5acb7738cc8768f7cba84dbb95c639399b291ae8e4e63df37d2d4096900a84d2f0606bf534a9ccaa4993f", + "0x86ee253f3a9446a33e4d1169719b7d513c6b50730988415382faaf751988c10a421020609f7bcdef91be136704b906e2", + "0xaac9438382a856caf84c5a8a234282f71b5fc5f65219103b147e7e6cf565522285fbfd7417b513bdad8277a00f652ca1", + "0x83f3799d8e5772527930f5dc071a2e0a65471618993ec8990a96ccdeee65270e490bda9d26bb877612475268711ffd80", + "0x93f28a81ac8c0ec9450b9d762fae9c7f8feaace87a6ee6bd141ef1d2d0697ef1bbd159fe6e1de640dbdab2b0361fca8a", + "0xa0825c95ba69999b90eac3a31a3fd830ea4f4b2b7409bde5f202b61d741d6326852ce790f41de5cb0eccec7af4db30c1", + "0x83924b0e66233edd603c3b813d698daa05751fc34367120e3cf384ea7432e256ccee4d4daf13858950549d75a377107d", + "0x956fd9fa58345277e06ba2ec72f49ed230b8d3d4ff658555c52d6cddeb84dd4e36f1a614f5242d5ca0192e8daf0543c2", + "0x944869912476baae0b114cced4ff65c0e4c90136f73ece5656460626599051b78802df67d7201c55d52725a97f5f29fe", + "0x865cb25b64b4531fb6fe4814d7c8cd26b017a6c6b72232ff53defc18a80fe3b39511b23f9e4c6c7249d06e03b2282ed2", + "0x81e09ff55214960775e1e7f2758b9a6c4e4cd39edf7ec1adfaad51c52141182b79fe2176b23ddc7df9fd153e5f82d668", + "0xb31006896f02bc90641121083f43c3172b1039334501fbaf1672f7bf5d174ddd185f945adf1a9c6cf77be34c5501483d", + "0x88b92f6f42ae45e9f05b16e52852826e933efd0c68b0f2418ac90957fd018df661bc47c8d43c2a7d7bfcf669dab98c3c", + "0x92fc68f595853ee8683930751789b799f397135d002eda244fe63ecef2754e15849edde3ba2f0cc8b865c9777230b712", + "0x99ca06a49c5cd0bb097c447793fcdd809869b216a34c66c78c7e41e8c22f05d09168d46b8b1f3390db9452d91bc96dea", + "0xb48b9490a5d65296802431852d548d81047bbefc74fa7dc1d4e2a2878faacdfcb365ae59209cb0ade01901a283cbd15d", + "0xaff0fdbef7c188b120a02bc9085d7b808e88f73973773fef54707bf2cd772cd066740b1b6f4127b5c349f657bd97e738", + "0x966fd4463b4f43dd8ccba7ad50baa42292f9f8b2e70da23bb6780e14155d9346e275ef03ddaf79e47020dcf43f3738bd", + "0x9330c3e1fadd9e08ac85f4839121ae20bbeb0a5103d84fa5aadbd1213805bdcda67bf2fb75fc301349cbc851b5559d20", + "0x993bb99867bd9041a71a55ad5d397755cfa7ab6a4618fc526179bfc10b7dc8b26e4372fe9a9b4a15d64f2b63c1052dda", + "0xa29b59bcfab51f9b3c490a3b96f0bf1934265c315349b236012adbd64a56d7f6941b2c8cc272b412044bc7731f71e1dc", + "0xa65c9cefe1fc35d089fe8580c2e7671ebefdb43014ac291528ff4deefd4883fd4df274af83711dad610dad0d615f9d65", + "0x944c78c56fb227ae632805d448ca3884cd3d2a89181cead3d2b7835e63297e6d740aa79a112edb1d4727824991636df5", + "0xa73d782da1db7e4e65d7b26717a76e16dd9fab4df65063310b8e917dc0bc24e0d6755df5546c58504d04d9e68c3b474a", + "0xaf80f0b87811ae3124f68108b4ca1937009403f87928bbc53480e7c5408d072053ace5eeaf5a5aba814dab8a45502085", + "0x88aaf1acfc6e2e19b8387c97da707cb171c69812fefdd4650468e9b2c627bd5ccfb459f4d8e56bdfd84b09ddf87e128f", + "0x92c97276ff6f72bab6e9423d02ad6dc127962dbce15a0dd1e4a393b4510c555df6aa27be0f697c0d847033a9ca8b8dfd", + "0xa0e07d43d96e2d85b6276b3c60aadb48f0aedf2de8c415756dc597249ea64d2093731d8735231dadc961e5682ac59479", + "0xadc9e6718a8f9298957d1da3842a7751c5399bbdf56f8de6c1c4bc39428f4aee6f1ba6613d37bf46b9403345e9d6fc81", + "0x951da434da4b20d949b509ceeba02e24da7ed2da964c2fcdf426ec787779c696b385822c7dbea4df3e4a35921f1e912c", + "0xa04cbce0d2b2e87bbf038c798a12ec828423ca6aca08dc8d481cf6466e3c9c73d4d4a7fa47df9a7e2e15aae9e9f67208", + "0x8f855cca2e440d248121c0469de1f94c2a71b8ee2682bbad3a78243a9e03da31d1925e6760dbc48a1957e040fae9abe8", + "0xb642e5b17c1df4a4e101772d73851180b3a92e9e8b26c918050f51e6dd3592f102d20b0a1e96f0e25752c292f4c903ff", + "0xa92454c300781f8ae1766dbbb50a96192da7d48ef4cbdd72dd8cbb44c6eb5913c112cc38e9144615fdc03684deb99420", + "0x8b74f7e6c2304f8e780df4649ef8221795dfe85fdbdaa477a1542d135b75c8be45bf89adbbb6f3ddf54ca40f02e733e9", + "0x85cf66292cbb30cec5fd835ab10c9fcb3aea95e093aebf123e9a83c26f322d76ebc89c4e914524f6c5f6ee7d74fc917d", + "0xae0bfe0cdc97c09542a7431820015f2d16067b30dca56288013876025e81daa8c519e5e347268e19aa1a85fa1dc28793", + "0x921322fc6a47dc091afa0ad6df18ed14cde38e48c6e71550aa513918b056044983aee402de21051235eecf4ce8040fbe", + "0x96c030381e97050a45a318d307dcb3c8377b79b4dd5daf6337cded114de26eb725c14171b9b8e1b3c08fe1f5ea6b49e0", + "0x90c23b86b6111818c8baaf53a13eaee1c89203b50e7f9a994bf0edf851919b48edbac7ceef14ac9414cf70c486174a77", + "0x8bf6c301240d2d1c8d84c71d33a6dfc6d9e8f1cfae66d4d0f7a256d98ae12b0bcebfa94a667735ee89f810bcd7170cff", + "0xa41a4ffbbea0e36874d65c009ee4c3feffff322f6fc0e30d26ee4dbc1f46040d05e25d9d0ecb378cef0d24a7c2c4b850", + "0xa8d4cdd423986bb392a0a92c12a8bd4da3437eec6ef6af34cf5310944899287452a2eb92eb5386086d5063381189d10e", + "0xa81dd26ec057c4032a4ed7ad54d926165273ed51d09a1267b2e477535cf6966835a257c209e4e92d165d74fa75695fa3", + "0x8d7f708c3ee8449515d94fc26b547303b53d8dd55f177bc3b25d3da2768accd9bc8e9f09546090ebb7f15c66e6c9c723", + "0x839ba65cffcd24cfffa7ab3b21faabe3c66d4c06324f07b2729c92f15cad34e474b0f0ddb16cd652870b26a756b731d3", + "0x87f1a3968afec354d92d77e2726b702847c6afcabb8438634f9c6f7766de4c1504317dc4fa9a4a735acdbf985e119564", + "0x91a8a7fd6542f3e0673f07f510d850864b34ac087eb7eef8845a1d14b2b1b651cbdc27fa4049bdbf3fea54221c5c8549", + "0xaef3cf5f5e3a2385ead115728d7059e622146c3457d266c612e778324b6e06fbfb8f98e076624d2f3ce1035d65389a07", + "0x819915d6232e95ccd7693fdd78d00492299b1983bc8f96a08dcb50f9c0a813ed93ae53c0238345d5bea0beda2855a913", + "0x8e9ba68ded0e94935131b392b28218315a185f63bf5e3c1a9a9dd470944509ca0ba8f6122265f8da851b5cc2abce68f1", + "0xb28468e9b04ee9d69003399a3cf4457c9bf9d59f36ab6ceeb8e964672433d06b58beeea198fedc7edbaa1948577e9fa2", + "0xa633005e2c9f2fd94c8bce2dd5bb708fe946b25f1ec561ae65e54e15cdd88dc339f1a083e01f0d39610c8fe24151aaf0", + "0x841d0031e22723f9328dd993805abd13e0c99b0f59435d2426246996b08d00ce73ab906f66c4eab423473b409e972ce0", + "0x85758d1b084263992070ec8943f33073a2d9b86a8606672550c17545507a5b3c88d87382b41916a87ee96ff55a7aa535", + "0x8581b06b0fc41466ef94a76a1d9fb8ae0edca6d018063acf6a8ca5f4b02d76021902feba58972415691b4bdbc33ae3b4", + "0x83539597ff5e327357ee62bc6bf8c0bcaec2f227c55c7c385a4806f0d37fb461f1690bad5066b8a5370950af32fafbef", + "0xaee3557290d2dc10827e4791d00e0259006911f3f3fce4179ed3c514b779160613eca70f720bff7804752715a1266ffa", + "0xb48d2f0c4e90fc307d5995464e3f611a9b0ef5fe426a289071f4168ed5cc4f8770c9332960c2ca5c8c427f40e6bb389f", + "0x847af8973b4e300bb06be69b71b96183fd1a0b9d51b91701bef6fcfde465068f1eb2b1503b07afda380f18d69de5c9e1", + "0xa70a6a80ce407f07804c0051ac21dc24d794b387be94eb24e1db94b58a78e1bcfb48cd0006db8fc1f9bedaece7a44fbe", + "0xb40e942b8fa5336910ff0098347df716bff9d1fa236a1950c16eeb966b3bc1a50b8f7b0980469d42e75ae13ced53cead", + "0xb208fabaa742d7db3148515330eb7a3577487845abdb7bd9ed169d0e081db0a5816595c33d375e56aeac5b51e60e49d3", + "0xb7c8194b30d3d6ef5ab66ec88ad7ebbc732a3b8a41731b153e6f63759a93f3f4a537eab9ad369705bd730184bdbbdc34", + "0x9280096445fe7394d04aa1bc4620c8f9296e991cc4d6c131bd703cb1cc317510e6e5855ac763f4d958c5edfe7eebeed7", + "0xabc2aa4616a521400af1a12440dc544e3c821313d0ab936c86af28468ef8bbe534837e364598396a81cf8d06274ed5a6", + "0xb18ca8a3325adb0c8c18a666d4859535397a1c3fe08f95eebfac916a7a99bbd40b3c37b919e8a8ae91da38bc00fa56c0", + "0x8a40c33109ecea2a8b3558565877082f79121a432c45ec2c5a5e0ec4d1c203a6788e6b69cb37f1fd5b8c9a661bc5476d", + "0x88c47301dd30998e903c84e0b0f2c9af2e1ce6b9f187dab03528d44f834dc991e4c86d0c474a2c63468cf4020a1e24a0", + "0x920c832853e6ab4c851eecfa9c11d3acc7da37c823be7aa1ab15e14dfd8beb5d0b91d62a30cec94763bd8e4594b66600", + "0x98e1addbe2a6b8edc7f12ecb9be81c3250aeeca54a1c6a7225772ca66549827c15f3950d01b8eb44aecb56fe0fff901a", + "0x8cfb0fa1068be0ec088402f5950c4679a2eb9218c729da67050b0d1b2d7079f3ddf4bf0f57d95fe2a8db04bc6bcdb20c", + "0xb70f381aafe336b024120453813aeab70baac85b9c4c0f86918797b6aee206e6ed93244a49950f3d8ec9f81f4ac15808", + "0xa4c8edf4aa33b709a91e1062939512419711c1757084e46f8f4b7ed64f8e682f4e78b7135920c12f0eb0422fe9f87a6a", + "0xb4817e85fd0752d7ebb662d3a51a03367a84bac74ebddfba0e5af5e636a979500f72b148052d333b3dedf9edd2b4031b", + "0xa87430169c6195f5d3e314ff2d1c2f050e766fd5d2de88f5207d72dba4a7745bb86d0baca6e9ae156582d0d89e5838c7", + "0x991b00f8b104566b63a12af4826b61ce7aa40f4e5b8fff3085e7a99815bdb4471b6214da1e480214fac83f86a0b93cc5", + "0xb39966e3076482079de0678477df98578377a094054960ee518ef99504d6851f8bcd3203e8da5e1d4f6f96776e1fe6eb", + "0xa448846d9dc2ab7a0995fa44b8527e27f6b3b74c6e03e95edb64e6baa4f1b866103f0addb97c84bef1d72487b2e21796", + "0x894bec21a453ae84b592286e696c35bc30e820e9c2fd3e63dd4fbe629e07df16439c891056070faa490155f255bf7187", + "0xa9ec652a491b11f6a692064e955f3f3287e7d2764527e58938571469a1e29b5225b9415bd602a45074dfbfe9c131d6ca", + "0xb39d37822e6cbe28244b5f42ce467c65a23765bd16eb6447c5b3e942278069793763483dafd8c4dd864f8917aad357fe", + "0x88dba51133f2019cb266641c56101e3e5987d3b77647a2e608b5ff9113dfc5f85e2b7c365118723131fbc0c9ca833c9c", + "0xb566579d904b54ecf798018efcb824dccbebfc6753a0fd2128ac3b4bd3b038c2284a7c782b5ca6f310eb7ea4d26a3f0a", + "0xa97a55c0a492e53c047e7d6f9d5f3e86fb96f3dddc68389c0561515343b66b4bc02a9c0d5722dff1e3445308240b27f7", + "0xa044028ab4bcb9e1a2b9b4ca4efbf04c5da9e4bf2fff0e8bd57aa1fc12a71e897999c25d9117413faf2f45395dee0f13", + "0xa78dc461decbeaeed8ebd0909369b491a5e764d6a5645a7dac61d3140d7dc0062526f777b0eb866bff27608429ebbdde", + "0xb2c2a8991f94c39ca35fea59f01a92cb3393e0eccb2476dfbf57261d406a68bd34a6cff33ed80209991688c183609ef4", + "0x84189eefb521aff730a4fd3fd5b10ddfd29f0d365664caef63bb015d07e689989e54c33c2141dd64427805d37a7e546e", + "0x85ac80bd734a52235da288ff042dea9a62e085928954e8eacd2c751013f61904ed110e5b3afe1ab770a7e6485efb7b5e", + "0x9183a560393dcb22d0d5063e71182020d0fbabb39e32493eeffeb808df084aa243eb397027f150b55a247d1ed0c8513e", + "0x81c940944df7ecc58d3c43c34996852c3c7915ed185d7654627f7af62abae7e0048dd444a6c09961756455000bd96d09", + "0xaa8c34e164019743fd8284b84f06c3b449aae7996e892f419ee55d82ad548cb300fd651de329da0384243954c0ef6a60", + "0x89a7b7bdfc7e300d06a14d463e573d6296d8e66197491900cc9ae49504c4809ff6e61b758579e9091c61085ba1237b83", + "0x878d21809ba540f50bd11f4c4d9590fb6f3ab9de5692606e6e2ef4ed9d18520119e385be5e1f4b3f2e2b09c319f0e8fc", + "0x8eb248390193189cf0355365e630b782cd15751e672dc478b39d75dc681234dcd9309df0d11f4610dbb249c1e6be7ef9", + "0xa1d7fb3aecb896df3a52d6bd0943838b13f1bd039c936d76d03de2044c371d48865694b6f532393b27fd10a4cf642061", + "0xa34bca58a24979be442238cbb5ece5bee51ae8c0794dd3efb3983d4db713bc6f28a96e976ac3bd9a551d3ed9ba6b3e22", + "0x817c608fc8cacdd178665320b5a7587ca21df8bdd761833c3018b967575d25e3951cf3d498a63619a3cd2ad4406f5f28", + "0x86c95707db0495689afd0c2e39e97f445f7ca0edffad5c8b4cacd1421f2f3cc55049dfd504f728f91534e20383955582", + "0x99c3b0bb15942c301137765d4e19502f65806f3b126dc01a5b7820c87e8979bce6a37289a8f6a4c1e4637227ad5bf3bf", + "0x8aa1518a80ea8b074505a9b3f96829f5d4afa55a30efe7b4de4e5dbf666897fdd2cf31728ca45921e21a78a80f0e0f10", + "0x8d74f46361c79e15128ac399e958a91067ef4cec8983408775a87eca1eed5b7dcbf0ddf30e66f51780457413496c7f07", + "0xa41cde4a786b55387458a1db95171aca4fd146507b81c4da1e6d6e495527c3ec83fc42fad1dfe3d92744084a664fd431", + "0x8c352852c906fae99413a84ad11701f93f292fbf7bd14738814f4c4ceab32db02feb5eb70bc73898b0bc724a39d5d017", + "0xa5993046e8f23b71ba87b7caa7ace2d9023fb48ce4c51838813174880d918e9b4d2b0dc21a2b9c6f612338c31a289df8", + "0x83576d3324bf2d8afbfb6eaecdc5d767c8e22e7d25160414924f0645491df60541948a05e1f4202e612368e78675de8a", + "0xb43749b8df4b15bc9a3697e0f1c518e6b04114171739ef1a0c9c65185d8ec18e40e6954d125cbc14ebc652cf41ad3109", + "0xb4eebd5d80a7327a040cafb9ccdb12b2dfe1aa86e6bc6d3ac8a57fadfb95a5b1a7332c66318ff72ba459f525668af056", + "0x9198be7f1d413c5029b0e1c617bcbc082d21abe2c60ec8ce9b54ca1a85d3dba637b72fda39dae0c0ae40d047eab9f55a", + "0x8d96a0232832e24d45092653e781e7a9c9520766c3989e67bbe86b3a820c4bf621ea911e7cd5270a4bfea78b618411f6", + "0x8d7160d0ea98161a2d14d46ef01dff72d566c330cd4fabd27654d300e1bc7644c68dc8eabf2a20a59bfe7ba276545f9b", + "0xabb60fce29dec7ba37e3056e412e0ec3e05538a1fc0e2c68877378c867605966108bc5742585ab6a405ce0c962b285b6", + "0x8fabffa3ed792f05e414f5839386f6449fd9f7b41a47595c5d71074bd1bb3784cc7a1a7e1ad6b041b455035957e5b2dc", + "0x90ff017b4804c2d0533b72461436b10603ab13a55f86fd4ec11b06a70ef8166f958c110519ca1b4cc7beba440729fe2d", + "0xb340cfd120f6a4623e3a74cf8c32bfd7cd61a280b59dfd17b15ca8fae4d82f64a6f15fbde4c02f424debc72b7db5fe67", + "0x871311c9c7220c932e738d59f0ecc67a34356d1429fe570ca503d340c9996cb5ee2cd188fad0e3bd16e4c468ec1dbebd", + "0xa772470262186e7b94239ba921b29f2412c148d6f97c4412e96d21e55f3be73f992f1ad53c71008f0558ec3f84e2b5a7", + "0xb2a897dcb7ffd6257f3f2947ec966f2077d57d5191a88840b1d4f67effebe8c436641be85524d0a21be734c63ab5965d", + "0xa044f6eacc48a4a061fa149500d96b48cbf14853469aa4d045faf3dca973be1bd4b4ce01646d83e2f24f7c486d03205d", + "0x981af5dc2daa73f7fa9eae35a93d81eb6edba4a7f673b55d41f6ecd87a37685d31bb40ef4f1c469b3d72f2f18b925a17", + "0x912d2597a07864de9020ac77083eff2f15ceb07600f15755aba61251e8ce3c905a758453b417f04d9c38db040954eb65", + "0x9642b7f6f09394ba5e0805734ef6702c3eddf9eea187ba98c676d5bbaec0e360e3e51dc58433aaa1e2da6060c8659cb7", + "0x8ab3836e0a8ac492d5e707d056310c4c8e0489ca85eb771bff35ba1d658360084e836a6f51bb990f9e3d2d9aeb18fbb5", + "0x879e058e72b73bb1f4642c21ffdb90544b846868139c6511f299aafe59c2d0f0b944dffc7990491b7c4edcd6a9889250", + "0xb9e60b737023f61479a4a8fd253ed0d2a944ea6ba0439bbc0a0d3abf09b0ad1f18d75555e4a50405470ae4990626f390", + "0xb9c2535d362796dcd673640a9fa2ebdaec274e6f8b850b023153b0a7a30fffc87f96e0b72696f647ebe7ab63099a6963", + "0x94aeff145386a087b0e91e68a84a5ede01f978f9dd9fe7bebca78941938469495dc30a96bba9508c0d017873aeea9610", + "0x98b179f8a3d9f0d0a983c30682dd425a2ddc7803be59bd626c623c8951a5179117d1d2a68254c95c9952989877d0ee55", + "0x889ecf5f0ee56938273f74eb3e9ecfb5617f04fb58e83fe4c0e4aef51615cf345bc56f3f61b17f6eed3249d4afd54451", + "0xa0f2b2c39bcea4b50883e2587d16559e246248a66ecb4a4b7d9ab3b51fb39fe98d83765e087eee37a0f86b0ba4144c02", + "0xb2a61e247ed595e8a3830f7973b07079cbda510f28ad8c78c220b26cb6acde4fbb5ee90c14a665f329168ee951b08cf0", + "0x95bd0fcfb42f0d6d8a8e73d7458498a85bcddd2fb132fd7989265648d82ac2707d6d203fac045504977af4f0a2aca4b7", + "0x843e5a537c298666e6cf50fcc044f13506499ef83c802e719ff2c90e85003c132024e04711be7234c04d4b0125512d5d", + "0xa46d1797c5959dcd3a5cfc857488f4d96f74277c3d13b98b133620192f79944abcb3a361d939a100187f1b0856eae875", + "0xa1c7786736d6707a48515c38660615fcec67eb8a2598f46657855215f804fd72ab122d17f94fcffad8893f3be658dca7", + "0xb23dc9e610abc7d8bd21d147e22509a0fa49db5be6ea7057b51aae38e31654b3aa044df05b94b718153361371ba2f622", + "0xb00cc8f257d659c22d30e6d641f79166b1e752ea8606f558e4cad6fc01532e8319ea4ee12265ba4140ac45aa4613c004", + "0xac7019af65221b0cc736287b32d7f1a3561405715ba9a6a122342e04e51637ba911c41573de53e4781f2230fdcb2475f", + "0x81a630bc41b3da8b3eb4bf56cba10cd9f93153c3667f009dc332287baeb707d505fb537e6233c8e53d299ec0f013290c", + "0xa6b7aea5c545bb76df0f230548539db92bc26642572cb7dd3d5a30edca2b4c386f44fc8466f056b42de2a452b81aff5b", + "0x8271624ff736b7b238e43943c81de80a1612207d32036d820c11fc830c737972ccc9c60d3c2359922b06652311e3c994", + "0x8a684106458cb6f4db478170b9ad595d4b54c18bf63b9058f095a2fa1b928c15101472c70c648873d5887880059ed402", + "0xa5cc3c35228122f410184e4326cf61a37637206e589fcd245cb5d0cec91031f8f7586b80503070840fdfd8ce75d3c88b", + "0x9443fc631aed8866a7ed220890911057a1f56b0afe0ba15f0a0e295ab97f604b134b1ed9a4245e46ee5f9a93aa74f731", + "0x984b6f7d79835dffde9558c6bb912d992ca1180a2361757bdba4a7b69dc74b056e303adc69fe67414495dd9c2dd91e64", + "0xb15a5c8cba5de080224c274d31c68ed72d2a7126d347796569aef0c4e97ed084afe3da4d4b590b9dda1a07f0c2ff3dfb", + "0x991708fe9650a1f9a4e43938b91d45dc68c230e05ee999c95dbff3bf79b1c1b2bb0e7977de454237c355a73b8438b1d9", + "0xb4f7edc7468b176a4a7c0273700c444fa95c726af6697028bed4f77eee887e3400f9c42ee15b782c0ca861c4c3b8c98a", + "0x8c60dcc16c51087eb477c13e837031d6c6a3dc2b8bf8cb43c23f48006bc7173151807e866ead2234b460c2de93b31956", + "0x83ad63e9c910d1fc44bc114accfb0d4d333b7ebe032f73f62d25d3e172c029d5e34a1c9d547273bf6c0fead5c8801007", + "0x85de73213cc236f00777560756bdbf2b16841ba4b55902cf2cad9742ecaf5d28209b012ceb41f337456dfeca93010cd7", + "0xa7561f8827ccd75b6686ba5398bb8fc3083351c55a589b18984e186820af7e275af04bcd4c28e1dc11be1e8617a0610b", + "0x88c0a4febd4068850557f497ea888035c7fc9f404f6cc7794e7cc8722f048ad2f249e7dc62743e7a339eb7473ad3b0cd", + "0x932b22b1d3e6d5a6409c34980d176feb85ada1bf94332ef5c9fc4d42b907dabea608ceef9b5595ef3feee195151f18d8", + "0xa2867bb3f5ab88fbdae3a16c9143ab8a8f4f476a2643c505bb9f37e5b1fd34d216cab2204c9a017a5a67b7ad2dda10e8", + "0xb573d5f38e4e9e8a3a6fd82f0880dc049efa492a946d00283019bf1d5e5516464cf87039e80aef667cb86fdea5075904", + "0xb948f1b5ab755f3f5f36af27d94f503b070696d793b1240c1bdfd2e8e56890d69e6904688b5f8ff5a4bdf5a6abfe195f", + "0x917eae95ebc4109a2e99ddd8fec7881d2f7aaa0e25fda44dec7ce37458c2ee832f1829db7d2dcfa4ca0f06381c7fe91d", + "0x95751d17ed00a3030bce909333799bb7f4ab641acf585807f355b51d6976dceee410798026a1a004ef4dcdff7ec0f5b8", + "0xb9b7bd266f449a79bbfe075e429613e76c5a42ac61f01c8f0bbbd34669650682efe01ff9dbbc400a1e995616af6aa278", + "0xac1722d097ce9cd7617161f8ec8c23d68f1fb1c9ca533e2a8b4f78516c2fd8fb38f23f834e2b9a03bb06a9d655693ca9", + "0xa7ad9e96ffd98db2ecdb6340c5d592614f3c159abfd832fe27ee9293519d213a578e6246aae51672ee353e3296858873", + "0x989b8814d5de7937c4acafd000eec2b4cd58ba395d7b25f98cafd021e8efa37029b29ad8303a1f6867923f5852a220eb", + "0xa5bfe6282c771bc9e453e964042d44eff4098decacb89aecd3be662ea5b74506e1357ab26f3527110ba377711f3c9f41", + "0x8900a7470b656639721d2abbb7b06af0ac4222ab85a1976386e2a62eb4b88bfb5b72cf7921ddb3cf3a395d7eeb192a2e", + "0x95a71b55cd1f35a438cf5e75f8ff11c5ec6a2ebf2e4dba172f50bfad7d6d5dca5de1b1afc541662c81c858f7604c1163", + "0x82b5d62fea8db8d85c5bc3a76d68dedd25794cf14d4a7bc368938ffca9e09f7e598fdad2a5aac614e0e52f8112ae62b9", + "0x997173f07c729202afcde3028fa7f52cefc90fda2d0c8ac2b58154a5073140683e54c49ed1f254481070d119ce0ce02a", + "0xaeffb91ccc7a72bbd6ffe0f9b99c9e66e67d59cec2e02440465e9636a613ab3017278cfa72ea8bc4aba9a8dc728cb367", + "0x952743b06e8645894aeb6440fc7a5f62dd3acf96dab70a51e20176762c9751ea5f2ba0b9497ccf0114dc4892dc606031", + "0x874c63baeddc56fbbca2ff6031f8634b745f6e34ea6791d7c439201aee8f08ef5ee75f7778700a647f3b21068513fce6", + "0x85128fec9c750c1071edfb15586435cc2f317e3e9a175bb8a9697bcda1eb9375478cf25d01e7fed113483b28f625122d", + "0x85522c9576fd9763e32af8495ae3928ed7116fb70d4378448926bc9790e8a8d08f98cf47648d7da1b6e40d6a210c7924", + "0x97d0f37a13cfb723b848099ca1c14d83e9aaf2f7aeb71829180e664b7968632a08f6a85f557d74b55afe6242f2a36e7c", + "0xabaa472d6ad61a5fccd1a57c01aa1bc081253f95abbcba7f73923f1f11c4e79b904263890eeb66926de3e2652f5d1c70", + "0xb3c04945ba727a141e5e8aec2bf9aa3772b64d8fd0e2a2b07f3a91106a95cbcb249adcd074cbe498caf76fffac20d4ef", + "0x82c46781a3d730d9931bcabd7434a9171372dde57171b6180e5516d4e68db8b23495c8ac3ab96994c17ddb1cf249b9fb", + "0xa202d8b65613c42d01738ccd68ed8c2dbc021631f602d53f751966e04182743ebc8e0747d600b8a8676b1da9ae7f11ab", + "0xae73e7256e9459db04667a899e0d3ea5255211fb486d084e6550b6dd64ca44af6c6b2d59d7aa152de9f96ce9b58d940d", + "0xb67d87b176a9722945ec7593777ee461809861c6cfd1b945dde9ee4ff009ca4f19cf88f4bbb5c80c9cbab2fe25b23ac8", + "0x8f0b7a317a076758b0dac79959ee4a06c08b07d0f10538a4b53d3da2eda16e2af26922feb32c090330dc4d969cf69bd3", + "0x90b36bf56adbd8c4b6cb32febc3a8d5f714370c2ac3305c10fa6d168dffb2a026804517215f9a2d4ec8310cdb6bb459b", + "0xaa80c19b0682ead69934bf18cf476291a0beddd8ef4ed75975d0a472e2ab5c70f119722a8574ae4973aceb733d312e57", + "0xa3fc9abb12574e5c28dcb51750b4339b794b8e558675eef7d26126edf1de920c35e992333bcbffcbf6a5f5c0d383ce62", + "0xa1573ff23ab972acdcd08818853b111fc757fdd35aa070186d3e11e56b172fb49d840bf297ac0dd222e072fc09f26a81", + "0x98306f2be4caa92c2b4392212d0cbf430b409b19ff7d5b899986613bd0e762c909fc01999aa94be3bd529d67f0113d7f", + "0x8c1fc42482a0819074241746d17dc89c0304a2acdae8ed91b5009e9e3e70ff725ba063b4a3e68fdce05b74f5180c545e", + "0xa6c6113ebf72d8cf3163b2b8d7f3fa24303b13f55752522c660a98cd834d85d8c79214d900fa649499365e2e7641f77a", + "0xab95eea424f8a2cfd9fb1c78bb724e5b1d71a0d0d1e4217c5d0f98b0d8bbd3f8400a2002abc0a0e4576d1f93f46fefad", + "0x823c5a4fd8cf4a75fdc71d5f2dd511b6c0f189b82affeacd2b7cfcad8ad1a5551227dcc9bfdb2e34b2097eaa00efbb51", + "0xb97314dfff36d80c46b53d87a61b0e124dc94018a0bb680c32765b9a2d457f833a7c42bbc90b3b1520c33a182580398d", + "0xb17566ee3dcc6bb3b004afe4c0136dfe7dd27df9045ae896dca49fb36987501ae069eb745af81ba3fc19ff037e7b1406", + "0xb0bdc0f55cfd98d331e3a0c4fbb776a131936c3c47c6bffdc3aaf7d8c9fa6803fbc122c2fefbb532e634228687d52174", + "0xaa5d9e60cc9f0598559c28bb9bdd52aa46605ab4ffe3d192ba982398e72cec9a2a44c0d0d938ce69935693cabc0887ea", + "0x802b6459d2354fa1d56c592ac1346c428dadea6b6c0a87bf7d309bab55c94e1cf31dd98a7a86bd92a840dd51f218b91b", + "0xa526914efdc190381bf1a73dd33f392ecf01350b9d3f4ae96b1b1c3d1d064721c7d6eec5788162c933245a3943f5ee51", + "0xb3b8fcf637d8d6628620a1a99dbe619eabb3e5c7ce930d6efd2197e261bf394b74d4e5c26b96c4b8009c7e523ccfd082", + "0x8f7510c732502a93e095aba744535f3928f893f188adc5b16008385fb9e80f695d0435bfc5b91cdad4537e87e9d2551c", + "0x97b90beaa56aa936c3ca45698f79273a68dd3ccd0076eab48d2a4db01782665e63f33c25751c1f2e070f4d1a8525bf96", + "0xb9fb798324b1d1283fdc3e48288e3861a5449b2ab5e884b34ebb8f740225324af86e4711da6b5cc8361c1db15466602f", + "0xb6d52b53cea98f1d1d4c9a759c25bf9d8a50b604b144e4912acbdbdc32aab8b9dbb10d64a29aa33a4f502121a6fb481c", + "0x9174ffff0f2930fc228f0e539f5cfd82c9368d26b074467f39c07a774367ff6cccb5039ac63f107677d77706cd431680", + "0xa33b6250d4ac9e66ec51c063d1a6a31f253eb29bbaed12a0d67e2eccfffb0f3a52750fbf52a1c2aaba8c7692346426e7", + "0xa97025fd5cbcebe8ef865afc39cd3ea707b89d4e765ec817fd021d6438e02fa51e3544b1fd45470c58007a08efac6edd", + "0xb32a78480edd9ff6ba2f1eec4088db5d6ceb2d62d7e59e904ecaef7bb4a2e983a4588e51692b3be76e6ffbc0b5f911a5", + "0xb5ab590ef0bb77191f00495b33d11c53c65a819f7d0c1f9dc4a2caa147a69c77a4fff7366a602d743ee1f395ce934c1e", + "0xb3fb0842f9441fb1d0ee0293b6efbc70a8f58d12d6f769b12872db726b19e16f0f65efbc891cf27a28a248b0ef9c7e75", + "0x9372ad12856fefb928ccb0d34e198df99e2f8973b07e9d417a3134d5f69e12e79ff572c4e03ccd65415d70639bc7c73e", + "0xaa8d6e83d09ce216bfe2009a6b07d0110d98cf305364d5529c170a23e693aabb768b2016befb5ada8dabdd92b4d012bb", + "0xa954a75791eeb0ce41c85200c3763a508ed8214b5945a42c79bfdcfb1ec4f86ad1dd7b2862474a368d4ac31911a2b718", + "0x8e2081cfd1d062fe3ab4dab01f68062bac802795545fede9a188f6c9f802cb5f884e60dbe866710baadbf55dc77c11a4", + "0xa2f06003b9713e7dd5929501ed485436b49d43de80ea5b15170763fd6346badf8da6de8261828913ee0dacd8ff23c0e1", + "0x98eecc34b838e6ffd1931ca65eec27bcdb2fdcb61f33e7e5673a93028c5865e0d1bf6d3bec040c5e96f9bd08089a53a4", + "0x88cc16019741b341060b95498747db4377100d2a5bf0a5f516f7dec71b62bcb6e779de2c269c946d39040e03b3ae12b7", + "0xad1135ccbc3019d5b2faf59a688eef2500697642be8cfbdf211a1ab59abcc1f24483e50d653b55ff1834675ac7b4978f", + "0xa946f05ed9972f71dfde0020bbb086020fa35b482cce8a4cc36dd94355b2d10497d7f2580541bb3e81b71ac8bba3c49f", + "0xa83aeed488f9a19d8cfd743aa9aa1982ab3723560b1cd337fc2f91ad82f07afa412b3993afb845f68d47e91ba4869840", + "0x95eebe006bfc316810cb71da919e5d62c2cebb4ac99d8e8ef67be420302320465f8b69873470982de13a7c2e23516be9", + "0xa55f8961295a11e91d1e5deadc0c06c15dacbfc67f04ccba1d069cba89d72aa3b3d64045579c3ea8991b150ac29366ae", + "0xb321991d12f6ac07a5de3c492841d1a27b0d3446082fbce93e7e1f9e8d8fe3b45d41253556261c21b70f5e189e1a7a6f", + "0xa0b0822f15f652ce7962a4f130104b97bf9529797c13d6bd8e24701c213cc37f18157bd07f3d0f3eae6b7cd1cb40401f", + "0x96e2fa4da378aa782cc2d5e6e465fc9e49b5c805ed01d560e9b98abb5c0de8b74a2e7bec3aa5e2887d25cccb12c66f0c", + "0x97e4ab610d414f9210ed6f35300285eb3ccff5b0b6a95ed33425100d7725e159708ea78704497624ca0a2dcabce3a2f9", + "0x960a375b17bdb325761e01e88a3ea57026b2393e1d887b34b8fa5d2532928079ce88dc9fd06a728b26d2bb41b12b9032", + "0x8328a1647398e832aadc05bd717487a2b6fcdaa0d4850d2c4da230c6a2ed44c3e78ec4837b6094f3813f1ee99414713f", + "0xaa283834ebd18e6c99229ce4b401eda83f01d904f250fedd4e24f1006f8fa0712a6a89a7296a9bf2ce8de30e28d1408e", + "0xb29e097f2caadae3e0f0ae3473c072b0cd0206cf6d2e9b22c1a5ad3e07d433e32bd09ed1f4e4276a2da4268633357b7f", + "0x9539c5cbba14538b2fe077ecf67694ef240da5249950baaabea0340718b882a966f66d97f08556b08a4320ceb2cc2629", + "0xb4529f25e9b42ae8cf8338d2eface6ba5cd4b4d8da73af502d081388135c654c0b3afb3aa779ffc80b8c4c8f4425dd2b", + "0x95be0739c4330619fbe7ee2249c133c91d6c07eab846c18c5d6c85fc21ac5528c5d56dcb0145af68ed0c6a79f68f2ccd", + "0xac0c83ea802227bfc23814a24655c9ff13f729619bcffdb487ccbbf029b8eaee709f8bddb98232ef33cd70e30e45ca47", + "0xb503becb90acc93b1901e939059f93e671900ca52c6f64ae701d11ac891d3a050b505d89324ce267bc43ab8275da6ffe", + "0x98e3811b55b1bacb70aa409100abb1b870f67e6d059475d9f278c751b6e1e2e2d6f2e586c81a9fb6597fda06e7923274", + "0xb0b0f61a44053fa6c715dbb0731e35d48dba257d134f851ee1b81fd49a5c51a90ebf5459ec6e489fce25da4f184fbdb1", + "0xb1d2117fe811720bb997c7c93fe9e4260dc50fca8881b245b5e34f724aaf37ed970cdad4e8fcb68e05ac8cf55a274a53", + "0xa10f502051968f14b02895393271776dee7a06db9de14effa0b3471825ba94c3f805302bdddac4d397d08456f620999d", + "0xa3dbad2ef060ae0bb7b02eaa4a13594f3f900450faa1854fc09620b01ac94ab896321dfb1157cf2374c27e5718e8026a", + "0xb550fdec503195ecb9e079dcdf0cad559d64d3c30818ef369b4907e813e689da316a74ad2422e391b4a8c2a2bef25fc0", + "0xa25ba865e2ac8f28186cea497294c8649a201732ecb4620c4e77b8e887403119910423df061117e5f03fc5ba39042db1", + "0xb3f88174e03fdb443dd6addd01303cf88a4369352520187c739fc5ae6b22fa99629c63c985b4383219dab6acc5f6f532", + "0x97a7503248e31e81b10eb621ba8f5210c537ad11b539c96dfb7cf72b846c7fe81bd7532c5136095652a9618000b7f8d3", + "0xa8bcdc1ce5aa8bfa683a2fc65c1e79de8ff5446695dcb8620f7350c26d2972a23da22889f9e2b1cacb3f688c6a2953dc", + "0x8458c111df2a37f5dd91a9bee6c6f4b79f4f161c93fe78075b24a35f9817da8dde71763218d627917a9f1f0c4709c1ed", + "0xac5f061a0541152b876cbc10640f26f1cc923c9d4ae1b6621e4bb3bf2cec59bbf87363a4eb72fb0e5b6d4e1c269b52d5", + "0xa9a25ca87006e8a9203cbb78a93f50a36694aa4aad468b8d80d3feff9194455ca559fcc63838128a0ab75ad78c07c13a", + "0xa450b85f5dfffa8b34dfd8bc985f921318efacf8857cf7948f93884ba09fb831482ee90a44224b1a41e859e19b74962f", + "0x8ed91e7f92f5c6d7a71708b6132f157ac226ecaf8662af7d7468a4fa25627302efe31e4620ad28719318923e3a59bf82", + "0xab524165fd4c71b1fd395467a14272bd2b568592deafa039d8492e9ef36c6d3f96927c95c72d410a768dc0b6d1fbbc9b", + "0xb662144505aa8432c75ffb8d10318526b6d5777ac7af9ebfad87d9b0866c364f7905a6352743bd8fd79ffd9d5dd4f3e6", + "0xa48f1677550a5cd40663bb3ba8f84caaf8454f332d0ceb1d94dbea52d0412fe69c94997f7749929712fd3995298572f7", + "0x8391cd6e2f6b0c242de1117a612be99776c3dc95cb800b187685ea5bf7e2722275eddb79fd7dfc8be8e389c4524cdf70", + "0x875d3acb9af47833b72900bc0a2448999d638f153c5e97e8a14ec02d0c76f6264353a7e275e1f1a5855daced523d243b", + "0x91f1823657d30b59b2f627880a9a9cb530f5aca28a9fd217fe6f2f5133690dfe7ad5a897872e400512db2e788b3f7628", + "0xad3564332aa56cea84123fc7ca79ea70bb4fef2009fa131cb44e4b15e8613bd11ca1d83b9d9bf456e4b7fee9f2e8b017", + "0x8c530b84001936d5ab366c84c0b105241a26d1fb163669f17c8f2e94776895c2870edf3e1bc8ccd04d5e65531471f695", + "0x932d01fa174fdb0c366f1230cffde2571cc47485f37f23ba5a1825532190cc3b722aeb1f15aed62cf83ccae9403ba713", + "0x88b28c20585aca50d10752e84b901b5c2d58efef5131479fbbe53de7bce2029e1423a494c0298e1497669bd55be97a5d", + "0xb914148ca717721144ebb3d3bf3fcea2cd44c30c5f7051b89d8001502f3856fef30ec167174d5b76265b55d70f8716b5", + "0x81d0173821c6ddd2a068d70766d9103d1ee961c475156e0cbd67d54e668a796310474ef698c7ab55abe6f2cf76c14679", + "0x8f28e8d78e2fe7fa66340c53718e0db4b84823c8cfb159c76eac032a62fb53da0a5d7e24ca656cf9d2a890cb2a216542", + "0x8a26360335c73d1ab51cec3166c3cf23b9ea51e44a0ad631b0b0329ef55aaae555420348a544e18d5760969281759b61", + "0x94f326a32ed287545b0515be9e08149eb0a565025074796d72387cc3a237e87979776410d78339e23ef3172ca43b2544", + "0xa785d2961a2fa5e70bffa137858a92c48fe749fee91b02599a252b0cd50d311991a08efd7fa5e96b78d07e6e66ffe746", + "0x94af9030b5ac792dd1ce517eaadcec1482206848bea4e09e55cc7f40fd64d4c2b3e9197027c5636b70d6122c51d2235d", + "0x9722869f7d1a3992850fe7be405ec93aa17dc4d35e9e257d2e469f46d2c5a59dbd504056c85ab83d541ad8c13e8bcd54", + "0xb13c4088b61a06e2c03ac9813a75ff1f68ffdfee9df6a8f65095179a475e29cc49119cad2ce05862c3b1ac217f3aace9", + "0x8c64d51774753623666b10ca1b0fe63ae42f82ed6aa26b81dc1d48c86937c5772eb1402624c52a154b86031854e1fb9f", + "0xb47e4df18002b7dac3fee945bf9c0503159e1b8aafcce2138818e140753011b6d09ef1b20894e08ba3006b093559061b", + "0x93cb5970076522c5a0483693f6a35ffd4ea2aa7aaf3730c4eccd6af6d1bebfc1122fc4c67d53898ae13eb6db647be7e2", + "0xa68873ef80986795ea5ed1a597d1cd99ed978ec25e0abb57fdcc96e89ef0f50aeb779ff46e3dce21dc83ada3157a8498", + "0x8cab67f50949cc8eee6710e27358aea373aae3c92849f8f0b5531c080a6300cdf2c2094fe6fecfef6148de0d28446919", + "0x993e932bcb616dbaa7ad18a4439e0565211d31071ef1b85a0627db74a05d978c60d507695eaeea5c7bd9868a21d06923", + "0xacdadff26e3132d9478a818ef770e9fa0d2b56c6f5f48bd3bd674436ccce9bdfc34db884a73a30c04c5f5e9764cb2218", + "0xa0d3e64c9c71f84c0eef9d7a9cb4fa184224b969db5514d678e93e00f98b41595588ca802643ea225512a4a272f5f534", + "0x91c9140c9e1ba6e330cb08f6b2ce4809cd0d5a0f0516f70032bf30e912b0ed684d07b413b326ab531ee7e5b4668c799b", + "0x87bc2ee7a0c21ba8334cd098e35cb703f9af57f35e091b8151b9b63c3a5b0f89bd7701dbd44f644ea475901fa6d9ef08", + "0x9325ccbf64bf5d71b303e31ee85d486298f9802c5e55b2c3d75427097bf8f60fa2ab4fcaffa9b60bf922c3e24fbd4b19", + "0x95d0506e898318f3dc8d28d16dfd9f0038b54798838b3c9be2a2ae3c2bf204eb496166353fc042220b0bd4f6673b9285", + "0x811de529416331fe9c416726d45df9434c29dcd7e949045eb15740f47e97dde8f31489242200e19922cac2a8b7c6fd1f", + "0xade632d04a4c8bbab6ca7df370b2213cb9225023e7973f0e29f4f5e52e8aeaabc65171306bbdd12a67b195dfbb96d48f", + "0x88b7f029e079b6ae956042c0ea75d53088c5d0efd750dd018adaeacf46be21bf990897c58578c491f41afd3978d08073", + "0x91f477802de507ffd2be3f4319903119225b277ad24f74eb50f28b66c14d32fae53c7edb8c7590704741af7f7f3e3654", + "0x809838b32bb4f4d0237e98108320d4b079ee16ed80c567e7548bd37e4d7915b1192880f4812ac0e00476d246aec1dbc8", + "0x84183b5fc4a7997a8ae5afedb4d21dce69c480d5966b5cbdafd6dd10d29a9a6377f3b90ce44da0eb8b176ac3af0253bb", + "0x8508abbf6d3739a16b9165caf0f95afb3b3ac1b8c38d6d374cf0c91296e2c1809a99772492b539cda184510bce8a0271", + "0x8722054e59bab2062e6419a6e45fc803af77fde912ef2cd23055ad0484963de65a816a2debe1693d93c18218d2b8e81a", + "0x8e895f80e485a7c4f56827bf53d34b956281cdc74856c21eb3b51f6288c01cc3d08565a11cc6f3e2604775885490e8c5", + "0xafc92714771b7aa6e60f3aee12efd9c2595e9659797452f0c1e99519f67c8bc3ac567119c1ddfe82a3e961ee9defea9a", + "0x818ff0fd9cefd32db87b259e5fa32967201016fc02ef44116cdca3c63ce5e637756f60477a408709928444a8ad69c471", + "0x8251e29af4c61ae806fc5d032347fb332a94d472038149225298389495139ce5678fae739d02dfe53a231598a992e728", + "0xa0ea39574b26643f6f1f48f99f276a8a64b5481989cfb2936f9432a3f8ef5075abfe5c067dc5512143ce8bf933984097", + "0xaf67a73911b372bf04e57e21f289fc6c3dfac366c6a01409b6e76fea4769bdb07a6940e52e8d7d3078f235c6d2f632c6", + "0xb5291484ef336024dd2b9b4cf4d3a6b751133a40656d0a0825bcc6d41c21b1c79cb50b0e8f4693f90c29c8f4358641f9", + "0x8bc0d9754d70f2cb9c63f991902165a87c6535a763d5eece43143b5064ae0bcdce7c7a8f398f2c1c29167b2d5a3e6867", + "0x8d7faff53579ec8f6c92f661c399614cc35276971752ce0623270f88be937c414eddcb0997e14724a783905a026c8883", + "0x9310b5f6e675fdf60796f814dbaa5a6e7e9029a61c395761e330d9348a7efab992e4e115c8be3a43d08e90d21290c892", + "0xb5eb4f3eb646038ad2a020f0a42202532d4932e766da82b2c1002bf9c9c2e5336b54c8c0ffcc0e02d19dde2e6a35b6cc", + "0x91dabfd30a66710f1f37a891136c9be1e23af4abf8cb751f512a40c022a35f8e0a4fb05b17ec36d4208de02d56f0d53a", + "0xb3ded14e82d62ac7a5a036122a62f00ff8308498f3feae57d861babaff5a6628d43f0a0c5fc903f10936bcf4e2758ceb", + "0xa88e8348fed2b26acca6784d19ef27c75963450d99651d11a950ea81d4b93acd2c43e0ecce100eaf7e78508263d5baf3", + "0xb1f5bbf7c4756877b87bb42163ac570e08c6667c4528bf68b5976680e19beeff7c5effd17009b0718797077e2955457a", + "0xad2e7b516243f915d4d1415326e98b1a7390ae88897d0b03b66c2d9bd8c3fba283d7e8fe44ed3333296a736454cef6d8", + "0x8f82eae096d5b11f995de6724a9af895f5e1c58d593845ad16ce8fcae8507e0d8e2b2348a0f50a1f66a17fd6fac51a5c", + "0x890e4404d0657c6c1ee14e1aac132ecf7a568bb3e04137b85ac0f84f1d333bd94993e8750f88eee033a33fb00f85dcc7", + "0x82ac7d3385e035115f1d39a99fc73e5919de44f5e6424579776d118d711c8120b8e5916372c6f27bed4cc64cac170b6c", + "0x85ee16d8901c272cfbbe966e724b7a891c1bd5e68efd5d863043ad8520fc409080af61fd726adc680b3f1186fe0ac8b8", + "0x86dc564c9b545567483b43a38f24c41c6551a49cabeebb58ce86404662a12dbfafd0778d30d26e1c93ce222e547e3898", + "0xa29f5b4522db26d88f5f95f18d459f8feefab02e380c2edb65aa0617a82a3c1a89474727a951cef5f15050bcf7b380fb", + "0xa1ce039c8f6cac53352899edb0e3a72c76da143564ad1a44858bd7ee88552e2fe6858d1593bbd74aeee5a6f8034b9b9d", + "0x97f10d77983f088286bd7ef3e7fdd8fa275a56bec19919adf33cf939a90c8f2967d2b1b6fc51195cb45ad561202a3ed7", + "0xa25e2772e8c911aaf8712bdac1dd40ee061c84d3d224c466cfaae8e5c99604053f940cde259bd1c3b8b69595781dbfec", + "0xb31bb95a0388595149409c48781174c340960d59032ab2b47689911d03c68f77a2273576fbe0c2bf4553e330656058c7", + "0xb8b2e9287ad803fb185a13f0d7456b397d4e3c8ad5078f57f49e8beb2e85f661356a3392dbd7bcf6a900baa5582b86a1", + "0xa3d0893923455eb6e96cc414341cac33d2dbc88fba821ac672708cce131761d85a0e08286663a32828244febfcae6451", + "0x82310cb42f647d99a136014a9f881eb0b9791efd2e01fc1841907ad3fc8a9654d3d1dab6689c3607214b4dc2aca01cee", + "0x874022d99c16f60c22de1b094532a0bc6d4de700ad01a31798fac1d5088b9a42ad02bef8a7339af7ed9c0d4f16b186ee", + "0x94981369e120265aed40910eebc37eded481e90f4596b8d57c3bec790ab7f929784bd33ddd05b7870aad6c02e869603b", + "0xa4f1f50e1e2a73f07095e0dd31cb45154f24968dae967e38962341c1241bcd473102fff1ff668b20c6547e9732d11701", + "0xae2328f3b0ad79fcda807e69a1b5278145225083f150f67511dafc97e079f860c3392675f1752ae7e864c056e592205b", + "0x875d8c971e593ca79552c43d55c8c73b17cd20c81ff2c2fed1eb19b1b91e4a3a83d32df150dbfd5db1092d0aebde1e1f", + "0xadd2e80aa46aae95da73a11f130f4bda339db028e24c9b11e5316e75ba5e63bc991d2a1da172c7c8e8fee038baae3433", + "0xb46dbe1cb3424002aa7de51e82f600852248e251465c440695d52538d3f36828ff46c90ed77fc1d11534fe3c487df8ef", + "0xa5e5045d28b4e83d0055863c30c056628c58d4657e6176fd0536f5933f723d60e851bb726d5bf3c546b8ce4ac4a57ef8", + "0x91fec01e86dd1537e498fff7536ea3ca012058b145f29d9ada49370cd7b7193ac380e116989515df1b94b74a55c45df3", + "0xa7428176d6918cd916a310bdc75483c72de660df48cac4e6e7478eef03205f1827ea55afc0df5d5fa7567d14bbea7fc9", + "0x851d89bef45d9761fe5fdb62972209335193610015e16a675149519f9911373bac0919add226ef118d9f3669cfdf4734", + "0xb74acf5c149d0042021cb2422ea022be4c4f72a77855f42393e71ffd12ebb3eec16bdf16f812159b67b79a9706e7156d", + "0x99f35dce64ec99aa595e7894b55ce7b5a435851b396e79036ffb249c28206087db4c85379df666c4d95857db02e21ff9", + "0xb6b9a384f70db9e298415b8ab394ee625dafff04be2886476e59df8d052ca832d11ac68a9b93fba7ab055b7bc36948a4", + "0x898ee4aefa923ffec9e79f2219c7389663eb11eb5b49014e04ed4a336399f6ea1691051d86991f4c46ca65bcd4fdf359", + "0xb0f948217b0d65df7599a0ba4654a5e43c84db477936276e6f11c8981efc6eaf14c90d3650107ed4c09af4cc8ec11137", + "0xaa6286e27ac54f73e63dbf6f41865dd94d24bc0cf732262fcaff67319d162bb43af909f6f8ee27b1971939cfbba08141", + "0x8bca7cdf730cf56c7b2c8a2c4879d61361a6e1dba5a3681a1a16c17a56e168ace0e99cf0d15826a1f5e67e6b8a8a049a", + "0xa746d876e8b1ce225fcafca603b099b36504846961526589af977a88c60d31ba2cc56e66a3dec8a77b3f3531bf7524c9", + "0xa11e2e1927e6704cdb8874c75e4f1842cef84d7d43d7a38e339e61dc8ba90e61bbb20dd3c12e0b11d2471d58eed245be", + "0xa36395e22bc1d1ba8b0459a235203177737397da5643ce54ded3459d0869ff6d8d89f50c73cb62394bf66a959cde9b90", + "0x8b49f12ba2fdf9aca7e5f81d45c07d47f9302a2655610e7634d1e4bd16048381a45ef2c95a8dd5b0715e4b7cf42273af", + "0x91cffa2a17e64eb7f76bccbe4e87280ee1dd244e04a3c9eac12e15d2d04845d876eb24fe2ec6d6d266cce9efb281077f", + "0xa6b8afabf65f2dee01788114e33a2f3ce25376fb47a50b74da7c3c25ff1fdc8aa9f41307534abbf48acb6f7466068f69", + "0x8d13db896ccfea403bd6441191995c1a65365cab7d0b97fbe9526da3f45a877bd1f4ef2edef160e8a56838cd1586330e", + "0x98c717de9e01bef8842c162a5e757fe8552d53269c84862f4d451e7c656ae6f2ae473767b04290b134773f63be6fdb9d", + "0x8c2036ace1920bd13cf018e82848c49eb511fad65fd0ff51f4e4b50cf3bfc294afb63cba682c16f52fb595a98fa84970", + "0xa3520fdff05dbad9e12551b0896922e375f9e5589368bcb2cc303bde252743b74460cb5caf99629325d3620f13adc796", + "0x8d4f83a5bfec05caf5910e0ce538ee9816ee18d0bd44c1d0da2a87715a23cd2733ad4d47552c6dc0eb397687d611dd19", + "0xa7b39a0a6a02823452d376533f39d35029867b3c9a6ad6bca181f18c54132d675613a700f9db2440fb1b4fa13c8bf18a", + "0x80bcb114b2544b80f404a200fc36860ed5e1ad31fe551acd4661d09730c452831751baa9b19d7d311600d267086a70bc", + "0x90dcce03c6f88fc2b08f2b42771eedde90cc5330fe0336e46c1a7d1b5a6c1641e5fcc4e7b3d5db00bd8afca9ec66ed81", + "0xaec15f40805065c98e2965b1ae12a6c9020cfdb094c2d0549acfc7ea2401a5fb48d3ea7d41133cf37c4e096e7ff53eb9", + "0x80e129b735dba49fa627a615d6c273119acec8e219b2f2c4373a332b5f98d66cbbdd688dfbe72a8f8bfefaccc02c50c1", + "0xa9b596da3bdfe23e6799ece5f7975bf7a1979a75f4f546deeaf8b34dfe3e0d623217cb4cf4ccd504cfa3625b88cd53f1", + "0xabcbbb70b16f6e517c0ab4363ab76b46e4ff58576b5f8340e5c0e8cc0e02621b6e23d742d73b015822a238b17cfd7665", + "0xa046937cc6ea6a2e1adae543353a9fe929c1ae4ad655be1cc051378482cf88b041e28b1e9a577e6ccff2d3570f55e200", + "0x831279437282f315e65a60184ef158f0a3dddc15a648dc552bdc88b3e6fe8288d3cfe9f0031846d81350f5e7874b4b33", + "0x993d7916fa213c6d66e7c4cafafc1eaec9a2a86981f91c31eb8a69c5df076c789cbf498a24c84e0ee77af95b42145026", + "0x823907a3b6719f8d49b3a4b7c181bd9bb29fcf842d7c70660c4f351852a1e197ca46cf5e879b47fa55f616fa2b87ce5e", + "0x8d228244e26132b234930ee14c75d88df0943cdb9c276a8faf167d259b7efc1beec2a87c112a6c608ad1600a239e9aae", + "0xab6e55766e5bfb0cf0764ed909a8473ab5047d3388b4f46faeba2d1425c4754c55c6daf6ad4751e634c618b53e549529", + "0xab0cab6860e55a84c5ad2948a7e0989e2b4b1fd637605634b118361497332df32d9549cb854b2327ca54f2bcb85eed8f", + "0xb086b349ae03ef34f4b25a57bcaa5d1b29bd94f9ebf87e22be475adfe475c51a1230c1ebe13506cb72c4186192451658", + "0x8a0b49d8a254ca6d91500f449cbbfbb69bb516c6948ac06808c65595e46773e346f97a5ce0ef7e5a5e0de278af22709c", + "0xac49de11edaaf04302c73c578cc0824bdd165c0d6321be1c421c1950e68e4f3589aa3995448c9699e93c6ebae8803e27", + "0x884f02d841cb5d8f4c60d1402469216b114ab4e93550b5bc1431756e365c4f870a9853449285384a6fa49e12ce6dc654", + "0xb75f3a28fa2cc8d36b49130cb7448a23d73a7311d0185ba803ad55c8219741d451c110f48b786e96c728bc525903a54f", + "0x80ae04dbd41f4a35e33f9de413b6ad518af0919e5a30cb0fa1b061b260420780bb674f828d37fd3b52b5a31673cbd803", + "0xb9a8011eb5fcea766907029bf743b45262db3e49d24f84503687e838651ed11cb64c66281e20a0ae9f6aa51acc552263", + "0x90bfdd75e2dc9cf013e22a5d55d2d2b8a754c96103a17524488e01206e67f8b6d52b1be8c4e3d5307d4fe06d0e51f54c", + "0xb4af353a19b06203a815ec43e79a88578cc678c46f5a954b85bc5c53b84059dddba731f3d463c23bfd5273885c7c56a4", + "0xaa125e96d4553b64f7140e5453ff5d2330318b69d74d37d283e84c26ad672fa00e3f71e530eb7e28be1e94afb9c4612e", + "0xa18e060aee3d49cde2389b10888696436bb7949a79ca7d728be6456a356ea5541b55492b2138da90108bd1ce0e6f5524", + "0x93e55f92bdbccc2de655d14b1526836ea2e52dba65eb3f87823dd458a4cb5079bf22ce6ef625cb6d6bfdd0995ab9a874", + "0x89f5a683526b90c1c3ceebbb8dc824b21cff851ce3531b164f6626e326d98b27d3e1d50982e507d84a99b1e04e86a915", + "0x83d1c38800361633a3f742b1cb2bfc528129496e80232611682ddbe403e92c2ac5373aea0bca93ecb5128b0b2b7a719e", + "0x8ecba560ac94905e19ce8d9c7af217bf0a145d8c8bd38e2db82f5e94cc3f2f26f55819176376b51f154b4aab22056059", + "0xa7e2a4a002b60291924850642e703232994acb4cfb90f07c94d1e0ecd2257bb583443283c20fc6017c37e6bfe85b7366", + "0x93ed7316fa50b528f1636fc6507683a672f4f4403e55e94663f91221cc198199595bd02eef43d609f451acc9d9b36a24", + "0xa1220a8ebc5c50ceed76a74bc3b7e0aa77f6884c71b64b67c4310ac29ce5526cb8992d6abc13ef6c8413ce62486a6795", + "0xb2f6eac5c869ad7f4a25161d3347093e2f70e66cd925032747e901189355022fab3038bca4d610d2f68feb7e719c110b", + "0xb703fa11a4d511ca01c7462979a94acb40b5d933759199af42670eb48f83df202fa0c943f6ab3b4e1cc54673ea3aab1e", + "0xb5422912afbfcb901f84791b04f1ddb3c3fbdc76d961ee2a00c5c320e06d3cc5b5909c3bb805df66c5f10c47a292b13d", + "0xad0934368da823302e1ac08e3ede74b05dfdbfffca203e97ffb0282c226814b65c142e6e15ec1e754518f221f01b30f7", + "0xa1dd302a02e37df15bf2f1147efe0e3c06933a5a767d2d030e1132f5c3ce6b98e216b6145eb39e1e2f74e76a83165b8d", + "0xa346aab07564432f802ae44738049a36f7ca4056df2d8f110dbe7fef4a3e047684dea609b2d03dc6bf917c9c2a47608f", + "0xb96c5f682a5f5d02123568e50f5d0d186e4b2c4c9b956ec7aabac1b3e4a766d78d19bd111adb5176b898e916e49be2aa", + "0x8a96676d56876fc85538db2e806e1cba20fd01aeb9fa3cb43ca6ca94a2c102639f65660db330e5d74a029bb72d6a0b39", + "0xab0048336bd5c3def1a4064eadd49e66480c1f2abb4df46e03afbd8a3342c2c9d74ee35d79f08f4768c1646681440984", + "0x888427bdf76caec90814c57ee1c3210a97d107dd88f7256f14f883ad0f392334b82be11e36dd8bfec2b37935177c7831", + "0xb622b282becf0094a1916fa658429a5292ba30fb48a4c8066ce1ddcefb71037948262a01c95bab6929ed3a76ba5db9fe", + "0xb5b9e005c1f456b6a368a3097634fb455723abe95433a186e8278dceb79d4ca2fbe21f8002e80027b3c531e5bf494629", + "0xa3c6707117a1e48697ed41062897f55d8119403eea6c2ee88f60180f6526f45172664bfee96bf61d6ec0b7fbae6aa058", + "0xb02a9567386a4fbbdb772d8a27057b0be210447348efe6feb935ceec81f361ed2c0c211e54787dc617cdffed6b4a6652", + "0xa9b8364e40ef15c3b5902e5534998997b8493064fa2bea99600def58279bb0f64574c09ba11e9f6f669a8354dd79dc85", + "0x9998a2e553a9aa9a206518fae2bc8b90329ee59ab23005b10972712389f2ec0ee746033c733092ffe43d73d33abbb8ef", + "0x843a4b34d9039bf79df96d79f2d15e8d755affb4d83d61872daf540b68c0a3888cf8fc00d5b8b247b38524bcb3b5a856", + "0x84f7128920c1b0bb40eee95701d30e6fc3a83b7bb3709f16d97e72acbb6057004ee7ac8e8f575936ca9dcb7866ab45f7", + "0x918d3e2222e10e05edb34728162a899ad5ada0aaa491aeb7c81572a9c0d506e31d5390e1803a91ff3bd8e2bb15d47f31", + "0x9442d18e2489613a7d47bb1cb803c8d6f3259d088cd079460976d87f7905ee07dea8f371b2537f6e1d792d36d7e42723", + "0xb491976970fe091995b2ed86d629126523ccf3e9daf8145302faca71b5a71a5da92e0e05b62d7139d3efac5c4e367584", + "0xaa628006235dc77c14cef4c04a308d66b07ac92d377df3de1a2e6ecfe3144f2219ad6d7795e671e1cb37a3641910b940", + "0x99d386adaea5d4981d7306feecac9a555b74ffdc218c907c5aa7ac04abaead0ec2a8237300d42a3fbc464673e417ceed", + "0x8f78e8b1556f9d739648ea3cab9606f8328b52877fe72f9305545a73b74d49884044ba9c1f1c6db7d9b7c7b7c661caba", + "0x8fb357ae49932d0babdf74fc7aa7464a65d3b6a2b3acf4f550b99601d3c0215900cfd67f2b6651ef94cfc323bac79fae", + "0x9906f2fa25c0290775aa001fb6198113d53804262454ae8b83ef371b5271bde189c0460a645829cb6c59f9ee3a55ce4d", + "0x8f4379b3ebb50e052325b27655ca6a82e6f00b87bf0d2b680d205dd2c7afdc9ff32a9047ae71a1cdf0d0ce6b9474d878", + "0xa85534e88c2bd43c043792eaa75e50914b21741a566635e0e107ae857aed0412035f7576cf04488ade16fd3f35fdbb87", + "0xb4ce93199966d3c23251ca7f28ec5af7efea1763d376b0385352ffb2e0a462ef95c69940950278cf0e3dafd638b7bd36", + "0xb10cb3d0317dd570aa73129f4acf63c256816f007607c19b423fb42f65133ce21f2f517e0afb41a5378cccf893ae14d0", + "0xa9b231c9f739f7f914e5d943ed9bff7eba9e2c333fbd7c34eb1648a362ee01a01af6e2f7c35c9fe962b11152cddf35de", + "0x99ff6a899e156732937fb81c0cced80ae13d2d44c40ba99ac183aa246103b31ec084594b1b7feb96da58f4be2dd5c0ed", + "0x8748d15d18b75ff2596f50d6a9c4ce82f61ecbcee123a6ceae0e43cab3012a29b6f83cf67b48c22f6f9d757c6caf76b2", + "0xb88ab05e4248b7fb634cf640a4e6a945d13e331237410f7217d3d17e3e384ddd48897e7a91e4516f1b9cbd30f35f238b", + "0x8d826deaeeb84a3b2d2c04c2300ca592501f992810582d6ae993e0d52f6283a839dba66c6c72278cff5871802b71173b", + "0xb36fed027c2f05a5ef625ca00b0364b930901e9e4420975b111858d0941f60e205546474bb25d6bfa6928d37305ae95f", + "0xaf2fcfc6b87967567e8b8a13a4ed914478185705724e56ce68fb2df6d1576a0cf34a61e880997a0d35dc2c3276ff7501", + "0xac351b919cd1fbf106feb8af2c67692bfcddc84762d18cea681cfa7470a5644839caace27efee5f38c87d3df306f4211", + "0x8d6665fb1d4d8d1fa23bd9b8a86e043b8555663519caac214d1e3e3effbc6bee7f2bcf21e645f77de0ced279d69a8a8b", + "0xa9fc1c2061756b2a1a169c1b149f212ff7f0d2488acd1c5a0197eba793cffa593fc6d1d1b40718aa75ca3ec77eff10e1", + "0xaff64f0fa009c7a6cf0b8d7a22ddb2c8170c3cb3eec082e60d5aadb00b0040443be8936d728d99581e33c22178c41c87", + "0x82e0b181adc5e3b1c87ff8598447260e839d53debfae941ebea38265575546c3a74a14b4325a030833a62ff6c52d9365", + "0xb7ad43cbb22f6f892c2a1548a41dc120ab1f4e1b8dea0cb6272dd9cb02054c542ecabc582f7e16de709d48f5166cae86", + "0x985e0c61094281532c4afb788ecb2dfcba998e974b5d4257a22040a161883908cdd068fe80f8eb49b8953cfd11acf43a", + "0xae46895c6d67ea6d469b6c9c07b9e5d295d9ae73b22e30da4ba2c973ba83a130d7eef39717ec9d0f36e81d56bf742671", + "0x8600177ea1f7e7ef90514b38b219a37dedfc39cb83297e4c7a5b479817ef56479d48cf6314820960c751183f6edf8b0e", + "0xb9208ec1c1d7a1e99b59c62d3e4e61dfb706b0e940d09d3abfc3454c19749083260614d89cfd7e822596c3cdbcc6bb95", + "0xa1e94042c796c2b48bc724352d2e9f3a22291d9a34705993357ddb6adabd76da6fc25dac200a8cb0b5bbd99ecddb7af6", + "0xb29c3adedd0bcad8a930625bc4dfdc3552a9afd5ca6dd9c0d758f978068c7982b50b711aa0eb5b97f2b84ee784637835", + "0xaf0632a238bb1f413c7ea8e9b4c3d68f2827bd2e38cd56024391fba6446ac5d19a780d0cfd4a78fe497d537b766a591a", + "0xaaf6e7f7d54f8ef5e2e45dd59774ecbeecf8683aa70483b2a75be6a6071b5981bbaf1627512a65d212817acdfab2e428", + "0x8c751496065da2e927cf492aa5ca9013b24f861d5e6c24b30bbf52ec5aaf1905f40f9a28175faef283dd4ed4f2182a09", + "0x8952377d8e80a85cf67d6b45499f3bad5fd452ea7bcd99efc1b066c4720d8e5bff1214cea90fd1f972a7f0baac3d29be", + "0xa1946ee543d1a6e21f380453be4d446e4130950c5fc3d075794eb8260f6f52d0a795c1ff91d028a648dc1ce7d9ab6b47", + "0x89f3fefe37af31e0c17533d2ca1ce0884cc1dc97c15cbfab9c331b8debd94781c9396abef4bb2f163d09277a08d6adf0", + "0xa2753f1e6e1a154fb117100a5bd9052137add85961f8158830ac20541ab12227d83887d10acf7fd36dcaf7c2596d8d23", + "0x814955b4198933ee11c3883863b06ff98c7eceb21fc3e09df5f916107827ccf3323141983e74b025f46ae00284c9513b", + "0x8cc5c6bb429073bfef47cae7b3bfccb0ffa076514d91a1862c6bda4d581e0df87db53cc6c130bf8a7826304960f5a34e", + "0x909f22c1f1cdc87f7be7439c831a73484a49acbf8f23d47087d7cf867c64ef61da3bde85dc57d705682b4c3fc710d36e", + "0x8048fee7f276fcd504aed91284f28e73693615e0eb3858fa44bcf79d7285a9001c373b3ef71d9a3054817ba293ebe28c", + "0x94400e5cf5d2700ca608c5fe35ce14623f71cc24959f2bc27ca3684092850f76b67fb1f07ca9e5b2ca3062cf8ad17bd4", + "0x81c2ae7d4d1b17f8b6de6a0430acc0d58260993980fe48dc2129c4948269cdc74f9dbfbf9c26b19360823fd913083d48", + "0x8c41fe765128e63f6889d6a979f6a4342300327c8b245a8cfe3ecfbcac1e09c3da30e2a1045b24b78efc6d6d50c8c6ac", + "0xa5dd4ae51ae48c8be4b218c312ade226cffce671cf121cb77810f6c0990768d6dd767badecb5c69921d5574d5e8433d3", + "0xb7642e325f4ba97ae2a39c1c9d97b35aafd49d53dba36aed3f3cb0ca816480b3394079f46a48252d46596559c90f4d58", + "0xae87375b40f35519e7bd4b1b2f73cd0b329b0c2cb9d616629342a71c6c304338445eda069b78ea0fbe44087f3de91e09", + "0xb08918cb6f736855e11d3daca1ddfbdd61c9589b203b5493143227bf48e2c77c2e8c94b0d1aa2fab2226e0eae83f2681", + "0xac36b84a4ac2ebd4d6591923a449c564e3be8a664c46092c09e875c2998eba16b5d32bfd0882fd3851762868e669f0b1", + "0xa44800a3bb192066fa17a3f29029a23697240467053b5aa49b9839fb9b9b8b12bcdcbfc557f024b61f4f51a9aacdefcb", + "0x9064c688fec23441a274cdf2075e5a449caf5c7363cc5e8a5dc9747183d2e00a0c69f2e6b3f6a7057079c46014c93b3b", + "0xaa367b021469af9f5b764a79bb3afbe2d87fe1e51862221672d1a66f954b165778b7c27a705e0f93841fab4c8468344d", + "0xa1a8bfc593d4ab71f91640bc824de5c1380ab2591cfdafcbc78a14b32de3c0e15f9d1b461d85c504baa3d4232c16bb53", + "0x97df48da1799430f528184d30b6baa90c2a2f88f34cdfb342d715339c5ebd6d019aa693cea7c4993daafc9849063a3aa", + "0xabd923831fbb427e06e0dd335253178a9e5791395c84d0ab1433c07c53c1209161097e9582fb8736f8a60bde62d8693e", + "0x84cd1a43f1a438b43dc60ffc775f646937c4f6871438163905a3cebf1115f814ccd38a6ccb134130bff226306e412f32", + "0x91426065996b0743c5f689eb3ca68a9f7b9e4d01f6c5a2652b57fa9a03d8dc7cd4bdbdab0ca5a891fee1e97a7f00cf02", + "0xa4bee50249db3df7fd75162b28f04e57c678ba142ce4d3def2bc17bcb29e4670284a45f218dad3969af466c62a903757", + "0x83141ebcc94d4681404e8b67a12a46374fded6df92b506aff3490d875919631408b369823a08b271d006d5b93136f317", + "0xa0ea1c8883d58d5a784da3d8c8a880061adea796d7505c1f903d07c287c5467f71e4563fc0faafbc15b5a5538b0a7559", + "0x89d9d480574f201a87269d26fb114278ed2c446328df431dc3556e3500e80e4cd01fcac196a2459d8646361ebda840df", + "0x8bf302978973632dd464bec819bdb91304712a3ec859be071e662040620422c6e75eba6f864f764cffa2799272efec39", + "0x922f666bc0fd58b6d7d815c0ae4f66d193d32fc8382c631037f59eeaeae9a8ca6c72d08e72944cf9e800b8d639094e77", + "0x81ad8714f491cdff7fe4399f2eb20e32650cff2999dd45b9b3d996d54a4aba24cc6c451212e78c9e5550368a1a38fb3f", + "0xb58fcf4659d73edb73175bd9139d18254e94c3e32031b5d4b026f2ed37aa19dca17ec2eb54c14340231615277a9d347e", + "0xb365ac9c2bfe409b710928c646ea2fb15b28557e0f089d39878e365589b9d1c34baf5566d20bb28b33bb60fa133f6eff", + "0x8fcae1d75b53ab470be805f39630d204853ca1629a14158bac2f52632277d77458dec204ff84b7b2d77e641c2045be65", + "0xa03efa6bebe84f4f958a56e2d76b5ba4f95dd9ed7eb479edc7cc5e646c8d4792e5b0dfc66cc86aa4b4afe2f7a4850760", + "0xaf1c823930a3638975fb0cc5c59651771b2719119c3cd08404fbd4ce77a74d708cefbe3c56ea08c48f5f10e6907f338f", + "0x8260c8299b17898032c761c325ac9cabb4c5b7e735de81eacf244f647a45fb385012f4f8df743128888c29aefcaaad16", + "0xab2f37a573c82e96a8d46198691cd694dfa860615625f477e41f91b879bc58a745784fccd8ffa13065834ffd150d881d", + "0x986c746c9b4249352d8e5c629e8d7d05e716b3c7aab5e529ca969dd1e984a14b5be41528baef4c85d2369a42d7209216", + "0xb25e32da1a8adddf2a6080725818b75bc67240728ad1853d90738485d8924ea1e202df0a3034a60ffae6f965ec55cf63", + "0xa266e627afcebcefea6b6b44cbc50f5c508f7187e87d047b0450871c2a030042c9e376f3ede0afcf9d1952f089582f71", + "0x86c3bbca4c0300606071c0a80dbdec21ce1dd4d8d4309648151c420854032dff1241a1677d1cd5de4e4de4385efda986", + "0xb9a21a1fe2d1f3273a8e4a9185abf2ff86448cc98bfa435e3d68306a2b8b4a6a3ea33a155be3cb62a2170a86f77679a5", + "0xb117b1ea381adce87d8b342cba3a15d492ff2d644afa28f22424cb9cbc820d4f7693dfc1a4d1b3697046c300e1c9b4c8", + "0x9004c425a2e68870d6c69b658c344e3aa3a86a8914ee08d72b2f95c2e2d8a4c7bb0c6e7e271460c0e637cec11117bf8e", + "0x86a18aa4783b9ebd9131580c8b17994825f27f4ac427b0929a1e0236907732a1c8139e98112c605488ee95f48bbefbfc", + "0x84042243b955286482ab6f0b5df4c2d73571ada00716d2f737ca05a0d2e88c6349e8ee9e67934cfee4a1775dbf7f4800", + "0x92c2153a4733a62e4e1d5b60369f3c26777c7d01cd3c8679212660d572bd3bac9b8a8a64e1f10f7dbf5eaa7579c4e423", + "0x918454b6bb8e44a2afa144695ba8d48ae08d0cdfef4ad078f67709eddf3bb31191e8b006f04e82ea45a54715ef4d5817", + "0xacf0b54f6bf34cf6ed6c2b39cf43194a40d68de6bcf1e4b82c34c15a1343e9ac3737885e1a30b78d01fa3a5125463db8", + "0xa7d60dbe4b6a7b054f7afe9ee5cbbfeca0d05dc619e6041fa2296b549322529faddb8a11e949562309aecefb842ac380", + "0x91ffb53e6d7e5f11159eaf13e783d6dbdfdb1698ed1e6dbf3413c6ea23492bbb9e0932230a9e2caac8fe899a17682795", + "0xb6e8d7be5076ee3565d5765a710c5ecf17921dd3cf555c375d01e958a365ae087d4a88da492a5fb81838b7b92bf01143", + "0xa8c6b763de2d4b2ed42102ef64eccfef31e2fb2a8a2776241c82912fa50fc9f77f175b6d109a97ede331307c016a4b1a", + "0x99839f86cb700c297c58bc33e28d46b92931961548deac29ba8df91d3e11721b10ea956c8e16984f9e4acf1298a79b37", + "0x8c2e2c338f25ea5c25756b7131cde0d9a2b35abf5d90781180a00fe4b8e64e62590dc63fe10a57fba3a31c76d784eb01", + "0x9687d7df2f41319ca5469d91978fed0565a5f11f829ebadaa83db92b221755f76c6eacd7700735e75c91e257087512e3", + "0x8795fdfb7ff8439c58b9bf58ed53873d2780d3939b902b9ddaaa4c99447224ced9206c3039a23c2c44bcc461e2bb637f", + "0xa803697b744d2d087f4e2307218d48fa88620cf25529db9ce71e2e3bbcc65bac5e8bb9be04777ef7bfb5ed1a5b8e6170", + "0x80f3d3efbbb9346ddd413f0a8e36b269eb5d7ff6809d5525ff9a47c4bcab2c01b70018b117f6fe05253775612ff70c6b", + "0x9050e0e45bcc83930d4c505af35e5e4d7ca01cd8681cba92eb55821aececcebe32bb692ebe1a4daac4e7472975671067", + "0x8d206812aac42742dbaf233e0c080b3d1b30943b54b60283515da005de05ea5caa90f91fedcfcba72e922f64d7040189", + "0xa2d44faaeb2eff7915c83f32b13ca6f31a6847b1c1ce114ea240bac3595eded89f09b2313b7915ad882292e2b586d5b4", + "0x961776c8576030c39f214ea6e0a3e8b3d32f023d2600958c098c95c8a4e374deeb2b9dc522adfbd6bda5949bdc09e2a2", + "0x993fa7d8447407af0fbcd9e6d77f815fa5233ab00674efbcf74a1f51c37481445ae291cc7b76db7c178f9cb0e570e0fc", + "0xabd5b1c78e05f9d7c8cc99bdaef8b0b6a57f2daf0f02bf492bec48ea4a27a8f1e38b5854da96efff11973326ff980f92", + "0x8f15af4764bc275e6ccb892b3a4362cacb4e175b1526a9a99944e692fe6ccb1b4fc19abf312bb2a089cb1f344d91a779", + "0xa09b27ccd71855512aba1d0c30a79ffbe7f6707a55978f3ced50e674b511a79a446dbc6d7946add421ce111135a460af", + "0x94b2f98ce86a9271fbd4153e1fc37de48421fe3490fb3840c00f2d5a4d0ba8810c6a32880b002f6374b59e0a7952518b", + "0x8650ac644f93bbcb88a6a0f49fee2663297fd4bc6fd47b6a89b9d8038d32370438ab3a4775ec9b58cb10aea8a95ef7b6", + "0x95e5c2f2e84eed88c6980bbba5a1c0bb375d5a628bff006f7516d45bb7d723da676add4fdd45956f312e7bab0f052644", + "0xb3278a3fa377ac93af7cfc9453f8cb594aae04269bbc99d2e0e45472ff4b6a2f97a26c4c57bf675b9d86f5e77a5d55d1", + "0xb4bcbe6eb666a206e2ea2f877912c1d3b5bdbd08a989fc4490eb06013e1a69ad1ba08bcdac048bf29192312be399077b", + "0xa76d70b78c99fffcbf9bb9886eab40f1ea4f99a309710b660b64cbf86057cbcb644d243f6e341711bb7ef0fedf0435a7", + "0xb2093c1ee945dca7ac76ad5aed08eae23af31dd5a77c903fd7b6f051f4ab84425d33a03c3d45bf2907bc93c02d1f3ad8", + "0x904b1f7534e053a265b22d20be859912b9c9ccb303af9a8d6f1d8f6ccdc5c53eb4a45a1762b880d8444d9be0cd55e7f9", + "0x8f664a965d65bc730c9ef1ec7467be984d4b8eb46bd9b0d64e38e48f94e6e55dda19aeac82cbcf4e1473440e64c4ca18", + "0x8bcee65c4cc7a7799353d07b114c718a2aae0cd10a3f22b7eead5185d159dafd64852cb63924bf87627d176228878bce", + "0x8c78f2e3675096fef7ebaa898d2615cd50d39ca3d8f02b9bdfb07e67da648ae4be3da64838dffc5935fd72962c4b96c7", + "0x8c40afd3701629421fec1df1aac4e849384ef2e80472c0e28d36cb1327acdf2826f99b357f3d7afdbc58a6347fc40b3c", + "0xa197813b1c65a8ea5754ef782522a57d63433ef752215ecda1e7da76b0412ee619f58d904abd2e07e0c097048b6ae1dd", + "0xa670542629e4333884ad7410f9ea3bd6f988df4a8f8a424ca74b9add2312586900cf9ae8bd50411f9146e82626b4af56", + "0xa19875cc07ab84e569d98b8b67fb1dbbdfb59093c7b748fae008c8904a6fd931a63ca8d03ab5fea9bc8d263568125a9b", + "0xb57e7f68e4eb1bd04aafa917b1db1bdab759a02aa8a9cdb1cba34ba8852b5890f655645c9b4e15d5f19bf37e9f2ffe9f", + "0x8abe4e2a4f6462b6c64b3f10e45db2a53c2b0d3c5d5443d3f00a453e193df771eda635b098b6c8604ace3557514027af", + "0x8459e4fb378189b22b870a6ef20183deb816cefbf66eca1dc7e86d36a2e011537db893729f500dc154f14ce24633ba47", + "0x930851df4bc7913c0d8c0f7bd3b071a83668987ed7c397d3d042fdc0d9765945a39a3bae83da9c88cb6b686ed8aeeb26", + "0x8078c9e5cd05e1a8c932f8a1d835f61a248b6e7133fcbb3de406bf4ffc0e584f6f9f95062740ba6008d98348886cf76b", + "0xaddff62bb29430983fe578e3709b0949cdc0d47a13a29bc3f50371a2cb5c822ce53e2448cfaa01bcb6e0aa850d5a380e", + "0x9433add687b5a1e12066721789b1db2edf9b6558c3bdc0f452ba33b1da67426abe326e9a34d207bfb1c491c18811bde1", + "0x822beda3389963428cccc4a2918fa9a8a51cf0919640350293af70821967108cded5997adae86b33cb917780b097f1ca", + "0xa7a9f52bda45e4148ed56dd176df7bd672e9b5ed18888ccdb405f47920fdb0844355f8565cefb17010b38324edd8315f", + "0xb35c3a872e18e607b2555c51f9696a17fa18da1f924d503b163b4ec9fe22ed0c110925275cb6c93ce2d013e88f173d6a", + "0xadf34b002b2b26ab84fc1bf94e05bd8616a1d06664799ab149363c56a6e0c807fdc473327d25632416e952ea327fcd95", + "0xae4a6b9d22a4a3183fac29e2551e1124a8ce4a561a9a2afa9b23032b58d444e6155bb2b48f85c7b6d70393274e230db7", + "0xa2ea3be4fc17e9b7ce3110284038d46a09e88a247b6971167a7878d9dcf36925d613c382b400cfa4f37a3ebea3699897", + "0x8e5863786b641ce3140fbfe37124d7ad3925472e924f814ebfc45959aaf3f61dc554a597610b5defaecc85b59a99b50f", + "0xaefde3193d0f700d0f515ab2aaa43e2ef1d7831c4f7859f48e52693d57f97fa9e520090f3ed700e1c966f4b76048e57f", + "0x841a50f772956622798e5cd208dc7534d4e39eddee30d8ce133383d66e5f267e389254a0cdae01b770ecd0a9ca421929", + "0x8fbc2bfd28238c7d47d4c03b1b910946c0d94274a199575e5b23242619b1de3497784e646a92aa03e3e24123ae4fcaba", + "0x926999579c8eec1cc47d7330112586bdca20b4149c8b2d066f527c8b9f609e61ce27feb69db67eea382649c6905efcf9", + "0xb09f31f305efcc65589adf5d3690a76cf339efd67cd43a4e3ced7b839507466e4be72dd91f04e89e4bbef629d46e68c0", + "0xb917361f6b95f759642638e0b1d2b3a29c3bdef0b94faa30de562e6078c7e2d25976159df3edbacbf43614635c2640b4", + "0x8e7e8a1253bbda0e134d62bfe003a2669d471b47bd2b5cde0ff60d385d8e62279d54022f5ac12053b1e2d3aaa6910b4c", + "0xb69671a3c64e0a99d90b0ed108ce1912ff8ed983e4bddd75a370e9babde25ee1f5efb59ec707edddd46793207a8b1fe7", + "0x910b2f4ebd37b7ae94108922b233d0920b4aba0bd94202c70f1314418b548d11d8e9caa91f2cd95aff51b9432d122b7f", + "0x82f645c90dfb52d195c1020346287c43a80233d3538954548604d09fbab7421241cde8593dbc4acc4986e0ea39a27dd9", + "0x8fee895f0a140d88104ce442fed3966f58ff9d275e7373483f6b4249d64a25fb5374bbdc6bce6b5ab0270c2847066f83", + "0x84f5bd7aab27b2509397aeb86510dd5ac0a53f2c8f73799bf720f2f87a52277f8d6b0f77f17bc80739c6a7119b7eb062", + "0x9903ceced81099d7e146e661bcf01cbaccab5ba54366b85e2177f07e2d8621e19d9c9c3eee14b9266de6b3f9b6ea75ae", + "0xb9c16ea2a07afa32dd6c7c06df0dec39bca2067a9339e45475c98917f47e2320f6f235da353fd5e15b477de97ddc68dd", + "0x9820a9bbf8b826bec61ebf886de2c4f404c1ebdc8bab82ee1fea816d9de29127ce1852448ff717a3fe8bbfe9e92012e5", + "0x817224d9359f5da6f2158c2c7bf9165501424f063e67ba9859a07ab72ee2ee62eb00ca6da821cfa19065c3282ca72c74", + "0x94b95c465e6cb00da400558a3c60cfec4b79b27e602ca67cbc91aead08de4b6872d8ea096b0dc06dca4525c8992b8547", + "0xa2b539a5bccd43fa347ba9c15f249b417997c6a38c63517ca38394976baa08e20be384a360969ff54e7e721db536b3e5", + "0x96caf707e34f62811ee8d32ccf28d8d6ec579bc33e424d0473529af5315c456fd026aa910c1fed70c91982d51df7d3ca", + "0x8a77b73e890b644c6a142bdbac59b22d6a676f3b63ddafb52d914bb9d395b8bf5aedcbcc90429337df431ebd758a07a6", + "0x8857830a7351025617a08bc44caec28d2fae07ebf5ffc9f01d979ce2a53839a670e61ae2783e138313929129790a51a1", + "0xaa3e420321ed6f0aa326d28d1a10f13facec6f605b6218a6eb9cbc074801f3467bf013a456d1415a5536f12599efa3d3", + "0x824aed0951957b00ea2f3d423e30328a3527bf6714cf9abbae84cf27e58e5c35452ba89ccc011de7c68c75d6e021d8f1", + "0xa2e87cc06bf202e953fb1081933d8b4445527dde20e38ed1a4f440144fd8fa464a2b73e068b140562e9045e0f4bd3144", + "0xae3b8f06ad97d7ae3a5e5ca839efff3e4824dc238c0c03fc1a8d2fc8aa546cdfd165b784a31bb4dec7c77e9305b99a4b", + "0xb30c3e12395b1fb8b776f3ec9f87c70e35763a7b2ddc68f0f60a4982a84017f27c891a98561c830038deb033698ed7fc", + "0x874e507757cd1177d0dff0b0c62ce90130324442a33da3b2c8ee09dbca5d543e3ecfe707e9f1361e7c7db641c72794bb", + "0xb53012dd10b5e7460b57c092eaa06d6502720df9edbbe3e3f61a9998a272bf5baaac4a5a732ad4efe35d6fac6feca744", + "0x85e6509d711515534d394e6cacbed6c81da710074d16ef3f4950bf2f578d662a494d835674f79c4d6315bced4defc5f0", + "0xb6132b2a34b0905dcadc6119fd215419a7971fe545e52f48b768006944b4a9d7db1a74b149e2951ea48c083b752d0804", + "0x989867da6415036d19b4bacc926ce6f4df7a556f50a1ba5f3c48eea9cefbb1c09da81481c8009331ee83f0859185e164", + "0x960a6c36542876174d3fbc1505413e29f053ed87b8d38fef3af180491c7eff25200b45dd5fe5d4d8e63c7e8c9c00f4c8", + "0x9040b59bd739d9cc2e8f6e894683429e4e876a8106238689ff4c22770ae5fdae1f32d962b30301fa0634ee163b524f35", + "0xaf3fcd0a45fe9e8fe256dc7eab242ef7f582dd832d147444483c62787ac820fafc6ca55d639a73f76bfa5e7f5462ab8f", + "0xb934c799d0736953a73d91e761767fdb78454355c4b15c680ce08accb57ccf941b13a1236980001f9e6195801cffd692", + "0x8871e8e741157c2c326b22cf09551e78da3c1ec0fc0543136f581f1550f8bab03b0a7b80525c1e99812cdbf3a9698f96", + "0xa8a977f51473a91d178ee8cfa45ffef8d6fd93ab1d6e428f96a3c79816d9c6a93cd70f94d4deda0125fd6816e30f3bea", + "0xa7688b3b0a4fc1dd16e8ba6dc758d3cfe1b7cf401c31739484c7fa253cce0967df1b290769bcefc9d23d3e0cb19e6218", + "0x8ae84322662a57c6d729e6ff9d2737698cc2da2daeb1f39e506618750ed23442a6740955f299e4a15dda6db3e534d2c6", + "0xa04a961cdccfa4b7ef83ced17ab221d6a043b2c718a0d6cc8e6f798507a31f10bf70361f70a049bc8058303fa7f96864", + "0xb463e39732a7d9daec8a456fb58e54b30a6e160aa522a18b9a9e836488cce3342bcbb2e1deab0f5e6ec0a8796d77197d", + "0xb1434a11c6750f14018a2d3bcf94390e2948f4f187e93bb22070ca3e5393d339dc328cbfc3e48815f51929465ffe7d81", + "0x84ff81d73f3828340623d7e3345553610aa22a5432217ef0ebd193cbf4a24234b190c65ca0873c22d10ea7b63bd1fbed", + "0xb6fe2723f0c47757932c2ddde7a4f8434f665612f7b87b4009c2635d56b6e16b200859a8ade49276de0ef27a2b6c970a", + "0x9742884ed7cd52b4a4a068a43d3faa02551a424136c85a9313f7cb58ea54c04aa83b0728fd741d1fe39621e931e88f8f", + "0xb7d2d65ea4d1ad07a5dee39e40d6c03a61264a56b1585b4d76fc5b2a68d80a93a42a0181d432528582bf08d144c2d6a9", + "0x88c0f66bada89f8a43e5a6ead2915088173d106c76f724f4a97b0f6758aed6ae5c37c373c6b92cdd4aea8f6261f3a374", + "0x81f9c43582cb42db3900747eb49ec94edb2284999a499d1527f03315fd330e5a509afa3bff659853570e9886aab5b28b", + "0x821f9d27d6beb416abf9aa5c79afb65a50ed276dbda6060103bc808bcd34426b82da5f23e38e88a55e172f5c294b4d40", + "0x8ba307b9e7cb63a6c4f3851b321aebfdb6af34a5a4c3bd949ff7d96603e59b27ff4dc4970715d35f7758260ff942c9e9", + "0xb142eb6c5f846de33227d0bda61d445a7c33c98f0a8365fe6ab4c1fabdc130849be597ef734305894a424ea715372d08", + "0xa732730ae4512e86a741c8e4c87fee8a05ee840fec0e23b2e037d58dba8dde8d10a9bc5191d34d00598941becbbe467f", + "0xadce6f7c30fd221f6b10a0413cc76435c4bb36c2d60bca821e5c67409fe9dbb2f4c36ef85eb3d734695e4be4827e9fd3", + "0xa74f00e0f9b23aff7b2527ce69852f8906dab9d6abe62ecd497498ab21e57542e12af9918d4fd610bb09e10b0929c510", + "0xa593b6b0ef26448ce4eb3ab07e84238fc020b3cb10d542ff4b16d4e2be1bcde3797e45c9cf753b8dc3b0ffdb63984232", + "0xaed3913afccf1aa1ac0eb4980eb8426d0baccebd836d44651fd72af00d09fac488a870223c42aca3ceb39752070405ae", + "0xb2c44c66a5ea7fde626548ba4cef8c8710191343d3dadfd3bb653ce715c0e03056a5303a581d47dde66e70ea5a2d2779", + "0x8e5029b2ccf5128a12327b5103f7532db599846e422531869560ceaff392236434d87159f597937dbf4054f810c114f4", + "0x82beed1a2c4477e5eb39fc5b0e773b30cfec77ef2b1bf17eadaf60eb35b6d0dd9d8cf06315c48d3546badb3f21cd0cca", + "0x90077bd6cc0e4be5fff08e5d07a5a158d36cebd1d1363125bc4fae0866ffe825b26f933d4ee5427ba5cd0c33c19a7b06", + "0xa7ec0d8f079970e8e34f0ef3a53d3e0e45428ddcef9cc776ead5e542ef06f3c86981644f61c5a637e4faf001fb8c6b3e", + "0xae6d4add6d1a6f90b22792bc9d40723ee6850c27d0b97eefafd5b7fd98e424aa97868b5287cc41b4fbd7023bca6a322c", + "0x831aa917533d077da07c01417feaa1408846363ba2b8d22c6116bb858a95801547dd88b7d7fa1d2e3f0a02bdeb2e103d", + "0x96511b860b07c8a5ed773f36d4aa9d02fb5e7882753bf56303595bcb57e37ccc60288887eb83bef08c657ec261a021a2", + "0x921d2a3e7e9790f74068623de327443666b634c8443aba80120a45bba450df920b2374d96df1ce3fb1b06dd06f8cf6e3", + "0xaa74451d51fe82b4581ead8e506ec6cd881010f7e7dd51fc388eb9a557db5d3c6721f81c151d08ebd9c2591689fbc13e", + "0xa972bfbcf4033d5742d08716c927c442119bdae336bf5dff914523b285ccf31953da2733759aacaa246a9af9f698342c", + "0xad1fcd0cae0e76840194ce4150cb8a56ebed728ec9272035f52a799d480dfc85840a4d52d994a18b6edb31e79be6e8ad", + "0xa2c69fe1d36f235215432dad48d75887a44c99dfa0d78149acc74087da215a44bdb5f04e6eef88ff7eff80a5a7decc77", + "0xa94ab2af2b6ee1bc6e0d4e689ca45380d9fbd3c5a65b9bd249d266a4d4c07bf5d5f7ef2ae6000623aee64027892bf8fe", + "0x881ec1fc514e926cdc66480ac59e139148ff8a2a7895a49f0dff45910c90cdda97b66441a25f357d6dd2471cddd99bb3", + "0x884e6d3b894a914c8cef946a76d5a0c8351843b2bffa2d1e56c6b5b99c84104381dd1320c451d551c0b966f4086e60f9", + "0x817c6c10ce2677b9fc5223500322e2b880583254d0bb0d247d728f8716f5e05c9ff39f135854342a1afecd9fbdcf7c46", + "0xaaf4a9cb686a14619aa1fc1ac285dd3843ac3dd99f2b2331c711ec87b03491c02f49101046f3c5c538dc9f8dba2a0ac2", + "0x97ecea5ce53ca720b5d845227ae61d70269a2f53540089305c86af35f0898bfd57356e74a8a5e083fa6e1ea70080bd31", + "0xa22d811e1a20a75feac0157c418a4bfe745ccb5d29466ffa854dca03e395b6c3504a734341746b2846d76583a780b32e", + "0x940cbaa0d2b2db94ae96b6b9cf2deefbfd059e3e5745de9aec4a25f0991b9721e5cd37ef71c631575d1a0c280b01cd5b", + "0xae33cb4951191258a11044682de861bf8d92d90ce751b354932dd9f3913f542b6a0f8a4dc228b3cd9244ac32c4582832", + "0xa580df5e58c4274fe0f52ac2da1837e32f5c9db92be16c170187db4c358f43e5cfdda7c5911dcc79d77a5764e32325f5", + "0x81798178cb9d8affa424f8d3be67576ba94d108a28ccc01d330c51d5a63ca45bb8ca63a2f569b5c5fe1303cecd2d777f", + "0x89975b91b94c25c9c3660e4af4047a8bacf964783010820dbc91ff8281509379cb3b24c25080d5a01174dd9a049118d5", + "0xa7327fcb3710ed3273b048650bde40a32732ef40a7e58cf7f2f400979c177944c8bc54117ba6c80d5d4260801dddab79", + "0x92b475dc8cb5be4b90c482f122a51bcb3b6c70593817e7e2459c28ea54a7845c50272af38119406eaadb9bcb993368d0", + "0x9645173e9ecefc4f2eae8363504f7c0b81d85f8949a9f8a6c01f2d49e0a0764f4eacecf3e94016dd407fc14494fce9f9", + "0x9215fd8983d7de6ae94d35e6698226fc1454977ae58d42d294be9aad13ac821562ad37d5e7ee5cdfe6e87031d45cd197", + "0x810360a1c9b88a9e36f520ab5a1eb8bed93f52deefbe1312a69225c0a08edb10f87cc43b794aced9c74220cefcc57e7d", + "0xad7e810efd61ed4684aeda9ed8bb02fb9ae4b4b63fda8217d37012b94ff1b91c0087043bfa4e376f961fff030c729f3b", + "0x8b07c95c6a06db8738d10bb03ec11b89375c08e77f0cab7e672ce70b2685667ca19c7e1c8b092821d31108ea18dfd4c7", + "0x968825d025ded899ff7c57245250535c732836f7565eab1ae23ee7e513201d413c16e1ba3f5166e7ac6cf74de8ceef4f", + "0x908243370c5788200703ade8164943ad5f8c458219186432e74dbc9904a701ea307fd9b94976c866e6c58595fd891c4b", + "0x959969d16680bc535cdc6339e6186355d0d6c0d53d7bbfb411641b9bf4b770fd5f575beef5deec5c4fa4d192d455c350", + "0xad177f4f826a961adeac76da40e2d930748effff731756c797eddc4e5aa23c91f070fb69b19221748130b0961e68a6bb", + "0x82f8462bcc25448ef7e0739425378e9bb8a05e283ce54aae9dbebaf7a3469f57833c9171672ad43a79778366c72a5e37", + "0xa28fb275b1845706c2814d9638573e9bc32ff552ebaed761fe96fdbce70395891ca41c400ae438369264e31a2713b15f", + "0x8a9c613996b5e51dadb587a787253d6081ea446bf5c71096980bf6bd3c4b69905062a8e8a3792de2d2ece3b177a71089", + "0x8d5aefef9f60cb27c1db2c649221204dda48bb9bf8bf48f965741da051340e8e4cab88b9d15c69f3f84f4c854709f48a", + "0x93ebf2ca6ad85ab6deace6de1a458706285b31877b1b4d7dcb9d126b63047efaf8c06d580115ec9acee30c8a7212fa55", + "0xb3ee46ce189956ca298057fa8223b7fd1128cf52f39159a58bca03c71dd25161ac13f1472301f72aef3e1993fe1ab269", + "0xa24d7a8d066504fc3f5027ccb13120e2f22896860e02c45b5eba1dbd512d6a17c28f39155ea581619f9d33db43a96f92", + "0xae9ceacbfe12137db2c1a271e1b34b8f92e4816bad1b3b9b6feecc34df0f8b3b0f7ed0133acdf59c537d43d33fc8d429", + "0x83967e69bf2b361f86361bd705dce0e1ad26df06da6c52b48176fe8dfcbeb03c462c1a4c9e649eff8c654b18c876fdef", + "0x9148e6b814a7d779c19c31e33a068e97b597de1f8100513db3c581190513edc4d544801ce3dd2cf6b19e0cd6daedd28a", + "0x94ccdafc84920d320ed22de1e754adea072935d3c5f8c2d1378ebe53d140ea29853f056fb3fb1e375846061a038cc9bc", + "0xafb43348498c38b0fa5f971b8cdd3a62c844f0eb52bc33daf2f67850af0880fce84ecfb96201b308d9e6168a0d443ae3", + "0x86d5736520a83538d4cd058cc4b4e84213ed00ebd6e7af79ae787adc17a92ba5359e28ba6c91936d967b4b28d24c3070", + "0xb5210c1ff212c5b1e9ef9126e08fe120a41e386bb12c22266f7538c6d69c7fd8774f11c02b81fd4e88f9137b020801fe", + "0xb78cfd19f94d24e529d0f52e18ce6185cb238edc6bd43086270fd51dd99f664f43dd4c7d2fe506762fbd859028e13fcf", + "0xa6e7220598c554abdcc3fdc587b988617b32c7bb0f82c06205467dbedb58276cc07cae317a190f19d19078773f4c2bbb", + "0xb88862809487ee430368dccd85a5d72fa4d163ca4aad15c78800e19c1a95be2192719801e315d86cff7795e0544a77e4", + "0x87ecb13a03921296f8c42ceb252d04716f10e09c93962239fcaa0a7fef93f19ab3f2680bc406170108bc583e9ff2e721", + "0xa810cd473832b6581c36ec4cb403f2849357ba2d0b54df98ef3004b8a530c078032922a81d40158f5fb0043d56477f6e", + "0xa247b45dd85ca7fbb718b328f30a03f03c84aef2c583fbdc9fcc9eb8b52b34529e8c8f535505c10598b1b4dac3d7c647", + "0x96ee0b91313c68bac4aa9e065ce9e1d77e51ca4cff31d6a438718c58264dee87674bd97fc5c6b8008be709521e4fd008", + "0x837567ad073e42266951a9a54750919280a2ac835a73c158407c3a2b1904cf0d17b7195a393c71a18ad029cbd9cf79ee", + "0xa6a469c44b67ebf02196213e7a63ad0423aab9a6e54acc6fcbdbb915bc043586993454dc3cd9e4be8f27d67c1050879b", + "0x8712d380a843b08b7b294f1f06e2f11f4ad6bcc655fdde86a4d8bc739c23916f6fad2b902fe47d6212f03607907e9f0e", + "0x920adfb644b534789943cdae1bdd6e42828dda1696a440af2f54e6b97f4f97470a1c6ea9fa6a2705d8f04911d055acd1", + "0xa161c73adf584a0061e963b062f59d90faac65c9b3a936b837a10d817f02fcabfa748824607be45a183dd40f991fe83f", + "0x874f4ecd408c76e625ea50bc59c53c2d930ee25baf4b4eca2440bfbffb3b8bc294db579caa7c68629f4d9ec24187c1ba", + "0x8bff18087f112be7f4aa654e85c71fef70eee8ae480f61d0383ff6f5ab1a0508f966183bb3fc4d6f29cb7ca234aa50d3", + "0xb03b46a3ca3bc743a173cbc008f92ab1aedd7466b35a6d1ca11e894b9482ea9dc75f8d6db2ddd1add99bfbe7657518b7", + "0x8b4f3691403c3a8ad9e097f02d130769628feddfa8c2b3dfe8cff64e2bed7d6e5d192c1e2ba0ac348b8585e94acd5fa1", + "0xa0d9ca4a212301f97591bf65d5ef2b2664766b427c9dd342e23cb468426e6a56be66b1cb41fea1889ac5d11a8e3c50a5", + "0x8c93ed74188ca23b3df29e5396974b9cc135c91fdefdea6c0df694c8116410e93509559af55533a3776ac11b228d69b1", + "0x82dd331fb3f9e344ebdeeb557769b86a2cc8cc38f6c298d7572a33aea87c261afa9dbd898989139b9fc16bc1e880a099", + "0xa65faedf326bcfd8ef98a51410c78b021d39206704e8291cd1f09e096a66b9b0486be65ff185ca224c45918ac337ddeb", + "0xa188b37d363ac072a766fd5d6fa27df07363feff1342217b19e3c37385e42ffde55e4be8355aceaa2f267b6d66b4ac41", + "0x810fa3ba3e96d843e3bafd3f2995727f223d3567c8ba77d684c993ba1773c66551eb5009897c51b3fe9b37196984f5ec", + "0x87631537541852da323b4353af45a164f68b304d24c01183bf271782e11687f3fcf528394e1566c2a26cb527b3148e64", + "0xb721cb2b37b3c477a48e3cc0044167d51ff568a5fd2fb606e5aec7a267000f1ddc07d3db919926ae12761a8e017c767c", + "0x904dfad4ba2cc1f6e60d1b708438a70b1743b400164cd981f13c064b8328d5973987d4fb9cf894068f29d3deaf624dfb", + "0xa70491538893552c20939fae6be2f07bfa84d97e2534a6bbcc0f1729246b831103505e9f60e97a8fa7d2e6c1c2384579", + "0x8726cf1b26b41f443ff7485adcfddc39ace2e62f4d65dd0bb927d933e262b66f1a9b367ded5fbdd6f3b0932553ac1735", + "0xae8a11cfdf7aa54c08f80cb645e3339187ab3886babe9fae5239ba507bb3dd1c0d161ca474a2df081dcd3d63e8fe445e", + "0x92328719e97ce60e56110f30a00ac5d9c7a2baaf5f8d22355d53c1c77941e3a1fec7d1405e6fbf8959665fe2ba7a8cad", + "0x8d9d6255b65798d0018a8cccb0b6343efd41dc14ff2058d3eed9451ceaad681e4a0fa6af67b0a04318aa628024e5553d", + "0xb70209090055459296006742d946a513f0cba6d83a05249ee8e7a51052b29c0ca9722dc4af5f9816a1b7938a5dac7f79", + "0xaab7b766b9bf91786dfa801fcef6d575dc6f12b77ecc662eb4498f0312e54d0de9ea820e61508fc8aeee5ab5db529349", + "0xa8104b462337748b7f086a135d0c3f87f8e51b7165ca6611264b8fb639d9a2f519926cb311fa2055b5fadf03da70c678", + "0xb0d2460747d5d8b30fc6c6bd0a87cb343ddb05d90a51b465e8f67d499cfc5e3a9e365da05ae233bbee792cdf90ec67d5", + "0xaa55f5bf3815266b4a149f85ed18e451c93de9163575e3ec75dd610381cc0805bb0a4d7c4af5b1f94d10231255436d2c", + "0x8d4c6a1944ff94426151909eb5b99cfd92167b967dabe2bf3aa66bb3c26c449c13097de881b2cfc1bf052862c1ef7b03", + "0x8862296162451b9b6b77f03bf32e6df71325e8d7485cf3335d66fd48b74c2a8334c241db8263033724f26269ad95b395", + "0x901aa96deb26cda5d9321190ae6624d357a41729d72ef1abfd71bebf6139af6d690798daba53b7bc5923462115ff748a", + "0x96c195ec4992728a1eb38cdde42d89a7bce150db43adbc9e61e279ea839e538deec71326b618dd39c50d589f78fc0614", + "0xb6ff8b8aa0837b99a1a8b46fb37f20ad4aecc6a98381b1308697829a59b8442ffc748637a88cb30c9b1f0f28a926c4f6", + "0x8d807e3dca9e7bef277db1d2cfb372408dd587364e8048b304eff00eacde2c723bfc84be9b98553f83cba5c7b3cba248", + "0x8800c96adb0195c4fc5b24511450dee503c32bf47044f5e2e25bd6651f514d79a2dd9b01cd8c09f3c9d3859338490f57", + "0x89fe366096097e38ec28dd1148887112efa5306cc0c3da09562aafa56f4eb000bf46ff79bf0bdd270cbde6bf0e1c8957", + "0xaf409a90c2776e1e7e3760b2042507b8709e943424606e31e791d42f17873a2710797f5baaab4cc4a19998ef648556b0", + "0x8d761863c9b6edbd232d35ab853d944f5c950c2b643f84a1a1327ebb947290800710ff01dcfa26dc8e9828481240e8b1", + "0x90b95e9be1e55c463ed857c4e0617d6dc3674e99b6aa62ed33c8e79d6dfcf7d122f4f4cc2ee3e7c5a49170cb617d2e2e", + "0xb3ff381efefabc4db38cc4727432e0301949ae4f16f8d1dea9b4f4de611cf5a36d84290a0bef160dac4e1955e516b3b0", + "0xa8a84564b56a9003adcadb3565dc512239fc79572762cda7b5901a255bc82656bb9c01212ad33d6bef4fbbce18dacc87", + "0x90a081890364b222eef54bf0075417f85e340d2fec8b7375995f598aeb33f26b44143ebf56fca7d8b4ebb36b5747b0eb", + "0xade6ee49e1293224ddf2d8ab7f14bb5be6bc6284f60fd5b3a1e0cf147b73cff57cf19763b8a36c5083badc79c606b103", + "0xb2fa99806dd2fa3de09320b615a2570c416c9bcdb052e592b0aead748bbe407ec9475a3d932ae48b71c2627eb81986a6", + "0x91f3b7b73c8ccc9392542711c45fe6f236057e6efad587d661ad5cb4d6e88265f86b807bb1151736b1009ab74fd7acb4", + "0x8800e2a46af96696dfbdcbf2ca2918b3dcf28ad970170d2d1783b52b8d945a9167d052beeb55f56c126da7ffa7059baa", + "0x9862267a1311c385956b977c9aa08548c28d758d7ba82d43dbc3d0a0fd1b7a221d39e8399997fea9014ac509ff510ac4", + "0xb7d24f78886fd3e2d283e18d9ad5a25c1a904e7d9b9104bf47da469d74f34162e27e531380dbbe0a9d051e6ffd51d6e7", + "0xb0f445f9d143e28b9df36b0f2c052da87ee2ca374d9d0fbe2eff66ca6fe5fe0d2c1951b428d58f7314b7e74e45d445ea", + "0xb63fc4083eabb8437dafeb6a904120691dcb53ce2938b820bb553da0e1eecd476f72495aacb72600cf9cad18698fd3db", + "0xb9ffd8108eaebd582d665f8690fe8bb207fd85185e6dd9f0b355a09bac1bbff26e0fdb172bc0498df025414e88fe2eda", + "0x967ed453e1f1a4c5b7b6834cc9f75c13f6889edc0cc91dc445727e9f408487bbf05c337103f61397a10011dfbe25d61d", + "0x98ceb673aff36e1987d5521a3984a07079c3c6155974bb8b413e8ae1ce84095fe4f7862fba7aefa14753eb26f2a5805f", + "0x85f01d28603a8fdf6ce6a50cb5c44f8a36b95b91302e3f4cd95c108ce8f4d212e73aec1b8d936520d9226802a2bd9136", + "0x88118e9703200ca07910345fbb789e7a8f92bd80bbc79f0a9e040e8767d33df39f6eded403a9b636eabf9101e588482a", + "0x90833a51eef1b10ed74e8f9bbd6197e29c5292e469c854eed10b0da663e2bceb92539710b1858bbb21887bd538d28d89", + "0xb513b905ec19191167c6193067b5cfdf5a3d3828375360df1c7e2ced5815437dfd37f0c4c8f009d7fb29ff3c8793f560", + "0xb1b6d405d2d18f9554b8a358cc7e2d78a3b34269737d561992c8de83392ac9a2857be4bf15de5a6c74e0c9d0f31f393c", + "0xb828bd3e452b797323b798186607849f85d1fb20c616833c0619360dfd6b3e3aa000fd09dafe4b62d74abc41072ff1a9", + "0x8efde67d0cca56bb2c464731879c9ac46a52e75bac702a63200a5e192b4f81c641f855ca6747752b84fe469cb7113b6c", + "0xb2762ba1c89ac3c9a983c242e4d1c2610ff0528585ed5c0dfc8a2c0253551142af9b59f43158e8915a1da7cc26b9df67", + "0x8a3f1157fb820d1497ef6b25cd70b7e16bb8b961b0063ad340d82a79ee76eb2359ca9e15e6d42987ed7f154f5eeaa2da", + "0xa75e29f29d38f09c879f971c11beb5368affa084313474a5ecafa2896180b9e47ea1995c2733ec46f421e395a1d9cffe", + "0x8e8c3dd3e7196ef0b4996b531ec79e4a1f211db5d5635e48ceb80ff7568b2ff587e845f97ee703bb23a60945ad64314a", + "0x8e7f32f4a3e3c584af5e3d406924a0aa34024c42eca74ef6cc2a358fd3c9efaf25f1c03aa1e66bb94b023a2ee2a1cace", + "0xab7dce05d59c10a84feb524fcb62478906b3fa045135b23afbede3bb32e0c678d8ebe59feabccb5c8f3550ea76cae44b", + "0xb38bb4b44d827f6fd3bd34e31f9186c59e312dbfadd4a7a88e588da10146a78b1f8716c91ad8b806beb8da65cab80c4c", + "0x9490ce9442bbbd05438c7f5c4dea789f74a7e92b1886a730544b55ba377840740a3ae4f2f146ee73f47c9278b0e233bc", + "0x83c003fab22a7178eed1a668e0f65d4fe38ef3900044e9ec63070c23f2827d36a1e73e5c2b883ec6a2afe2450171b3b3", + "0x9982f02405978ddc4fca9063ebbdb152f524c84e79398955e66fe51bc7c1660ec1afc3a86ec49f58d7b7dde03505731c", + "0xab337bd83ccdd2322088ffa8d005f450ced6b35790f37ab4534313315ee84312adc25e99cce052863a8bedee991729ed", + "0x8312ce4bec94366d88f16127a17419ef64285cd5bf9e5eda010319b48085966ed1252ed2f5a9fd3e0259b91bb65f1827", + "0xa60d5a6327c4041b0c00a1aa2f0af056520f83c9ce9d9ccd03a0bd4d9e6a1511f26a422ea86bd858a1f77438adf07e6c", + "0xb84a0a0b030bdad83cf5202aa9afe58c9820e52483ab41f835f8c582c129ee3f34aa096d11c1cd922eda02ea1196a882", + "0x8077d105317f4a8a8f1aadeb05e0722bb55f11abcb490c36c0904401107eb3372875b0ac233144829e734f0c538d8c1d", + "0x9202503bd29a6ec198823a1e4e098f9cfe359ed51eb5174d1ca41368821bfeebcbd49debfd02952c41359d1c7c06d2b1", + "0xabc28c155e09365cb77ffead8dc8f602335ef93b2f44e4ef767ce8fc8ef9dd707400f3a722e92776c2e0b40192c06354", + "0xb0f6d1442533ca45c9399e0a63a11f85ff288d242cea6cb3b68c02e77bd7d158047cae2d25b3bcd9606f8f66d9b32855", + "0xb01c3d56a0db84dc94575f4b6ee2de4beca3230e86bed63e2066beb22768b0a8efb08ebaf8ac3dedb5fe46708b084807", + "0x8c8634b0432159f66feaabb165842d1c8ac378f79565b1b90c381aa8450eb4231c3dad11ec9317b9fc2b155c3a771e32", + "0x8e67f623d69ecd430c9ee0888520b6038f13a2b6140525b056dc0951f0cfed2822e62cf11d952a483107c5c5acac4826", + "0x9590bb1cba816dd6acd5ac5fba5142c0a19d53573e422c74005e0bcf34993a8138c83124cad35a3df65879dba6134edd", + "0x801cd96cde0749021a253027118d3ea135f3fcdbe895db08a6c145641f95ebd368dd6a1568d995e1d0084146aebe224a", + "0x848b5d196427f6fc1f762ee3d36e832b64a76ec1033cfedc8b985dea93932a7892b8ef1035c653fb9dcd9ab2d9a44ac8", + "0xa1017eb83d5c4e2477e7bd2241b2b98c4951a3b391081cae7d75965cadc1acaec755cf350f1f3d29741b0828e36fedea", + "0x8d6d2785e30f3c29aad17bd677914a752f831e96d46caf54446d967cb2432be2c849e26f0d193a60bee161ea5c6fe90a", + "0x935c0ba4290d4595428e034b5c8001cbd400040d89ab00861108e8f8f4af4258e41f34a7e6b93b04bc253d3b9ffc13bf", + "0xaac02257146246998477921cef2e9892228590d323b839f3e64ea893b991b463bc2f47e1e5092ddb47e70b2f5bce7622", + "0xb921fde9412970a5d4c9a908ae8ce65861d06c7679af577cf0ad0d5344c421166986bee471fd6a6cecb7d591f06ec985", + "0x8ef4c37487b139d6756003060600bb6ebac7ea810b9c4364fc978e842f13ac196d1264fbe5af60d76ff6d9203d8e7d3f", + "0x94b65e14022b5cf6a9b95f94be5ace2711957c96f4211c3f7bb36206bd39cfbd0ea82186cab5ad0577a23214a5c86e9e", + "0xa31c166d2a2ca1d5a75a5920fef7532681f62191a50d8555fdaa63ba4581c3391cc94a536fc09aac89f64eafceec3f90", + "0x919a8cc128de01e9e10f5d83b08b52293fdd41bde2b5ae070f3d95842d4a16e5331cf2f3d61c765570c8022403610fa4", + "0xb23d6f8331eef100152d60483cfa14232a85ee712c8538c9b6417a5a7c5b353c2ac401390c6c215cb101f5cee6b5f43e", + "0xab357160c08a18319510a571eafff154298ce1020de8e1dc6138a09fcb0fcbcdd8359f7e9386bda00b7b9cdea745ffdc", + "0xab55079aea34afa5c0bd1124b9cdfe01f325b402fdfa017301bf87812eaa811ea5798c3aaf818074d420d1c782b10ada", + "0xade616010dc5009e7fc4f8d8b00dc716686a5fa0a7816ad9e503e15839d3b909b69d9dd929b7575376434ffec0d2bea8", + "0x863997b97ed46898a8a014599508fa3079f414b1f4a0c4fdc6d74ae8b444afa350f327f8bfc2a85d27f9e2d049c50135", + "0x8d602ff596334efd4925549ed95f2aa762b0629189f0df6dbb162581657cf3ea6863cd2287b4d9c8ad52813d87fcd235", + "0xb70f68c596dcdeed92ad5c6c348578b26862a51eb5364237b1221e840c47a8702f0fbc56eb520a22c0eed99795d3903e", + "0x9628088f8e0853cefadee305a8bf47fa990c50fa96a82511bbe6e5dc81ef4b794e7918a109070f92fc8384d77ace226f", + "0x97e26a46e068b605ce96007197ecd943c9a23881862f4797a12a3e96ba2b8d07806ad9e2a0646796b1889c6b7d75188c", + "0xb1edf467c068cc163e2d6413cc22b16751e78b3312fe47b7ea82b08a1206d64415b2c8f2a677fa89171e82cc49797150", + "0xa44d15ef18745b251429703e3cab188420e2d974de07251501799b016617f9630643fcd06f895634d8ecdd579e1bf000", + "0xabd126df3917ba48c618ee4dbdf87df506193462f792874439043fa1b844466f6f4e0ff2e42516e63b5b23c0892b2695", + "0xa2a67f57c4aa3c2aa1eeddbfd5009a89c26c2ce8fa3c96a64626aba19514beb125f27df8559506f737de3eae0f1fc18f", + "0xa633e0132197e6038197304b296ab171f1d8e0d0f34dcf66fe9146ac385b0239232a8470b9205a4802ab432389f4836d", + "0xa914b3a28509a906c3821463b936455d58ff45dcbe158922f9efb2037f2eb0ce8e92532d29b5d5a3fcd0d23fa773f272", + "0xa0e1412ce4505daf1a2e59ce4f0fc0e0023e335b50d2b204422f57cd65744cc7a8ed35d5ef131a42c70b27111d3115b7", + "0xa2339e2f2b6072e88816224fdd612c04d64e7967a492b9f8829db15367f565745325d361fd0607b0def1be384d010d9e", + "0xa7309fc41203cb99382e8193a1dcf03ac190a7ce04835304eb7e341d78634e83ea47cb15b885601956736d04cdfcaa01", + "0x81f3ccd6c7f5b39e4e873365f8c37b214e8ab122d04a606fbb7339dc3298c427e922ec7418002561d4106505b5c399ee", + "0x92c121cf914ca549130e352eb297872a63200e99b148d88fbc9506ad882bec9d0203d65f280fb5b0ba92e336b7f932e8", + "0xa4b330cf3f064f5b131578626ad7043ce2a433b6f175feb0b52d36134a454ca219373fd30d5e5796410e005b69082e47", + "0x86fe5774112403ad83f9c55d58317eeb17ad8e1176d9f2f69c2afb7ed83bc718ed4e0245ceab4b377f5f062dcd4c00e7", + "0x809d152a7e2654c7fd175b57f7928365a521be92e1ed06c05188a95864ddb25f7cab4c71db7d61bbf4cae46f3a1d96ce", + "0xb82d663e55c2a5ada7e169e9b1a87bc1c0177baf1ec1c96559b4cb1c5214ce1ddf2ab8d345014cab6402f3774235cf5a", + "0x86580af86df1bd2c385adb8f9a079e925981b7184db66fc5fe5b14cddb82e7d836b06eaeef14924ac529487b23dae111", + "0xb5f5f4c5c94944ecc804df6ab8687d64e27d988cbfeae1ba7394e0f6adbf778c5881ead7cd8082dd7d68542b9bb4ecd5", + "0xa6016916146c2685c46e8fdd24186394e2d5496e77e08c0c6a709d4cd7dfa97f1efcef94922b89196819076a91ad37b5", + "0xb778e7367ded3b6eab53d5fc257f7a87e8faf74a593900f2f517220add2125be3f6142022660d8181df8d164ad9441ce", + "0x8581b2d36abe6f553add4d24be761bec1b8efaa2929519114346615380b3c55b59e6ad86990e312f7e234d0203bdf59b", + "0x9917e74fd45c3f71a829ff5498a7f6b5599b48c098dda2339bf04352bfc7f368ccf1a407f5835901240e76452ae807d7", + "0xafd196ce6f9335069138fd2e3d133134da253978b4ce373152c0f26affe77a336505787594022e610f8feb722f7cc1fb", + "0xa477491a1562e329764645e8f24d8e228e5ef28c9f74c6b5b3abc4b6a562c15ffb0f680d372aed04d9e1bf944dece7be", + "0x9767440d58c57d3077319d3a330e5322b9ba16981ec74a5a14d53462eab59ae7fd2b14025bfc63b268862094acb444e6", + "0x80986d921be3513ef69264423f351a61cb48390c1be8673aee0f089076086aaebea7ebe268fd0aa7182695606116f679", + "0xa9554c5c921c07b450ee04e34ec58e054ac1541b26ce2ce5a393367a97348ba0089f53db6660ad76b60278b66fd12e3e", + "0x95097e7d2999b3e84bf052c775581cf361325325f4a50192521d8f4693c830bed667d88f482dc1e3f833aa2bd22d2cbf", + "0x9014c91d0f85aefd28436b5228c12f6353c055a9326c7efbf5e071e089e2ee7c070fcbc84c5fafc336cbb8fa6fec1ca1", + "0x90f57ba36ee1066b55d37384942d8b57ae00f3cf9a3c1d6a3dfee1d1af42d4b5fa9baeb0cd7e46687d1d6d090ddb931d", + "0x8e4b1db12fd760a17214c9e47f1fce6e43c0dbb4589a827a13ac61aaae93759345697bb438a00edab92e0b7b62414683", + "0x8022a959a513cdc0e9c705e0fc04eafd05ff37c867ae0f31f6d01cddd5df86138a426cab2ff0ac8ff03a62e20f7e8f51", + "0x914e9a38829834c7360443b8ed86137e6f936389488eccf05b4b4db7c9425611705076ecb3f27105d24b85c852be7511", + "0x957fb10783e2bd0db1ba66b18e794df710bc3b2b05776be146fa5863c15b1ebdd39747b1a95d9564e1772cdfc4f37b8a", + "0xb6307028444daed8ed785ac9d0de76bc3fe23ff2cc7e48102553613bbfb5afe0ebe45e4212a27021c8eb870721e62a1f", + "0x8f76143597777d940b15a01b39c5e1b045464d146d9a30a6abe8b5d3907250e6c7f858ff2308f8591e8b0a7b3f3c568a", + "0x96163138ac0ce5fd00ae9a289648fd9300a0ca0f63a88481d703ecd281c06a52a3b5178e849e331f9c85ca4ba398f4cc", + "0xa63ef47c3e18245b0482596a09f488a716df3cbd0f9e5cfabed0d742843e65db8961c556f45f49762f3a6ac8b627b3ef", + "0x8cb595466552e7c4d42909f232d4063e0a663a8ef6f6c9b7ce3a0542b2459cde04e0e54c7623d404acb5b82775ac04f6", + "0xb47fe69960eb45f399368807cff16d941a5a4ebad1f5ec46e3dc8a2e4d598a7e6114d8f0ca791e9720fd786070524e2b", + "0x89eb5ff83eea9df490e5beca1a1fbbbbcf7184a37e2c8c91ede7a1e654c81e8cd41eceece4042ea7918a4f4646b67fd6", + "0xa84f5d155ed08b9054eecb15f689ba81e44589e6e7207a99790c598962837ca99ec12344105b16641ca91165672f7153", + "0xa6cc8f25c2d5b2d2f220ec359e6a37a52b95fa6af6e173c65e7cd55299eff4aa9e6d9e6f2769e6459313f1f2aecb0fab", + "0xafcde944411f017a9f7979755294981e941cc41f03df5e10522ef7c7505e5f1babdd67b3bf5258e8623150062eb41d9b", + "0x8fab39f39c0f40182fcd996ade2012643fe7731808afbc53f9b26900b4d4d1f0f5312d9d40b3df8baa4739970a49c732", + "0xae193af9726da0ebe7df1f9ee1c4846a5b2a7621403baf8e66c66b60f523e719c30c6b4f897bb14b27d3ff3da8392eeb", + "0x8ac5adb82d852eba255764029f42e6da92dcdd0e224d387d1ef94174038db9709ac558d90d7e7c57ad4ce7f89bbfc38c", + "0xa2066b3458fdf678ee487a55dd5bfb74fde03b54620cb0e25412a89ee28ad0d685e309a51e3e4694be2fa6f1593a344c", + "0x88d031745dd0ae07d61a15b594be5d4b2e2a29e715d081649ad63605e3404b0c3a5353f0fd9fad9c05c18e93ce674fa1", + "0x8283cfb0ef743a043f2b77ecaeba3005e2ca50435585b5dd24777ee6bce12332f85e21b446b536da38508807f0f07563", + "0xb376de22d5f6b0af0b59f7d9764561f4244cf8ffe22890ecd3dcf2ff1832130c9b821e068c9d8773136f4796721e5963", + "0xae3afc50c764f406353965363840bf28ee85e7064eb9d5f0bb3c31c64ab10f48c853e942ee2c9b51bae59651eaa08c2f", + "0x948b204d103917461a01a6c57a88f2d66b476eae5b00be20ec8c747650e864bc8a83aee0aff59cb7584b7a3387e0ee48", + "0x81ab098a082b07f896c5ffd1e4446cb7fb44804cbbf38d125208b233fc82f8ec9a6a8d8dd1c9a1162dc28ffeec0dde50", + "0xa149c6f1312821ced2969268789a3151bdda213451760b397139a028da609c4134ac083169feb0ee423a0acafd10eceb", + "0xb0ac9e27a5dadaf523010f730b28f0ebac01f460d3bbbe277dc9d44218abb5686f4fac89ae462682fef9edbba663520a", + "0x8d0e0073cca273daaaa61b6fc54bfe5a009bc3e20ae820f6c93ba77b19eca517d457e948a2de5e77678e4241807157cb", + "0xad61d3a2edf7c7533a04964b97499503fd8374ca64286dba80465e68fe932e96749b476f458c6fc57cb1a7ca85764d11", + "0x90eb5e121ae46bc01a30881eaa556f46bd8457a4e80787cf634aab355082de34ac57d7f497446468225f7721e68e2a47", + "0x8cdac557de7c42d1f3780e33dec1b81889f6352279be81c65566cdd4952d4c15d79e656cbd46035ab090b385e90245ef", + "0x82b67e61b88b84f4f4d4f65df37b3e3dcf8ec91ea1b5c008fdccd52da643adbe6468a1cfdb999e87d195afe2883a3b46", + "0x8503b467e8f5d6048a4a9b78496c58493a462852cab54a70594ae3fd064cfd0deb4b8f336a262155d9fedcaa67d2f6fd", + "0x8db56c5ac763a57b6ce6832930c57117058e3e5a81532b7d19346346205e2ec614eb1a2ee836ef621de50a7bc9b7f040", + "0xad344699198f3c6e8c0a3470f92aaffc805b76266734414c298e10b5b3797ca53578de7ccb2f458f5e0448203f55282b", + "0x80602032c43c9e2a09154cc88b83238343b7a139f566d64cb482d87436b288a98f1ea244fd3bff8da3c398686a900c14", + "0xa6385bd50ecd548cfb37174cdbb89e10025b5cadaf3cff164c95d7aef5a33e3d6a9bf0c681b9e11db9ef54ebeee2a0c1", + "0xabf2d95f4aa34b0581eb9257a0cc8462b2213941a5deb8ba014283293e8b36613951b61261cc67bbd09526a54cbbff76", + "0xa3d5de52f48df72c289ff713e445991f142390798cd42bd9d9dbefaee4af4f5faf09042d126b975cf6b98711c3072553", + "0x8e627302ff3d686cff8872a1b7c2a57b35f45bf2fc9aa42b049d8b4d6996a662b8e7cbac6597f0cb79b0cc4e29fbf133", + "0x8510702e101b39a1efbf4e504e6123540c34b5689645e70d0bac1ecc1baf47d86c05cef6c4317a4e99b4edaeb53f2d00", + "0xaa173f0ecbcc6088f878f8726d317748c81ebf501bba461f163b55d66099b191ec7c55f7702f351a9c8eb42cfa3280e2", + "0xb560a697eafab695bcef1416648a0a664a71e311ecbe5823ae903bd0ed2057b9d7574b9a86d3fe22aa3e6ddce38ea513", + "0x8df6304a3d9cf40100f3f687575419c998cd77e5cc27d579cf4f8e98642de3609af384a0337d145dd7c5635172d26a71", + "0x8105c7f3e4d30a29151849673853b457c1885c186c132d0a98e63096c3774bc9deb956cf957367e633d0913680bda307", + "0x95373fc22c0917c3c2044ac688c4f29a63ed858a45c0d6d2d0fe97afd6f532dcb648670594290c1c89010ecc69259bef", + "0x8c2fae9bcadab341f49b55230310df93cac46be42d4caa0d42e45104148a91e527af1b4209c0d972448162aed28fab64", + "0xb05a77baab70683f76209626eaefdda2d36a0b66c780a20142d23c55bd479ddd4ad95b24579384b6cf62c8eb4c92d021", + "0x8e6bc6a7ea2755b4aaa19c1c1dee93811fcde514f03485fdc3252f0ab7f032c315614f6336e57cea25dcfb8fb6084eeb", + "0xb656a27d06aade55eadae2ad2a1059198918ea6cc3fd22c0ed881294d34d5ac7b5e4700cc24350e27d76646263b223aa", + "0xa296469f24f6f56da92d713afcd4dd606e7da1f79dc4e434593c53695847eefc81c7c446486c4b3b8c8d00c90c166f14", + "0x87a326f57713ac2c9dffeb3af44b9f3c613a8f952676fc46343299122b47ee0f8d792abaa4b5db6451ced5dd153aabd0", + "0xb689e554ba9293b9c1f6344a3c8fcb6951d9f9eac4a2e2df13de021aade7c186be27500e81388e5b8bcab4c80f220a31", + "0x87ae0aa0aa48eac53d1ca5a7b93917de12db9e40ceabf8fdb40884ae771cfdf095411deef7c9f821af0b7070454a2608", + "0xa71ffa7eae8ace94e6c3581d4cb2ad25d48cbd27edc9ec45baa2c8eb932a4773c3272b2ffaf077b40f76942a1f3af7f2", + "0x94c218c91a9b73da6b7a495b3728f3028df8ad9133312fc0c03e8c5253b7ccb83ed14688fd4602e2fd41f29a0bc698bd", + "0xae1e77b90ca33728af07a4c03fb2ef71cd92e2618e7bf8ed4d785ce90097fc4866c29999eb84a6cf1819d75285a03af2", + "0xb7a5945b277dab9993cf761e838b0ac6eaa903d7111fca79f9fde3d4285af7a89bf6634a71909d095d7619d913972c9c", + "0x8c43b37be02f39b22029b20aca31bff661abce4471dca88aa3bddefd9c92304a088b2dfc8c4795acc301ca3160656af2", + "0xb32e5d0fba024554bd5fe8a793ebe8003335ddd7f585876df2048dcf759a01285fecb53daae4950ba57f3a282a4d8495", + "0x85ea7fd5e10c7b659df5289b2978b2c89e244f269e061b9a15fcab7983fc1962b63546e82d5731c97ec74b6804be63ef", + "0x96b89f39181141a7e32986ac02d7586088c5a9662cec39843f397f3178714d02f929af70630c12cbaba0268f8ba2d4fa", + "0x929ab1a2a009b1eb37a2817c89696a06426529ebe3f306c586ab717bd34c35a53eca2d7ddcdef36117872db660024af9", + "0xa696dccf439e9ca41511e16bf3042d7ec0e2f86c099e4fc8879d778a5ea79e33aa7ce96b23dc4332b7ba26859d8e674d", + "0xa8fe69a678f9a194b8670a41e941f0460f6e2dbc60470ab4d6ae2679cc9c6ce2c3a39df2303bee486dbfde6844e6b31a", + "0x95f58f5c82de2f2a927ca99bf63c9fc02e9030c7e46d0bf6b67fe83a448d0ae1c99541b59caf0e1ccab8326231af09a5", + "0xa57badb2c56ca2c45953bd569caf22968f76ed46b9bac389163d6fe22a715c83d5e94ae8759b0e6e8c2f27bff7748f3f", + "0x868726fd49963b24acb5333364dffea147e98f33aa19c7919dc9aca0fd26661cfaded74ede7418a5fadbe7f5ae67b67b", + "0xa8d8550dcc64d9f1dd7bcdab236c4122f2b65ea404bb483256d712c7518f08bb028ff8801f1da6aed6cbfc5c7062e33b", + "0x97e25a87dae23155809476232178538d4bc05d4ff0882916eb29ae515f2a62bfce73083466cc0010ca956aca200aeacc", + "0xb4ea26be3f4bd04aa82d7c4b0913b97bcdf5e88b76c57eb1a336cbd0a3eb29de751e1bc47c0e8258adec3f17426d0c71", + "0x99ee555a4d9b3cf2eb420b2af8e3bc99046880536116d0ce7193464ac40685ef14e0e3c442f604e32f8338cb0ef92558", + "0x8c64efa1da63cd08f319103c5c7a761221080e74227bbc58b8fb35d08aa42078810d7af3e60446cbaff160c319535648", + "0x8d9fd88040076c28420e3395cbdfea402e4077a3808a97b7939d49ecbcf1418fe50a0460e1c1b22ac3f6e7771d65169a", + "0xae3c19882d7a9875d439265a0c7003c8d410367627d21575a864b9cb4918de7dbdb58a364af40c5e045f3df40f95d337", + "0xb4f7bfacab7b2cafe393f1322d6dcc6f21ffe69cd31edc8db18c06f1a2b512c27bd0618091fd207ba8df1808e9d45914", + "0x94f134acd0007c623fb7934bcb65ef853313eb283a889a3ffa79a37a5c8f3665f3d5b4876bc66223610c21dc9b919d37", + "0xaa15f74051171daacdc1f1093d3f8e2d13da2833624b80a934afec86fc02208b8f55d24b7d66076444e7633f46375c6a", + "0xa32d6bb47ef9c836d9d2371807bafbbbbb1ae719530c19d6013f1d1f813c49a60e4fa51d83693586cba3a840b23c0404", + "0xb61b3599145ea8680011aa2366dc511a358b7d67672d5b0c5be6db03b0efb8ca5a8294cf220ea7409621f1664e00e631", + "0x859cafc3ee90b7ececa1ed8ef2b2fc17567126ff10ca712d5ffdd16aa411a5a7d8d32c9cab1fbf63e87dce1c6e2f5f53", + "0xa2fef1b0b2874387010e9ae425f3a9676d01a095d017493648bcdf3b31304b087ccddb5cf76abc4e1548b88919663b6b", + "0x939e18c73befc1ba2932a65ede34c70e4b91e74cc2129d57ace43ed2b3af2a9cc22a40fbf50d79a63681b6d98852866d", + "0xb3b4259d37b1b14aee5b676c9a0dd2d7f679ab95c120cb5f09f9fbf10b0a920cb613655ddb7b9e2ba5af4a221f31303c", + "0x997255fe51aaca6e5a9cb3359bcbf25b2bb9e30649bbd53a8a7c556df07e441c4e27328b38934f09c09d9500b5fabf66", + "0xabb91be2a2d860fd662ed4f1c6edeefd4da8dc10e79251cf87f06029906e7f0be9b486462718f0525d5e049472692cb7", + "0xb2398e593bf340a15f7801e1d1fbda69d93f2a32a889ec7c6ae5e8a37567ac3e5227213c1392ee86cfb3b56ec2787839", + "0x8ddf10ccdd72922bed36829a36073a460c2118fc7a56ff9c1ac72581c799b15c762cb56cb78e3d118bb9f6a7e56cb25e", + "0x93e6bc0a4708d16387cacd44cf59363b994dc67d7ada7b6d6dbd831c606d975247541b42b2a309f814c1bfe205681fc6", + "0xb93fc35c05998cffda2978e12e75812122831523041f10d52f810d34ff71944979054b04de0117e81ddf5b0b4b3e13c0", + "0x92221631c44d60d68c6bc7b287509f37ee44cbe5fdb6935cee36b58b17c7325098f98f7910d2c3ca5dc885ad1d6dabc7", + "0xa230124424a57fad3b1671f404a94d7c05f4c67b7a8fbacfccea28887b78d7c1ed40b92a58348e4d61328891cd2f6cee", + "0xa6a230edb8518a0f49d7231bc3e0bceb5c2ac427f045819f8584ba6f3ae3d63ed107a9a62aad543d7e1fcf1f20605706", + "0x845be1fe94223c7f1f97d74c49d682472585d8f772762baad8a9d341d9c3015534cc83d102113c51a9dea2ab10d8d27b", + "0xb44262515e34f2db597c8128c7614d33858740310a49cdbdf9c8677c5343884b42c1292759f55b8b4abc4c86e4728033", + "0x805592e4a3cd07c1844bc23783408310accfdb769cca882ad4d07d608e590a288b7370c2cb327f5336e72b7083a0e30f", + "0x95153e8b1140df34ee864f4ca601cb873cdd3efa634af0c4093fbaede36f51b55571ab271e6a133020cd34db8411241f", + "0x82878c1285cfa5ea1d32175c9401f3cc99f6bb224d622d3fd98cc7b0a27372f13f7ab463ce3a33ec96f9be38dbe2dfe3", + "0xb7588748f55783077c27fc47d33e20c5c0f5a53fc0ac10194c003aa09b9f055d08ec971effa4b7f760553997a56967b3", + "0xb36b4de6d1883b6951f59cfae381581f9c6352fcfcf1524fccdab1571a20f80441d9152dc6b48bcbbf00371337ca0bd5", + "0x89c5523f2574e1c340a955cbed9c2f7b5fbceb260cb1133160dabb7d41c2f613ec3f6e74bbfab3c4a0a6f0626dbe068f", + "0xa52f58cc39f968a9813b1a8ddc4e83f4219e4dd82c7aa1dd083bea7edf967151d635aa9597457f879771759b876774e4", + "0x8300a67c2e2e123f89704abfde095463045dbd97e20d4c1157bab35e9e1d3d18f1f4aaba9cbe6aa2d544e92578eaa1b6", + "0xac6a7f2918768eb6a43df9d3a8a04f8f72ee52f2e91c064c1c7d75cad1a3e83e5aba9fe55bb94f818099ac91ccf2e961", + "0x8d64a2b0991cf164e29835c8ddef6069993a71ec2a7de8157bbfa2e00f6367be646ed74cbaf524f0e9fe13fb09fa15fd", + "0x8b2ffe5a545f9f680b49d0a9797a4a11700a2e2e348c34a7a985fc278f0f12def6e06710f40f9d48e4b7fbb71e072229", + "0x8ab8f71cd337fa19178924e961958653abf7a598e3f022138b55c228440a2bac4176cea3aea393549c03cd38a13eb3fc", + "0x8419d28318c19ea4a179b7abb43669fe96347426ef3ac06b158d79c0acf777a09e8e770c2fb10e14b3a0421705990b23", + "0x8bacdac310e1e49660359d0a7a17fe3d334eb820e61ae25e84cb52f863a2f74cbe89c2e9fc3283745d93a99b79132354", + "0xb57ace3fa2b9f6b2db60c0d861ace7d7e657c5d35d992588aeed588c6ce3a80b6f0d49f8a26607f0b17167ab21b675e4", + "0x83e265cde477f2ecc164f49ddc7fb255bb05ff6adc347408353b7336dc3a14fdedc86d5a7fb23f36b8423248a7a67ed1", + "0xa60ada971f9f2d79d436de5d3d045f5ab05308cae3098acaf5521115134b2a40d664828bb89895840db7f7fb499edbc5", + "0xa63eea12efd89b62d3952bf0542a73890b104dd1d7ff360d4755ebfa148fd62de668edac9eeb20507967ea37fb220202", + "0xa0275767a270289adc991cc4571eff205b58ad6d3e93778ddbf95b75146d82517e8921bd0d0564e5b75fa0ccdab8e624", + "0xb9b03fd3bf07201ba3a039176a965d736b4ef7912dd9e9bf69fe1b57c330a6aa170e5521fe8be62505f3af81b41d7806", + "0xa95f640e26fb1106ced1729d6053e41a16e4896acac54992279ff873e5a969aad1dcfa10311e28b8f409ac1dab7f03bb", + "0xb144778921742418053cb3c70516c63162c187f00db2062193bb2c14031075dbe055d020cde761b26e8c58d0ea6df2c1", + "0x8432fbb799e0435ef428d4fefc309a05dd589bce74d7a87faf659823e8c9ed51d3e42603d878e80f439a38be4321c2fa", + "0xb08ddef14e42d4fd5d8bf39feb7485848f0060d43b51ed5bdda39c05fe154fb111d29719ee61a23c392141358c0cfcff", + "0x8ae3c5329a5e025b86b5370e06f5e61177df4bda075856fade20a17bfef79c92f54ed495f310130021ba94fb7c33632b", + "0x92b6d3c9444100b4d7391febfc1dddaa224651677c3695c47a289a40d7a96d200b83b64e6d9df51f534564f272a2c6c6", + "0xb432bc2a3f93d28b5e506d68527f1efeb2e2570f6be0794576e2a6ef9138926fdad8dd2eabfa979b79ab7266370e86bc", + "0x8bc315eacedbcfc462ece66a29662ca3dcd451f83de5c7626ef8712c196208fb3d8a0faf80b2e80384f0dd9772f61a23", + "0xa72375b797283f0f4266dec188678e2b2c060dfed5880fc6bb0c996b06e91a5343ea2b695adaab0a6fd183b040b46b56", + "0xa43445036fbaa414621918d6a897d3692fdae7b2961d87e2a03741360e45ebb19fcb1703d23f1e15bb1e2babcafc56ac", + "0xb9636b2ffe305e63a1a84bd44fb402442b1799bd5272638287aa87ca548649b23ce8ce7f67be077caed6aa2dbc454b78", + "0x99a30bf0921d854c282b83d438a79f615424f28c2f99d26a05201c93d10378ab2cd94a792b571ddae5d4e0c0013f4006", + "0x8648e3c2f93d70b392443be116b48a863e4b75991bab5db656a4ef3c1e7f645e8d536771dfe4e8d1ceda3be8d32978b0", + "0xab50dc9e6924c1d2e9d2e335b2d679fc7d1a7632e84964d3bac0c9fe57e85aa5906ec2e7b0399d98ddd022e9b19b5904", + "0xab729328d98d295f8f3272afaf5d8345ff54d58ff9884da14f17ecbdb7371857fdf2f3ef58080054e9874cc919b46224", + "0x83fa5da7592bd451cad3ad7702b4006332b3aae23beab4c4cb887fa6348317d234bf62a359e665b28818e5410c278a09", + "0x8bdbff566ae9d368f114858ef1f009439b3e9f4649f73efa946e678d6c781d52c69af195df0a68170f5f191b2eac286b", + "0x91245e59b4425fd4edb2a61d0d47c1ccc83d3ced8180de34887b9655b5dcda033d48cde0bdc3b7de846d246c053a02e8", + "0xa2cb00721e68f1cad8933947456f07144dc69653f96ceed845bd577d599521ba99cdc02421118971d56d7603ed118cbf", + "0xaf8cd66d303e808b22ec57860dd909ca64c27ec2c60e26ffecfdc1179d8762ffd2739d87b43959496e9fee4108df71df", + "0x9954136812dffcd5d3f167a500e7ab339c15cfc9b3398d83f64b0daa3dd5b9a851204f424a3493b4e326d3de81e50a62", + "0x93252254d12511955f1aa464883ad0da793f84d900fea83e1df8bca0f2f4cf5b5f9acbaec06a24160d33f908ab5fea38", + "0x997cb55c26996586ba436a95566bd535e9c22452ca5d2a0ded2bd175376557fa895f9f4def4519241ff386a063f2e526", + "0xa12c78ad451e0ac911260ade2927a768b50cb4125343025d43474e7f465cdc446e9f52a84609c5e7e87ae6c9b3f56cda", + "0xa789d4ca55cbba327086563831b34487d63d0980ba8cf55197c016702ed6da9b102b1f0709ce3da3c53ff925793a3d73", + "0xa5d76acbb76741ce85be0e655b99baa04f7f587347947c0a30d27f8a49ae78cce06e1cde770a8b618d3db402be1c0c4b", + "0x873c0366668c8faddb0eb7c86f485718d65f8c4734020f1a18efd5fa123d3ea8a990977fe13592cd01d17e60809cb5ff", + "0xb659b71fe70f37573ff7c5970cc095a1dc0da3973979778f80a71a347ef25ad5746b2b9608bad4ab9a4a53a4d7df42d7", + "0xa34cbe05888e5e5f024a2db14cb6dcdc401a9cbd13d73d3c37b348f68688f87c24ca790030b8f84fef9e74b4eab5e412", + "0x94ce8010f85875c045b0f014db93ef5ab9f1f6842e9a5743dce9e4cb872c94affd9e77c1f1d1ab8b8660b52345d9acb9", + "0xadefa9b27a62edc0c5b019ddd3ebf45e4de846165256cf6329331def2e088c5232456d3de470fdce3fa758bfdd387512", + "0xa6b83821ba7c1f83cc9e4529cf4903adb93b26108e3d1f20a753070db072ad5a3689643144bdd9c5ea06bb9a7a515cd0", + "0xa3a9ddedc2a1b183eb1d52de26718151744db6050f86f3580790c51d09226bf05f15111691926151ecdbef683baa992c", + "0xa64bac89e7686932cdc5670d07f0b50830e69bfb8c93791c87c7ffa4913f8da881a9d8a8ce8c1a9ce5b6079358c54136", + "0xa77b5a63452cb1320b61ab6c7c2ef9cfbcade5fd4727583751fb2bf3ea330b5ca67757ec1f517bf4d503ec924fe32fbd", + "0x8746fd8d8eb99639d8cd0ca34c0d9c3230ed5a312aab1d3d925953a17973ee5aeb66e68667e93caf9cb817c868ea8f3d", + "0x88a2462a26558fc1fbd6e31aa8abdc706190a17c27fdc4217ffd2297d1b1f3321016e5c4b2384c5454d5717dc732ed03", + "0xb78893a97e93d730c8201af2e0d3b31cb923d38dc594ffa98a714e627c473d42ea82e0c4d2eeb06862ee22a9b2c54588", + "0x920cc8b5f1297cf215a43f6fc843e379146b4229411c44c0231f6749793d40f07b9af7699fd5d21fd69400b97febe027", + "0xa0f0eafce1e098a6b58c7ad8945e297cd93aaf10bc55e32e2e32503f02e59fc1d5776936577d77c0b1162cb93b88518b", + "0x98480ba0064e97a2e7a6c4769b4d8c2a322cfc9a3b2ca2e67e9317e2ce04c6e1108169a20bd97692e1cb1f1423b14908", + "0x83dbbb2fda7e287288011764a00b8357753a6a44794cc8245a2275237f11affdc38977214e463ad67aec032f3dfa37e9", + "0x86442fff37598ce2b12015ff19b01bb8a780b40ad353d143a0f30a06f6d23afd5c2b0a1253716c855dbf445cc5dd6865", + "0xb8a4c60c5171189414887847b9ed9501bff4e4c107240f063e2d254820d2906b69ef70406c585918c4d24f1dd052142b", + "0x919f33a98e84015b2034b57b5ffe9340220926b2c6e45f86fd79ec879dbe06a148ae68b77b73bf7d01bd638a81165617", + "0x95c13e78d89474a47fbc0664f6f806744b75dede95a479bbf844db4a7f4c3ae410ec721cb6ffcd9fa9c323da5740d5ae", + "0xab7151acc41fffd8ec6e90387700bcd7e1cde291ea669567295bea1b9dd3f1df2e0f31f3588cd1a1c08af8120aca4921", + "0x80e74c5c47414bd6eeef24b6793fb1fa2d8fb397467045fcff887c52476741d5bc4ff8b6d3387cb53ad285485630537f", + "0xa296ad23995268276aa351a7764d36df3a5a3cffd7dbeddbcea6b1f77adc112629fdeffa0918b3242b3ccd5e7587e946", + "0x813d2506a28a2b01cb60f49d6bd5e63c9b056aa56946faf2f33bd4f28a8d947569cfead3ae53166fc65285740b210f86", + "0x924b265385e1646287d8c09f6c855b094daaee74b9e64a0dddcf9ad88c6979f8280ba30c8597b911ef58ddb6c67e9fe3", + "0x8d531513c70c2d3566039f7ca47cd2352fd2d55b25675a65250bdb8b06c3843db7b2d29c626eed6391c238fc651cf350", + "0x82b338181b62fdc81ceb558a6843df767b6a6e3ceedc5485664b4ea2f555904b1a45fbb35f6cf5d96f27da10df82a325", + "0x92e62faaedea83a37f314e1d3cb4faaa200178371d917938e59ac35090be1db4b4f4e0edb78b9c991de202efe4f313d8", + "0x99d645e1b642c2dc065bac9aaa0621bc648c9a8351efb6891559c3a41ba737bd155fb32d7731950514e3ecf4d75980e4", + "0xb34a13968b9e414172fb5d5ece9a39cf2eb656128c3f2f6cc7a9f0c69c6bae34f555ecc8f8837dc34b5e470e29055c78", + "0xa2a0bb7f3a0b23a2cbc6585d59f87cd7e56b2bbcb0ae48f828685edd9f7af0f5edb4c8e9718a0aaf6ef04553ba71f3b7", + "0x8e1a94bec053ed378e524b6685152d2b52d428266f2b6eadd4bcb7c4e162ed21ab3e1364879673442ee2162635b7a4d8", + "0x9944adaff14a85eab81c73f38f386701713b52513c4d4b838d58d4ffa1d17260a6d056b02334850ea9a31677c4b078bd", + "0xa450067c7eceb0854b3eca3db6cf38669d72cb7143c3a68787833cbca44f02c0be9bfbe082896f8a57debb13deb2afb1", + "0x8be4ad3ac9ef02f7df09254d569939757101ee2eda8586fefcd8c847adc1efe5bdcb963a0cafa17651befaafb376a531", + "0x90f6de91ea50255f148ac435e08cf2ac00c772a466e38155bd7e8acf9197af55662c7b5227f88589b71abe9dcf7ba343", + "0x86e5a24f0748b106dee2d4d54e14a3b0af45a96cbee69cac811a4196403ebbee17fd24946d7e7e1b962ac7f66dbaf610", + "0xafdd96fbcda7aa73bf9eeb2292e036c25753d249caee3b9c013009cc22e10d3ec29e2aa6ddbb21c4e949b0c0bccaa7f4", + "0xb5a4e7436d5473647c002120a2cb436b9b28e27ad4ebdd7c5f122b91597c507d256d0cbd889d65b3a908531936e53053", + "0xb632414c3da704d80ac2f3e5e0e9f18a3637cdc2ebeb613c29300745582427138819c4e7b0bec3099c1b8739dac1807b", + "0xa28df1464d3372ce9f37ef1db33cc010f752156afae6f76949d98cd799c0cf225c20228ae86a4da592d65f0cffe3951b", + "0x898b93d0a31f7d3f11f253cb7a102db54b669fd150da302d8354d8e02b1739a47cb9bd88015f3baf12b00b879442464e", + "0x96fb88d89a12049091070cb0048a381902965e67a8493e3991eaabe5d3b7ff7eecd5c94493a93b174df3d9b2c9511755", + "0xb899cb2176f59a5cfba3e3d346813da7a82b03417cad6342f19cc8f12f28985b03bf031e856a4743fd7ebe16324805b0", + "0xa60e2d31bc48e0c0579db15516718a03b73f5138f15037491f4dae336c904e312eda82d50862f4debd1622bb0e56d866", + "0x979fc8b987b5cef7d4f4b58b53a2c278bd25a5c0ea6f41c715142ea5ff224c707de38451b0ad3aa5e749aa219256650a", + "0xb2a75bff18e1a6b9cf2a4079572e41205741979f57e7631654a3c0fcec57c876c6df44733c9da3d863db8dff392b44a3", + "0xb7a0f0e811222c91e3df98ff7f286b750bc3b20d2083966d713a84a2281744199e664879401e77470d44e5a90f3e5181", + "0x82b74ba21c9d147fbc338730e8f1f8a6e7fc847c3110944eb17a48bea5e06eecded84595d485506d15a3e675fd0e5e62", + "0xa7f44eef817d5556f0d1abcf420301217d23c69dd2988f44d91ea1f1a16c322263cbacd0f190b9ba22b0f141b9267b4f", + "0xaadb68164ede84fc1cb3334b3194d84ba868d5a88e4c9a27519eef4923bc4abf81aab8114449496c073c2a6a0eb24114", + "0xb5378605fabe9a8c12a5dc55ef2b1de7f51aedb61960735c08767a565793cea1922a603a6983dc25f7cea738d0f7c40d", + "0xa97a4a5cd8d51302e5e670aee78fe6b5723f6cc892902bbb4f131e82ca1dfd5de820731e7e3367fb0c4c1922a02196e3", + "0x8bdfeb15c29244d4a28896f2b2cb211243cd6a1984a3f5e3b0ebe5341c419beeab3304b390a009ffb47588018034b0ea", + "0xa9af3022727f2aa2fca3b096968e97edad3f08edcbd0dbca107b892ae8f746a9c0485e0d6eb5f267999b23a845923ed0", + "0x8e7594034feef412f055590fbb15b6322dc4c6ab7a4baef4685bd13d71a83f7d682b5781bdfa0d1c659489ce9c2b8000", + "0x84977ca6c865ebee021c58106c1a4ad0c745949ecc5332948002fd09bd9b890524878d0c29da96fd11207621136421fe", + "0x8687551a79158e56b2375a271136756313122132a6670fa51f99a1b5c229ed8eea1655a734abae13228b3ebfd2a825dd", + "0xa0227d6708979d99edfc10f7d9d3719fd3fc68b0d815a7185b60307e4c9146ad2f9be2b8b4f242e320d4288ceeb9504c", + "0x89f75583a16735f9dd8b7782a130437805b34280ccea8dac6ecaee4b83fe96947e7b53598b06fecfffdf57ffc12cc445", + "0xa0056c3353227f6dd9cfc8e3399aa5a8f1d71edf25d3d64c982910f50786b1e395c508d3e3727ac360e3e040c64b5298", + "0xb070e61a6d813626144b312ded1788a6d0c7cec650a762b2f8df6e4743941dd82a2511cd956a3f141fc81e15f4e092da", + "0xb4e6db232e028a1f989bb5fc13416711f42d389f63564d60851f009dcffac01acfd54efa307aa6d4c0f932892d4e62b0", + "0x89b5991a67db90024ddd844e5e1a03ef9b943ad54194ae0a97df775dde1addf31561874f4e40fbc37a896630f3bbda58", + "0xad0e8442cb8c77d891df49cdb9efcf2b0d15ac93ec9be1ad5c3b3cca1f4647b675e79c075335c1f681d56f14dc250d76", + "0xb5d55a6ae65bb34dd8306806cb49b5ccb1c83a282ee47085cf26c4e648e19a52d9c422f65c1cd7e03ca63e926c5e92ea", + "0xb749501347e5ec07e13a79f0cb112f1b6534393458b3678a77f02ca89dca973fa7b30e55f0b25d8b92b97f6cb0120056", + "0x94144b4a3ffc5eec6ba35ce9c245c148b39372d19a928e236a60e27d7bc227d18a8cac9983851071935d8ffb64b3a34f", + "0x92bb4f9f85bc8c028a3391306603151c6896673135f8a7aefedd27acb322c04ef5dac982fc47b455d6740023e0dd3ea3", + "0xb9633a4a101461a782fc2aa092e9dbe4e2ad00987578f18cd7cf0021a909951d60fe79654eb7897806795f93c8ff4d1c", + "0x809f0196753024821b48a016eca5dbb449a7c55750f25981bb7a4b4c0e0846c09b8f6128137905055fc43a3f0deb4a74", + "0xa27dc9cdd1e78737a443570194a03d89285576d3d7f3a3cf15cc55b3013e42635d4723e2e8fe1d0b274428604b630db9", + "0x861f60f0462e04cd84924c36a28163def63e777318d00884ab8cb64c8df1df0bce5900342163edb60449296484a6c5bf", + "0xb7bc23fb4e14af4c4704a944253e760adefeca8caee0882b6bbd572c84434042236f39ae07a8f21a560f486b15d82819", + "0xb9a6eb492d6dd448654214bd01d6dc5ff12067a11537ab82023fc16167507ee25eed2c91693912f4155d1c07ed9650b3", + "0x97678af29c68f9a5e213bf0fb85c265303714482cfc4c2c00b4a1e8a76ed08834ee6af52357b143a1ca590fb0265ea5a", + "0x8a15b499e9eca5b6cac3070b5409e8296778222018ad8b53a5d1f6b70ad9bb10c68a015d105c941ed657bf3499299e33", + "0xb487fefede2e8091f2c7bfe85770db2edff1db83d4effe7f7d87bff5ab1ace35e9b823a71adfec6737fede8d67b3c467", + "0x8b51b916402aa2c437fce3bcad6dad3be8301a1a7eab9d163085b322ffb6c62abf28637636fe6114573950117fc92898", + "0xb06a2106d031a45a494adec0881cb2f82275dff9dcdd2bc16807e76f3bec28a6734edd3d54f0be8199799a78cd6228ad", + "0xaf0a185391bbe2315eb97feac98ad6dd2e5d931d012c621abd6e404a31cc188b286fef14871762190acf086482b2b5e2", + "0x8e78ee8206506dd06eb7729e32fceda3bebd8924a64e4d8621c72e36758fda3d0001af42443851d6c0aea58562870b43", + "0xa1ba52a569f0461aaf90b49b92be976c0e73ec4a2c884752ee52ffb62dd137770c985123d405dfb5de70692db454b54a", + "0x8d51b692fa1543c51f6b62b9acb8625ed94b746ef96c944ca02859a4133a5629da2e2ce84e111a7af8d9a5b836401c64", + "0xa7a20d45044cf6492e0531d0b8b26ffbae6232fa05a96ed7f06bdb64c2b0f5ca7ec59d5477038096a02579e633c7a3ff", + "0x84df867b98c53c1fcd4620fef133ee18849c78d3809d6aca0fb6f50ff993a053a455993f216c42ab6090fa5356b8d564", + "0xa7227c439f14c48e2577d5713c97a5205feb69acb0b449152842e278fa71e8046adfab468089c8b2288af1fc51fa945b", + "0x855189b3a105670779997690876dfaa512b4a25a24931a912c2f0f1936971d2882fb4d9f0b3d9daba77eaf660e9d05d5", + "0xb5696bd6706de51c502f40385f87f43040a5abf99df705d6aac74d88c913b8ecf7a99a63d7a37d9bdf3a941b9e432ff5", + "0xab997beb0d6df9c98d5b49864ef0b41a2a2f407e1687dfd6089959757ba30ed02228940b0e841afe6911990c74d536c4", + "0xb36b65f85546ebfdbe98823d5555144f96b4ab39279facd19c0de3b8919f105ba0315a0784dce4344b1bc62d8bb4a5a3", + "0xb8371f0e4450788720ac5e0f6cd3ecc5413d33895083b2c168d961ec2b5c3de411a4cc0712481cbe8df8c2fa1a7af006", + "0x98325d8026b810a8b7a114171ae59a57e8bbc9848e7c3df992efc523621729fd8c9f52114ce01d7730541a1ada6f1df1", + "0x8d0e76dbd37806259486cd9a31bc8b2306c2b95452dc395546a1042d1d17863ef7a74c636b782e214d3aa0e8d717f94a", + "0xa4e15ead76da0214d702c859fb4a8accdcdad75ed08b865842bd203391ec4cba2dcc916455e685f662923b96ee0c023f", + "0x8618190972086ebb0c4c1b4a6c94421a13f378bc961cc8267a301de7390c5e73c3333864b3b7696d81148f9d4843fd02", + "0x85369d6cc7342e1aa15b59141517d8db8baaaeb7ab9670f3ba3905353948d575923d283b7e5a05b13a30e7baf1208a86", + "0x87c51ef42233c24a6da901f28c9a075d9ba3c625687c387ad6757b72ca6b5a8885e6902a3082da7281611728b1e45f26", + "0xaa6348a4f71927a3106ad0ea8b02fc8d8c65531e4ab0bd0a17243e66f35afe252e40ab8eef9f13ae55a72566ffdaff5c", + "0x96a3bc976e9d03765cc3fee275fa05b4a84c94fed6b767e23ca689394501e96f56f7a97cffddc579a6abff632bf153be", + "0x97dbf96c6176379fdb2b888be4e757b2bca54e74124bd068d3fa1dbd82a011bbeb75079da38e0cd22a761fe208ecad9b", + "0xb70cf0a1d14089a4129ec4e295313863a59da8c7e26bf74cc0e704ed7f0ee4d7760090d0ddf7728180f1bf2c5ac64955", + "0x882d664714cc0ffe53cbc9bef21f23f3649824f423c4dbad1f893d22c4687ab29583688699efc4d5101aa08b0c3e267a", + "0x80ecb7cc963e677ccaddbe3320831dd6ee41209acf4ed41b16dc4817121a3d86a1aac9c4db3d8c08a55d28257088af32", + "0xa25ba667d832b145f9ce18c3f9b1bd00737aa36db020e1b99752c8ef7d27c6c448982bd8d352e1b6df266b8d8358a8d5", + "0x83734841c13dee12759d40bdd209b277e743b0d08cc0dd1e0b7afd2d65bfa640400eefcf6be4a52e463e5b3d885eeac6", + "0x848d16505b04804afc773aebabb51b36fd8aacfbb0e09b36c0d5d57df3c0a3b92f33e7d5ad0a7006ec46ebb91df42b8c", + "0x909a8d793f599e33bb9f1dc4792a507a97169c87cd5c087310bc05f30afcd247470b4b56dec59894c0fb1d48d39bb54e", + "0x8e558a8559df84a1ba8b244ece667f858095c50bb33a5381e60fcc6ba586b69693566d8819b4246a27287f16846c1dfa", + "0x84d6b69729f5aaa000cd710c2352087592cfbdf20d5e1166977e195818e593fa1a50d1e04566be23163a2523dc1612f1", + "0x9536d262b7a42125d89f4f32b407d737ba8d9242acfc99d965913ab3e043dcac9f7072a43708553562cac4cba841df30", + "0x9598548923ca119d6a15fd10861596601dd1dedbcccca97bb208cdc1153cf82991ea8cc17686fbaa867921065265970c", + "0xb87f2d4af6d026e4d2836bc3d390a4a18e98a6e386282ce96744603bab74974272e97ac2da281afa21885e2cbb3a8001", + "0x991ece62bf07d1a348dd22191868372904b9f8cf065ae7aa4e44fd24a53faf6d851842e35fb472895963aa1992894918", + "0xa8c53dea4c665b30e51d22ca6bc1bc78aaf172b0a48e64a1d4b93439b053877ec26cb5221c55efd64fa841bbf7d5aff4", + "0x93487ec939ed8e740f15335b58617c3f917f72d07b7a369befd479ae2554d04deb240d4a14394b26192efae4d2f4f35d", + "0xa44793ab4035443f8f2968a40e043b4555960193ffa3358d22112093aadfe2c136587e4139ffd46d91ed4107f61ea5e0", + "0xb13fe033da5f0d227c75927d3dacb06dbaf3e1322f9d5c7c009de75cdcba5e308232838785ab69a70f0bedea755e003f", + "0x970a29b075faccd0700fe60d1f726bdebf82d2cc8252f4a84543ebd3b16f91be42a75c9719a39c4096139f0f31393d58", + "0xa4c3eb1f7160f8216fc176fb244df53008ff32f2892363d85254002e66e2de21ccfe1f3b1047589abee50f29b9d507e3", + "0x8c552885eab04ba40922a8f0c3c38c96089c95ff1405258d3f1efe8d179e39e1295cbf67677894c607ae986e4e6b1fb0", + "0xb3671746fa7f848c4e2ae6946894defadd815230b906b419143523cc0597bc1d6c0a4c1e09d49b66b4a2c11cde3a4de3", + "0x937a249a95813a5e2ef428e355efd202e15a37d73e56cfb7e57ea9f943f2ce5ca8026f2f1fd25bf164ba89d07077d858", + "0x83646bdf6053a04aa9e2f112499769e5bd5d0d10f2e13db3ca89bd45c0b3b7a2d752b7d137fb3909f9c62b78166c9339", + "0xb4eac4b91e763666696811b7ed45e97fd78310377ebea1674b58a2250973f80492ac35110ed1240cd9bb2d17493d708c", + "0x82db43a99bc6573e9d92a3fd6635dbbb249ac66ba53099c3c0c8c8080b121dd8243cd5c6e36ba0a4d2525bae57f5c89c", + "0xa64d6a264a681b49d134c655d5fc7756127f1ee7c93d328820f32bca68869f53115c0d27fef35fe71f7bc4fdaed97348", + "0x8739b7a9e2b4bc1831e7f04517771bc7cde683a5e74e052542517f8375a2f64e53e0d5ac925ef722327e7bb195b4d1d9", + "0x8f337cdd29918a2493515ebb5cf702bbe8ecb23b53c6d18920cc22f519e276ca9b991d3313e2d38ae17ae8bdfa4f8b7e", + "0xb0edeab9850e193a61f138ef2739fc42ceec98f25e7e8403bfd5fa34a7bc956b9d0898250d18a69fa4625a9b3d6129da", + "0xa9920f26fe0a6d51044e623665d998745c9eca5bce12051198b88a77d728c8238f97d4196f26e43b24f8841500b998d0", + "0x86e655d61502b979eeeeb6f9a7e1d0074f936451d0a1b0d2fa4fb3225b439a3770767b649256fe481361f481a8dbc276", + "0x84d3b32fa62096831cc3bf013488a9f3f481dfe293ae209ed19585a03f7db8d961a7a9dd0db82bd7f62d612707575d9c", + "0x81c827826ec9346995ffccf62a241e3b2d32f7357acd1b1f8f7a7dbc97022d3eb51b8a1230e23ce0b401d2e535e8cd78", + "0x94a1e40c151191c5b055b21e86f32e69cbc751dcbdf759a48580951834b96a1eed75914c0d19a38aefd21fb6c8d43d0c", + "0xab890222b44bc21b71f7c75e15b6c6e16bb03371acce4f8d4353ff3b8fcd42a14026589c5ed19555a3e15e4d18bfc3a3", + "0xaccb0be851e93c6c8cc64724cdb86887eea284194b10e7a43c90528ed97e9ec71ca69c6fac13899530593756dd49eab2", + "0xb630220aa9e1829c233331413ee28c5efe94ea8ea08d0c6bfd781955078b43a4f92915257187d8526873e6c919c6a1de", + "0xadd389a4d358c585f1274b73f6c3c45b58ef8df11f9d11221f620e241bf3579fba07427b288c0c682885a700cc1fa28d", + "0xa9fe6ca8bf2961a3386e8b8dcecc29c0567b5c0b3bcf3b0f9169f88e372b80151af883871fc5229815f94f43a6f5b2b0", + "0xad839ae003b92b37ea431fa35998b46a0afc3f9c0dd54c3b3bf7a262467b13ff3c323ada1c1ae02ac7716528bdf39e3e", + "0x9356d3fd0edcbbb65713c0f2a214394f831b26f792124b08c5f26e7f734b8711a87b7c4623408da6a091c9aef1f6af3c", + "0x896b25b083c35ac67f0af3784a6a82435b0e27433d4d74cd6d1eafe11e6827827799490fb1c77c11de25f0d75f14e047", + "0x8bfa019391c9627e8e5f05c213db625f0f1e51ec68816455f876c7e55b8f17a4f13e5aae9e3fb9e1cf920b1402ee2b40", + "0x8ba3a6faa6a860a8f3ce1e884aa8769ceded86380a86520ab177ab83043d380a4f535fe13884346c5e51bee68da6ab41", + "0xa8292d0844084e4e3bb7af92b1989f841a46640288c5b220fecfad063ee94e86e13d3d08038ec2ac82f41c96a3bfe14d", + "0x8229bb030b2fc566e11fd33c7eab7a1bb7b49fed872ea1f815004f7398cb03b85ea14e310ec19e1f23e0bdaf60f8f76c", + "0x8cfbf869ade3ec551562ff7f63c2745cc3a1f4d4dc853a0cd42dd5f6fe54228f86195ea8fe217643b32e9f513f34a545", + "0xac52a3c8d3270ddfe1b5630159da9290a5ccf9ccbdef43b58fc0a191a6c03b8a5974cf6e2bbc7bd98d4a40a3581482d7", + "0xab13decb9e2669e33a7049b8eca3ca327c40dea15ad6e0e7fa63ed506db1d258bc36ac88b35f65cae0984e937eb6575d", + "0xb5e748eb1a7a1e274ff0cc56311c198f2c076fe4b7e73e5f80396fe85358549df906584e6bb2c8195b3e2be7736850a5", + "0xb5cb911325d8f963c41f691a60c37831c7d3bbd92736efa33d1f77a22b3fde7f283127256c2f47e197571e6fe0b46149", + "0x8a01dc6ed1b55f26427a014faa347130738b191a06b800e32042a46c13f60b49534520214359d68eb2e170c31e2b8672", + "0xa72fa874866e19b2efb8e069328362bf7921ec375e3bcd6b1619384c3f7ee980f6cf686f3544e9374ff54b4d17a1629c", + "0x8db21092f7c5f110fba63650b119e82f4b42a997095d65f08f8237b02dd66fdf959f788df2c35124db1dbd330a235671", + "0x8c65d50433d9954fe28a09fa7ba91a70a590fe7ba6b3060f5e4be0f6cef860b9897fa935fb4ebc42133524eb071dd169", + "0xb4614058e8fa21138fc5e4592623e78b8982ed72aa35ee4391b164f00c68d277fa9f9eba2eeefc890b4e86eba5124591", + "0xab2ad3a1bce2fbd55ca6b7c23786171fe1440a97d99d6df4d80d07dd56ac2d7203c294b32fc9e10a6c259381a73f24a1", + "0x812ae3315fdc18774a8da3713a4679e8ed10b9405edc548c00cacbe25a587d32040566676f135e4723c5dc25df5a22e9", + "0xa464b75f95d01e5655b54730334f443c8ff27c3cb79ec7af4b2f9da3c2039c609908cd128572e1fd0552eb597e8cef8d", + "0xa0db3172e93ca5138fe419e1c49a1925140999f6eff7c593e5681951ee0ec1c7e454c851782cbd2b8c9bc90d466e90e0", + "0x806db23ba7d00b87d544eed926b3443f5f9c60da6b41b1c489fba8f73593b6e3b46ebfcab671ee009396cd77d5e68aa1", + "0x8bfdf2c0044cc80260994e1c0374588b6653947b178e8b312be5c2a05e05767e98ea15077278506aee7df4fee1aaf89e", + "0x827f6558c16841b5592ff089c9c31e31eb03097623524394813a2e4093ad2d3f8f845504e2af92195aaa8a1679d8d692", + "0x925c4f8eab2531135cd71a4ec88e7035b5eea34ba9d799c5898856080256b4a15ed1a746e002552e2a86c9c157e22e83", + "0xa9f9a368f0e0b24d00a35b325964c85b69533013f9c2cfad9708be5fb87ff455210f8cb8d2ce3ba58ca3f27495552899", + "0x8ac0d3bebc1cae534024187e7c71f8927ba8fcc6a1926cb61c2b6c8f26bb7831019e635a376146c29872a506784a4aaa", + "0x97c577be2cbbfdb37ad754fae9df2ada5fc5889869efc7e18a13f8e502fbf3f4067a509efbd46fd990ab47ce9a70f5a8", + "0x935e7d82bca19f16614aa43b4a3474e4d20d064e4bfdf1cea2909e5c9ab72cfe3e54dc50030e41ee84f3588cebc524e9", + "0x941aafc08f7c0d94cebfbb1f0aad5202c02e6e37f2c12614f57e727efa275f3926348f567107ee6d8914dd71e6060271", + "0xaf0fbc1ba05b4b5b63399686df3619968be5d40073de0313cbf5f913d3d4b518d4c249cdd2176468ccaa36040a484f58", + "0xa0c414f23f46ca6d69ce74c6f8a00c036cb0edd098af0c1a7d39c802b52cfb2d5dbdf93fb0295453d4646e2af7954d45", + "0x909cf39e11b3875bb63b39687ae1b5d1f5a15445e39bf164a0b14691b4ddb39a8e4363f584ef42213616abc4785b5d66", + "0xa92bac085d1194fbd1c88299f07a061d0bdd3f980b663e81e6254dbb288bf11478c0ee880e28e01560f12c5ccb3c0103", + "0x841705cd5cd76b943e2b7c5e845b9dd3c8defe8ef67e93078d6d5e67ade33ad4b0fd413bc196f93b0a4073c855cd97d4", + "0x8e7eb8364f384a9161e81d3f1d52ceca9b65536ae49cc35b48c3e2236322ba4ae9973e0840802d9fa4f4d82ea833544f", + "0xaed3ab927548bc8bec31467ba80689c71a168e34f50dcb6892f19a33a099f5aa6b3f9cb79f5c0699e837b9a8c7f27efe", + "0xb8fbf7696210a36e20edabd77839f4dfdf50d6d015cdf81d587f90284a9bcef7d2a1ff520728d7cc69a4843d6c20dedd", + "0xa9d533769ce6830211c884ae50a82a7bf259b44ac71f9fb11f0296fdb3981e6b4c1753fe744647b247ebc433a5a61436", + "0x8b4bdf90d33360b7f428c71cde0a49fb733badba8c726876945f58c620ce7768ae0e98fc8c31fa59d8955a4823336bb1", + "0x808d42238e440e6571c59e52a35ae32547d502dc24fd1759d8ea70a7231a95859baf30b490a4ba55fa2f3aaa11204597", + "0x85594701f1d2fee6dc1956bc44c7b31db93bdeec2f3a7d622c1a08b26994760773e3d57521a44cfd7e407ac3fd430429", + "0xa66de045ce7173043a6825e9dc440ac957e2efb6df0a337f4f8003eb0c719d873a52e6eba3cb0d69d977ca37d9187674", + "0x87a1c6a1fdff993fa51efa5c3ba034c079c0928a7d599b906336af7c2dcab9721ceaf3108c646490af9dff9a754f54b3", + "0x926424223e462ceb75aed7c22ade8a7911a903b7e5dd4bc49746ddce8657f4616325cd12667d4393ac52cdd866396d0e", + "0xb5dc96106593b42b30f06f0b0a1e0c1aafc70432e31807252d3674f0b1ea5e58eac8424879d655c9488d85a879a3e572", + "0x997ca0987735cc716507cb0124b1d266d218b40c9d8e0ecbf26a1d65719c82a637ce7e8be4b4815d307df717bde7c72a", + "0x92994d3f57a569b7760324bb5ae4e8e14e1633d175dab06aa57b8e391540e05f662fdc08b8830f489a063f59b689a688", + "0xa8087fcc6aa4642cb998bea11facfe87eb33b90a9aa428ab86a4124ad032fc7d2e57795311a54ec9f55cc120ebe42df1", + "0xa9bd7d1de6c0706052ca0b362e2e70e8c8f70f1f026ea189b4f87a08ce810297ebfe781cc8004430776c54c1a05ae90c", + "0x856d33282e8a8e33a3d237fb0a0cbabaf77ba9edf2fa35a831fdafcadf620561846aa6cbb6bdc5e681118e1245834165", + "0x9524a7aa8e97a31a6958439c5f3339b19370f03e86b89b1d02d87e4887309dbbe9a3a8d2befd3b7ed5143c8da7e0a8ad", + "0x824fdf433e090f8acbd258ac7429b21f36f9f3b337c6d0b71d1416a5c88a767883e255b2888b7c906dd2e9560c4af24c", + "0x88c7fee662ca7844f42ed5527996b35723abffd0d22d4ca203b9452c639a5066031207a5ae763dbc0865b3299d19b1ec", + "0x919dca5c5595082c221d5ab3a5bc230f45da7f6dec4eb389371e142c1b9c6a2c919074842479c2844b72c0d806170c0c", + "0xb939be8175715e55a684578d8be3ceff3087f60fa875fff48e52a6e6e9979c955efef8ff67cfa2b79499ea23778e33b0", + "0x873b6db725e7397d11bc9bed9ac4468e36619135be686790a79bc6ed4249058f1387c9a802ea86499f692cf635851066", + "0xaeae06db3ec47e9e5647323fa02fac44e06e59b885ad8506bf71b184ab3895510c82f78b6b22a5d978e8218e7f761e9f", + "0xb99c0a8359c72ab88448bae45d4bf98797a26bca48b0d4460cd6cf65a4e8c3dd823970ac3eb774ae5d0cea4e7fadf33e", + "0x8f10c8ec41cdfb986a1647463076a533e6b0eec08520c1562401b36bb063ac972aa6b28a0b6ce717254e35940b900e3c", + "0xa106d9be199636d7add43b942290269351578500d8245d4aae4c083954e4f27f64740a3138a66230391f2d0e6043a8de", + "0xa469997908244578e8909ff57cffc070f1dbd86f0098df3cfeb46b7a085cfecc93dc69ee7cad90ff1dc5a34d50fe580c", + "0xa4ef087bea9c20eb0afc0ee4caba7a9d29dfa872137828c721391273e402fb6714afc80c40e98bbd8276d3836bffa080", + "0xb07a013f73cd5b98dae0d0f9c1c0f35bff8a9f019975c4e1499e9bee736ca6fcd504f9bc32df1655ff333062382cff04", + "0xb0a77188673e87cc83348c4cc5db1eecf6b5184e236220c8eeed7585e4b928db849944a76ec60ef7708ef6dac02d5592", + "0xb1284b37e59b529f0084c0dacf0af6c0b91fc0f387bf649a8c74819debf606f7b07fc3e572500016fb145ec2b24e9f17", + "0x97b20b5b4d6b9129da185adfbf0d3d0b0faeba5b9715f10299e48ea0521709a8296a9264ce77c275a59c012b50b6519a", + "0xb9d37e946fae5e4d65c1fbfacc8a62e445a1c9d0f882e60cca649125af303b3b23af53c81d7bac544fb7fcfc7a314665", + "0x8e5acaac379f4bb0127efbef26180f91ff60e4c525bc9b798fc50dfaf4fe8a5aa84f18f3d3cfb8baead7d1e0499af753", + "0xb0c0b8ab1235bf1cda43d4152e71efc1a06c548edb964eb4afceb201c8af24240bf8ab5cae30a08604e77432b0a5faf0", + "0x8cc28d75d5c8d062d649cbc218e31c4d327e067e6dbd737ec0a35c91db44fbbd0d40ec424f5ed79814add16947417572", + "0x95ae6219e9fd47efaa9cb088753df06bc101405ba50a179d7c9f7c85679e182d3033f35b00dbba71fdcd186cd775c52e", + "0xb5d28fa09f186ebc5aa37453c9b4d9474a7997b8ae92748ecb940c14868792292ac7d10ade01e2f8069242b308cf97e5", + "0x8c922a0faa14cc6b7221f302df3342f38fc8521ec6c653f2587890192732c6da289777a6cd310747ea7b7d104af95995", + "0xb9ad5f660b65230de54de535d4c0fcae5bc6b59db21dea5500fdc12eea4470fb8ea003690fdd16d052523418d5e01e8c", + "0xa39a9dd41a0ff78c82979483731f1cd68d3921c3e9965869662c22e02dde3877802e180ba93f06e7346f96d9fa9261d2", + "0x8b32875977ec372c583b24234c27ed73aef00cdff61eb3c3776e073afbdeade548de9497c32ec6d703ff8ad0a5cb7fe4", + "0x9644cbe755a5642fe9d26cfecf170d3164f1848c2c2e271d5b6574a01755f3980b3fc870b98cf8528fef6ecef4210c16", + "0x81ea9d1fdd9dd66d60f40ce0712764b99da9448ae0b300f8324e1c52f154e472a086dda840cb2e0b9813dc8ce8afd4b5", + "0x906aaa4a7a7cdf01909c5cfbc7ded2abc4b869213cbf7c922d4171a4f2e637e56f17020b852ad339d83b8ac92f111666", + "0x939b5f11acbdeff998f2a080393033c9b9d8d5c70912ea651c53815c572d36ee822a98d6dfffb2e339f29201264f2cf4", + "0xaba4898bf1ccea9b9e2df1ff19001e05891581659c1cbbde7ee76c349c7fc7857261d9785823c9463a8aea3f40e86b38", + "0x83ca1a56b8a0be4820bdb5a9346357c68f9772e43f0b887729a50d2eb2a326bbcede676c8bf2e51d7c89bbd8fdb778a6", + "0x94e86e9fe6addfe2c3ee3a547267ed921f4230d877a85bb4442c2d9350c2fa9a9c54e6fe662de82d1a2407e4ab1691c2", + "0xa0cc3bdef671a59d77c6984338b023fa2b431b32e9ed2abe80484d73edc6540979d6f10812ecc06d4d0c5d4eaca7183c", + "0xb5343413c1b5776b55ea3c7cdd1f3af1f6bd802ea95effe3f2b91a523817719d2ecc3f8d5f3cc2623ace7e35f99ca967", + "0x92085d1ed0ed28d8cabe3e7ff1905ed52c7ceb1eac5503760c52fb5ee3a726aba7c90b483c032acc3f166b083d7ec370", + "0x8ec679520455275cd957fca8122724d287db5df7d29f1702a322879b127bff215e5b71d9c191901465d19c86c8d8d404", + "0xb65eb2c63d8a30332eb24ee8a0c70156fc89325ebbb38bacac7cf3f8636ad8a472d81ccca80423772abc00192d886d8a", + "0xa9fe1c060b974bee4d590f2873b28635b61bfcf614e61ff88b1be3eee4320f4874e21e8d666d8ac8c9aba672efc6ecae", + "0xb3fe2a9a389c006a831dea7e777062df84b5c2803f9574d7fbe10b7e1c125817986af8b6454d6be9d931a5ac94cfe963", + "0x95418ad13b734b6f0d33822d9912c4c49b558f68d08c1b34a0127fcfa666bcae8e6fda8832d2c75bb9170794a20e4d7c", + "0xa9a7df761e7f18b79494bf429572140c8c6e9d456c4d4e336184f3f51525a65eb9582bea1e601bdb6ef8150b7ca736a5", + "0xa0de03b1e75edf7998c8c1ac69b4a1544a6fa675a1941950297917366682e5644a4bda9cdeedfaf9473d7fccd9080b0c", + "0xa61838af8d95c95edf32663a68f007d95167bf6e41b0c784a30b22d8300cfdd5703bd6d16e86396638f6db6ae7e42a85", + "0x8866d62084d905c145ff2d41025299d8b702ac1814a7dec4e277412c161bc9a62fed735536789cb43c88693c6b423882", + "0x91da22c378c81497fe363e7f695c0268443abee50f8a6625b8a41e865638a643f07b157ee566de09ba09846934b4e2d7", + "0x941d21dd57c9496aa68f0c0c05507405fdd413acb59bc668ce7e92e1936c68ec4b065c3c30123319884149e88228f0b2", + "0xa77af9b094bc26966ddf2bf9e1520c898194a5ccb694915950dadc204facbe3066d3d89f50972642d76b14884cfbaa21", + "0x8e76162932346869f4618bde744647f7ab52ab498ad654bdf2a4feeb986ac6e51370841e5acbb589e38b6e7142bb3049", + "0xb60979ace17d6937ece72e4f015da4657a443dd01cebc7143ef11c09e42d4aa8855999a65a79e2ea0067f31c9fc2ab0f", + "0xb3e2ffdd5ee6fd110b982fd4fad4b93d0fca65478f986d086eeccb0804960bfaa1919afa743c2239973ea65091fe57d2", + "0x8ce0ce05e7d7160d44574011da687454dbd3c8b8290aa671731b066e2c82f8cf2d63cb8e932d78c6122ec610e44660e6", + "0xab005dd8d297045c39e2f72fb1c48edb501ccf3575d3d04b9817b3afee3f0bb0f3f53f64bda37d1d9cde545aae999bae", + "0x95bd7edb4c4cd60e3cb8a72558845a3cce6bb7032ccdf33d5a49ebb6ddf203bc3c79e7b7e550735d2d75b04c8b2441e8", + "0x889953ee256206284094e4735dbbb17975bafc7c3cb94c9fbfee4c3e653857bfd49e818f64a47567f721b98411a3b454", + "0xb188423e707640ab0e75a061e0b62830cde8afab8e1ad3dae30db69ffae4e2fc005bababbdcbd7213b918ed4f70e0c14", + "0xa97e0fafe011abd70d4f99a0b36638b3d6e7354284588f17a88970ed48f348f88392779e9a038c6cbc9208d998485072", + "0x87db11014a91cb9b63e8dfaa82cdebca98272d89eb445ee1e3ff9dbaf2b3fad1a03b888cffc128e4fe208ed0dddece0f", + "0xaad2e40364edd905d66ea4ac9d51f9640d6fda9a54957d26ba233809851529b32c85660fa401dbee3679ec54fa6dd966", + "0x863e99336ca6edf03a5a259e59a2d0f308206e8a2fb320cfc0be06057366df8e0f94b33a28f574092736b3c5ada84270", + "0xb34bcc56a057589f34939a1adc51de4ff6a9f4fee9c7fa9aa131e28d0cf0759a0c871b640162acdfbf91f3f1b59a3703", + "0x935dd28f2896092995c5eff1618e5b6efe7a40178888d7826da9b0503c2d6e68a28e7fac1a334e166d0205f0695ef614", + "0xb842cd5f8f5de5ca6c68cb4a5c1d7b451984930eb4cc18fd0934d52fdc9c3d2d451b1c395594d73bc3451432bfba653f", + "0x9014537885ce2debad736bc1926b25fdab9f69b216bf024f589c49dc7e6478c71d595c3647c9f65ff980b14f4bb2283b", + "0x8e827ccca1dd4cd21707140d10703177d722be0bbe5cac578db26f1ef8ad2909103af3c601a53795435b27bf95d0c9ed", + "0x8a0b8ad4d466c09d4f1e9167410dbe2edc6e0e6229d4b3036d30f85eb6a333a18b1c968f6ca6d6889bb08fecde017ef4", + "0x9241ee66c0191b06266332dc9161dede384c4bb4e116dbd0890f3c3790ec5566da4568243665c4725b718ac0f6b5c179", + "0xaeb4d5fad81d2b505d47958a08262b6f1b1de9373c2c9ba6362594194dea3e002ab03b8cbb43f867be83065d3d370f19", + "0x8781bc83bb73f7760628629fe19e4714b494dbed444c4e4e4729b7f6a8d12ee347841a199888794c2234f51fa26fc2b9", + "0xb58864f0acd1c2afa29367e637cbde1968d18589245d9936c9a489c6c495f54f0113ecdcbe4680ac085dd3c397c4d0c3", + "0x94a24284afaeead61e70f3e30f87248d76e9726759445ca18cdb9360586c60cc9f0ec1c397f9675083e0b56459784e2e", + "0xaed358853f2b54dcbddf865e1816c2e89be12e940e1abfa661e2ee63ffc24a8c8096be2072fa83556482c0d89e975124", + "0xb95374e6b4fc0765708e370bc881e271abf2e35c08b056a03b847e089831ef4fe3124b9c5849d9c276eb2e35b3daf264", + "0xb834cdbcfb24c8f84bfa4c552e7fadc0028a140952fd69ed13a516e1314a4cd35d4b954a77d51a1b93e1f5d657d0315d", + "0x8fb6d09d23bfa90e7443753d45a918d91d75d8e12ec7d016c0dfe94e5c592ba6aaf483d2f16108d190822d955ad9cdc3", + "0xaa315cd3c60247a6ad4b04f26c5404c2713b95972843e4b87b5a36a89f201667d70f0adf20757ebe1de1b29ae27dda50", + "0xa116862dca409db8beff5b1ccd6301cdd0c92ca29a3d6d20eb8b87f25965f42699ca66974dd1a355200157476b998f3b", + "0xb4c2f5fe173c4dc8311b60d04a65ce1be87f070ac42e13cd19c6559a2931c6ee104859cc2520edebbc66a13dc7d30693", + "0x8d4a02bf99b2260c334e7d81775c5cf582b00b0c982ce7745e5a90624919028278f5e9b098573bad5515ce7fa92a80c8", + "0x8543493bf564ce6d97bd23be9bff1aba08bd5821ca834f311a26c9139c92a48f0c2d9dfe645afa95fec07d675d1fd53b", + "0x9344239d13fde08f98cb48f1f87d34cf6abe8faecd0b682955382a975e6eed64e863fa19043290c0736261622e00045c", + "0xaa49d0518f343005ca72b9e6c7dcaa97225ce6bb8b908ebbe7b1a22884ff8bfb090890364e325a0d414ad180b8f161d1", + "0x907d7fd3e009355ab326847c4a2431f688627faa698c13c03ffdd476ecf988678407f029b8543a475dcb3dafdf2e7a9c", + "0x845f1f10c6c5dad2adc7935f5cd2e2b32f169a99091d4f1b05babe7317b9b1cdce29b5e62f947dc621b9acbfe517a258", + "0x8f3be8e3b380ea6cdf9e9c237f5e88fd5a357e5ded80ea1fc2019810814de82501273b4da38916881125b6fa0cfd4459", + "0xb9c7f487c089bf1d20c822e579628db91ed9c82d6ca652983aa16d98b4270c4da19757f216a71b9c13ddee3e6e43705f", + "0x8ba2d8c88ad2b872db104ea8ddbb006ec2f3749fd0e19298a804bb3a5d94de19285cc7fb19fee58a66f7851d1a66c39f", + "0x9375ecd3ed16786fe161af5d5c908f56eeb467a144d3bbddfc767e90065b7c94fc53431adebecba2b6c9b5821184d36e", + "0xa49e069bfadb1e2e8bff6a4286872e2a9765d62f0eaa4fcb0e5af4bbbed8be3510fb19849125a40a8a81d1e33e81c3eb", + "0x9522cc66757b386aa6b88619525c8ce47a5c346d590bb3647d12f991e6c65c3ab3c0cfc28f0726b6756c892eae1672be", + "0xa9a0f1f51ff877406fa83a807aeb17b92a283879f447b8a2159653db577848cc451cbadd01f70441e351e9ed433c18bc", + "0x8ff7533dcff6be8714df573e33f82cf8e9f2bcaaa43e939c4759d52b754e502717950de4b4252fb904560fc31dce94a4", + "0x959724671e265a28d67c29d95210e97b894b360da55e4cf16e6682e7912491ed8ca14bfaa4dce9c25a25b16af580494f", + "0x92566730c3002f4046c737032487d0833c971e775de59fe02d9835c9858e2e3bc37f157424a69764596c625c482a2219", + "0xa84b47ceff13ed9c3e5e9cdf6739a66d3e7c2bd8a6ba318fefb1a9aecf653bb2981da6733ddb33c4b0a4523acc429d23", + "0xb4ddf571317e44f859386d6140828a42cf94994e2f1dcbcc9777f4eebbfc64fc1e160b49379acc27c4672b8e41835c5d", + "0x8ab95c94072b853d1603fdd0a43b30db617d13c1d1255b99075198e1947bfa5f59aed2b1147548a1b5e986cd9173d15c", + "0x89511f2eab33894fd4b3753d24249f410ff7263052c1fef6166fc63a79816656b0d24c529e45ccce6be28de6e375d916", + "0xa0866160ca63d4f2be1b4ea050dac6b59db554e2ebb4e5b592859d8df339b46fd7cb89aaed0951c3ee540aee982c238a", + "0x8fcc5cbba1b94970f5ff2eb1922322f5b0aa7d918d4b380c9e7abfd57afd8b247c346bff7b87af82efbce3052511cd1b", + "0x99aeb2a5e846b0a2874cca02c66ed40d5569eb65ab2495bc3f964a092e91e1517941f2688e79f8cca49cd3674c4e06dc", + "0xb7a096dc3bad5ca49bee94efd884aa3ff5615cf3825cf95fbe0ce132e35f46581d6482fa82666c7ef5f1643eaee8f1ca", + "0x94393b1da6eaac2ffd186b7725eca582f1ddc8cdd916004657f8a564a7c588175cb443fc6943b39029f5bbe0add3fad8", + "0x884b85fe012ccbcd849cb68c3ad832d83b3ef1c40c3954ffdc97f103b1ed582c801e1a41d9950f6bddc1d11f19d5ec76", + "0xb00061c00131eded8305a7ce76362163deb33596569afb46fe499a7c9d7a0734c084d336b38d168024c2bb42b58e7660", + "0xa439153ac8e6ca037381e3240e7ba08d056c83d7090f16ed538df25901835e09e27de2073646e7d7f3c65056af6e4ce7", + "0x830fc9ca099097d1f38b90e6843dc86f702be9d20bdacc3e52cae659dc41df5b8d2c970effa6f83a5229b0244a86fe22", + "0xb81ea2ffaaff2bb00dd59a9ab825ba5eed4db0d8ac9c8ed1a632ce8f086328a1cddd045fbe1ace289083c1325881b7e7", + "0xb51ea03c58daf2db32c99b9c4789b183365168cb5019c72c4cc91ac30b5fb7311d3db76e6fa41b7cd4a8c81e2f6cdc94", + "0xa4170b2c6d09ca5beb08318730419b6f19215ce6c631c854116f904be3bc30dd85a80c946a8ab054d3e307afaa3f8fbc", + "0x897cc42ff28971ff54d2a55dd6b35cfb8610ac902f3c06e3a5cea0e0a257e870c471236a8e84709211c742a09c5601a6", + "0xa18f2e98d389dace36641621488664ecbb422088ab03b74e67009b8b8acacaaa24fdcf42093935f355207d934adc52a8", + "0x92adcfb678cc2ba19c866f3f2b988fdcb4610567f3ab436cc0cb9acaf5a88414848d71133ebdbec1983e38e6190f1b5f", + "0xa86d43c2ce01b366330d3b36b3ca85f000c3548b8297e48478da1ee7d70d8576d4650cba7852ed125c0d7cb6109aa7f3", + "0x8ed31ceed9445437d7732dce78a762d72ff32a7636bfb3fd7974b7ae15db414d8184a1766915244355deb354fbc5803b", + "0x9268f70032584f416e92225d65af9ea18c466ebc7ae30952d56a4e36fd9ea811dde0a126da9220ba3c596ec54d8a335e", + "0x9433b99ee94f2d3fbdd63b163a2bdf440379334c52308bd24537f7defd807145a062ff255a50d119a7f29f4b85d250e3", + "0x90ce664f5e4628a02278f5cf5060d1a34f123854634b1870906e5723ac9afd044d48289be283b267d45fcbf3f4656aaf", + "0xaaf21c4d59378bb835d42ae5c5e5ab7a3c8c36a59e75997989313197752b79a472d866a23683b329ea69b048b87fa13e", + "0xb83c0589b304cec9ede549fde54f8a7c2a468c6657da8c02169a6351605261202610b2055c639b9ed2d5b8c401fb8f56", + "0x9370f326ea0f170c2c05fe2c5a49189f20aec93b6b18a5572a818cd4c2a6adb359e68975557b349fb54f065d572f4c92", + "0xac3232fa5ce6f03fca238bef1ce902432a90b8afce1c85457a6bee5571c033d4bceefafc863af04d4e85ac72a4d94d51", + "0x80d9ea168ff821b22c30e93e4c7960ce3ad3c1e6deeebedd342a36d01bd942419b187e2f382dbfd8caa34cca08d06a48", + "0xa387a3c61676fb3381eefa2a45d82625635a666e999aba30e3b037ec9e040f414f9e1ad9652abd3bcad63f95d85038db", + "0xa1b229fe32121e0b391b0f6e0180670b9dc89d79f7337de4c77ea7ad0073e9593846f06797c20e923092a08263204416", + "0x92164a9d841a2b828cedf2511213268b698520f8d1285852186644e9a0c97512cafa4bfbe29af892c929ebccd102e998", + "0x82ee2fa56308a67c7db4fd7ef539b5a9f26a1c2cc36da8c3206ba4b08258fbb3cec6fe5cdbd111433fb1ba2a1e275927", + "0x8c77bfe9e191f190a49d46f05600603fa42345592539b82923388d72392404e0b29a493a15e75e8b068dddcd444c2928", + "0x80b927f93ccf79dcf5c5b20bcf5a7d91d7a17bc0401bb7cc9b53a6797feac31026eb114257621f5a64a52876e4474cc1", + "0xb6b68b6501c37804d4833d5a063dd108a46310b1400549074e3cac84acc6d88f73948b7ad48d686de89c1ec043ae8c1a", + "0xab3da00f9bdc13e3f77624f58a3a18fc3728956f84b5b549d62f1033ae4b300538e53896e2d943f160618e05af265117", + "0xb6830e87233b8eace65327fdc764159645b75d2fd4024bf8f313b2dd5f45617d7ecfb4a0b53ccafb5429815a9a1adde6", + "0xb9251cfe32a6dc0440615aadcd98b6b1b46e3f4e44324e8f5142912b597ee3526bea2431e2b0282bb58f71be5b63f65e", + "0xaf8d70711e81cdddfb39e67a1b76643292652584c1ce7ce4feb1641431ad596e75c9120e85f1a341e7a4da920a9cdd94", + "0x98cd4e996594e89495c078bfd52a4586b932c50a449a7c8dfdd16043ca4cda94dafbaa8ad1b44249c99bbcc52152506e", + "0xb9fc6d1c24f48404a4a64fbe3e43342738797905db46e4132aee5f086aaa4c704918ad508aaefa455cfe1b36572e6242", + "0xa365e871d30ba9291cedaba1be7b04e968905d003e9e1af7e3b55c5eb048818ae5b913514fb08b24fb4fbdccbb35d0b8", + "0x93bf99510971ea9af9f1e364f1234c898380677c8e8de9b0dd24432760164e46c787bc9ec42a7ad450500706cf247b2d", + "0xb872f825a5b6e7b9c7a9ddfeded3516f0b1449acc9b4fd29fc6eba162051c17416a31e5be6d3563f424d28e65bab8b8f", + "0xb06b780e5a5e8eb4f4c9dc040f749cf9709c8a4c9ef15e925f442b696e41e5095db0778a6c73bcd329b265f2c6955c8b", + "0x848f1a981f5fc6cd9180cdddb8d032ad32cdfa614fc750d690dbae36cc0cd355cbf1574af9b3ffc8b878f1b2fafb9544", + "0xa03f48cbff3e9e8a3a655578051a5ae37567433093ac500ed0021c6250a51b767afac9bdb194ee1e3eac38a08c0eaf45", + "0xb5be78ce638ff8c4aa84352b536628231d3f7558c5be3bf010b28feac3022e64691fa672f358c8b663904aebe24a54ed", + "0xa9d4da70ff676fa55d1728ba6ab03b471fa38b08854d99e985d88c2d050102d8ccffbe1c90249a5607fa7520b15fe791", + "0x8fe9f7092ffb0b69862c8e972fb1ecf54308c96d41354ed0569638bb0364f1749838d6d32051fff1599112978c6e229c", + "0xae6083e95f37770ecae0df1e010456f165d96cfe9a7278c85c15cffd61034081ce5723e25e2bede719dc9341ec8ed481", + "0xa260891891103089a7afbd9081ea116cfd596fd1015f5b65e10b0961eb37fab7d09c69b7ce4be8bf35e4131848fb3fe4", + "0x8d729fa32f6eb9fd2f6a140bef34e8299a2f3111bffd0fe463aa8622c9d98bfd31a1df3f3e87cd5abc52a595f96b970e", + "0xa30ec6047ae4bc7da4daa7f4c28c93aedb1112cfe240e681d07e1a183782c9ff6783ac077c155af23c69643b712a533f", + "0xac830726544bfe7b5467339e5114c1a75f2a2a8d89453ce86115e6a789387e23551cd64620ead6283dfa4538eb313d86", + "0x8445c135b7a48068d8ed3e011c6d818cfe462b445095e2fbf940301e50ded23f272d799eea47683fc027430ce14613ef", + "0x95785411715c9ae9d8293ce16a693a2aa83e3cb1b4aa9f76333d0da2bf00c55f65e21e42e50e6c5772ce213dd7b4f7a0", + "0xb273b024fa18b7568c0d1c4d2f0c4e79ec509dafac8c5951f14192d63ddbcf2d8a7512c1c1b615cc38fa3e336618e0c5", + "0xa78b9d3ea4b6a90572eb27956f411f1d105fdb577ee2ffeec9f221da9b45db84bfe866af1f29597220c75e0c37a628d8", + "0xa4be2bf058c36699c41513c4d667681ce161a437c09d81383244fc55e1c44e8b1363439d0cce90a3e44581fb31d49493", + "0xb6eef13040f17dd4eba22aaf284d2f988a4a0c4605db44b8d2f4bf9567ac794550b543cc513c5f3e2820242dd704152e", + "0x87eb00489071fa95d008c5244b88e317a3454652dcb1c441213aa16b28cd3ecaa9b22fec0bdd483c1df71c37119100b1", + "0x92d388acdcb49793afca329cd06e645544d2269234e8b0b27d2818c809c21726bc9cf725651b951e358a63c83dedee24", + "0xae27e219277a73030da27ab5603c72c8bd81b6224b7e488d7193806a41343dff2456132274991a4722fdb0ef265d04cd", + "0x97583e08ecb82bbc27c0c8476d710389fa9ffbead5c43001bd36c1b018f29faa98de778644883e51870b69c5ffb558b5", + "0x90a799a8ce73387599babf6b7da12767c0591cadd36c20a7990e7c05ea1aa2b9645654ec65308ee008816623a2757a6a", + "0xa1b47841a0a2b06efd9ab8c111309cc5fc9e1d5896b3e42ed531f6057e5ade8977c29831ce08dbda40348386b1dcc06d", + "0xb92b8ef59bbddb50c9457691bc023d63dfcc54e0fd88bd5d27a09e0d98ac290fc90e6a8f6b88492043bf7c87fac8f3e4", + "0xa9d6240b07d62e22ec8ab9b1f6007c975a77b7320f02504fc7c468b4ee9cfcfd945456ff0128bc0ef2174d9e09333f8d", + "0x8e96534c94693226dc32bca79a595ca6de503af635f802e86442c67e77564829756961d9b701187fe91318da515bf0e6", + "0xb6ba290623cd8dd5c2f50931c0045d1cfb0c30877bc8fe58cbc3ff61ee8da100045a39153916efa1936f4aee0892b473", + "0xb43baa7717fac02d4294f5b3bb5e58a65b3557747e3188b482410388daac7a9c177f762d943fd5dcf871273921213da8", + "0xb9cf00f8fb5e2ef2b836659fece15e735060b2ea39b8e901d3dcbdcf612be8bf82d013833718c04cd46ffaa70b85f42e", + "0x8017d0c57419e414cbba504368723e751ef990cc6f05dad7b3c2de6360adc774ad95512875ab8337d110bf39a42026fa", + "0xae7401048b838c0dcd4b26bb6c56d79d51964a0daba780970b6c97daee4ea45854ea0ac0e4139b3fe60dac189f84df65", + "0x887b237b0cd0f816b749b21db0b40072f9145f7896c36916296973f9e6990ede110f14e5976c906d08987c9836cca57f", + "0xa88c3d5770148aee59930561ca1223aceb2c832fb5417e188dca935905301fc4c6c2c9270bc1dff7add490a125eb81c6", + "0xb6cf9b02c0cd91895ad209e38c54039523f137b5848b9d3ad33ae43af6c20c98434952db375fe378de7866f2d0e8b18a", + "0x84ef3d322ff580c8ad584b1fe4fe346c60866eb6a56e982ba2cf3b021ecb1fdb75ecc6c29747adda86d9264430b3f816", + "0xa0561c27224baf0927ad144cb71e31e54a064c598373fcf0d66aebf98ab7af1d8e2f343f77baefff69a6da750a219e11", + "0xaa5cc43f5b8162b016f5e1b61214c0c9d15b1078911c650b75e6cdfb49b85ee04c6739f5b1687d15908444f691f732de", + "0xad4ac099b935589c7b8fdfdf3db332b7b82bb948e13a5beb121ebd7db81a87d278024a1434bcf0115c54ca5109585c3d", + "0x8a00466abf3f109a1dcd19e643b603d3af23d42794ef8ca2514dd507ecea44a031ac6dbc18bd02f99701168b25c1791e", + "0xb00b5900dfad79645f8bee4e5adc7b84eb22e5b1e67df77ccb505b7fc044a6c08a8ea5faca662414eb945f874f884cea", + "0x950e204e5f17112250b22ea6bb8423baf522fc0af494366f18fe0f949f51d6e6812074a80875cf1ed9c8e7420058d541", + "0x91e5cbf8bb1a1d50c81608c9727b414d0dd2fb467ebc92f100882a3772e54f94979cfdf8e373fdef7c7fcdd60fec9e00", + "0xa093f6a857b8caaff80599c2e89c962b415ecbaa70d8fd973155fa976a284c6b29a855f5f7a3521134d00d2972755188", + "0xb4d55a3551b00da54cc010f80d99ddd2544bde9219a3173dfaadf3848edc7e4056ab532fb75ac26f5f7141e724267663", + "0xa03ea050fc9b011d1b04041b5765d6f6453a93a1819cd9bd6328637d0b428f08526466912895dcc2e3008ee58822e9a7", + "0x99b12b3665e473d01bc6985844f8994fb65cb15745024fb7af518398c4a37ff215da8f054e8fdf3286984ae36a73ca5e", + "0x9972c7e7a7fb12e15f78d55abcaf322c11249cd44a08f62c95288f34f66b51f146302bce750ff4d591707075d9123bd2", + "0xa64b4a6d72354e596d87cda213c4fc2814009461570ccb27d455bbe131f8d948421a71925425b546d8cf63d5458cd64b", + "0x91c215c73b195795ede2228b7ed1f6e37892e0c6b0f4a0b5a16c57aa1100c84df9239054a173b6110d6c2b7f4bf1ce52", + "0x88807198910ec1303480f76a3683870246a995e36adaeadc29c22f0bdba8152fe705bd070b75de657b04934f7d0ccf80", + "0xb37c0026c7b32eb02cacac5b55cb5fe784b8e48b2945c64d3037af83ece556a117f0ff053a5968c2f5fa230e291c1238", + "0x94c768384ce212bc2387e91ce8b45e4ff120987e42472888a317abc9dcdf3563b62e7a61c8e98d7cdcbe272167d91fc6", + "0xa10c2564936e967a390cb14ef6e8f8b04ea9ece5214a38837eda09e79e0c7970b1f83adf017c10efd6faa8b7ffa2c567", + "0xa5085eed3a95f9d4b1269182ea1e0d719b7809bf5009096557a0674bde4201b0ddc1f0f16a908fc468846b3721748ce3", + "0x87468eb620b79a0a455a259a6b4dfbc297d0d53336537b771254dd956b145dc816b195b7002647ea218552e345818a3f", + "0xace2b77ffb87366af0a9cb5d27d6fc4a14323dbbf1643f5f3c4559306330d86461bb008894054394cbfaefeaa0bc2745", + "0xb27f56e840a54fbd793f0b7a7631aa4cee64b5947e4382b2dfb5eb1790270288884c2a19afebe5dc0c6ef335d4531c1c", + "0x876e438633931f7f895062ee16c4b9d10428875f7bc79a8e156a64d379a77a2c45bf5430c5ab94330f03da352f1e9006", + "0xa2512a252587d200d2092b44c914df54e04ff8bcef36bf631f84bde0cf5a732e3dc7f00f662842cfd74b0b0f7f24180e", + "0x827f1bc8f54a35b7a4bd8154f79bcc055e45faed2e74adf7cf21cca95df44d96899e847bd70ead6bb27b9c0ed97bbd8b", + "0xa0c92cf5a9ed843714f3aea9fe7b880f622d0b4a3bf66de291d1b745279accf6ba35097849691370f41732ba64b5966b", + "0xa63f5c1e222775658421c487b1256b52626c6f79cb55a9b7deb2352622cedffb08502042d622eb3b02c97f9c09f9c957", + "0x8cc093d52651e65fb390e186db6cc4de559176af4624d1c44cb9b0e836832419dacac7b8db0627b96288977b738d785d", + "0xaa7b6a17dfcec146134562d32a12f7bd7fe9522e300859202a02939e69dbd345ed7ff164a184296268f9984f9312e8fc", + "0x8ac76721f0d2b679f023d06cbd28c85ae5f4b43c614867ccee88651d4101d4fd352dbdb65bf36bfc3ebc0109e4b0c6f9", + "0x8d350f7c05fc0dcd9a1170748846fb1f5d39453e4cb31e6d1457bed287d96fc393b2ecc53793ca729906a33e59c6834a", + "0xb9913510dfc5056d7ec5309f0b631d1ec53e3a776412ada9aefdaf033c90da9a49fdde6719e7c76340e86599b1f0eec2", + "0x94955626bf4ce87612c5cfffcf73bf1c46a4c11a736602b9ba066328dc52ad6d51e6d4f53453d4ed55a51e0aad810271", + "0xb0fcab384fd4016b2f1e53f1aafd160ae3b1a8865cd6c155d7073ecc1664e05b1d8bca1def39c158c7086c4e1103345e", + "0x827de3f03edfbde08570b72de6662c8bfa499b066a0a27ebad9b481c273097d17a5a0a67f01553da5392ec3f149b2a78", + "0xab7940384c25e9027c55c40df20bd2a0d479a165ced9b1046958353cd69015eeb1e44ed2fd64e407805ba42df10fc7bf", + "0x8ad456f6ff8cd58bd57567d931f923d0c99141978511b17e03cab7390a72b9f62498b2893e1b05c7c22dd274e9a31919", + "0xac75399e999effe564672db426faa17a839e57c5ef735985c70cd559a377adec23928382767b55ed5a52f7b11b54b756", + "0xb17f975a00b817299ac7af5f2024ea820351805df58b43724393bfb3920a8cd747a3bbd4b8286e795521489db3657168", + "0xa2bed800a6d95501674d9ee866e7314063407231491d794f8cf57d5be020452729c1c7cefd8c50dc1540181f5caab248", + "0x9743f5473171271ffdd3cc59a3ae50545901a7b45cd4bc3570db487865f3b73c0595bebabbfe79268809ee1862e86e4a", + "0xb7eab77c2d4687b60d9d7b04e842b3880c7940140012583898d39fcc22d9b9b0a9be2c2e3788b3e6f30319b39c338f09", + "0x8e2b8f797a436a1b661140e9569dcf3e1eea0a77c7ff2bc4ff0f3e49af04ed2de95e255df8765f1d0927fb456a9926b1", + "0x8aefea201d4a1f4ff98ffce94e540bb313f2d4dfe7e9db484a41f13fc316ed02b282e1acc9bc6f56cad2dc2e393a44c9", + "0xb950c17c0e5ca6607d182144aa7556bb0efe24c68f06d79d6413a973b493bfdf04fd147a4f1ab03033a32004cc3ea66f", + "0xb7b8dcbb179a07165f2dc6aa829fad09f582a71b05c3e3ea0396bf9e6fe73076f47035c031c2101e8e38e0d597eadd30", + "0xa9d77ed89c77ec1bf8335d08d41c3c94dcca9fd1c54f22837b4e54506b212aa38d7440126c80648ab7723ff18e65ed72", + "0xa819d6dfd4aef70e52b8402fe5d135f8082d40eb7d3bb5c4d7997395b621e2bb10682a1bad2c9caa33dd818550fc3ec6", + "0x8f6ee34128fac8bbf13ce2d68b2bb363eb4fd65b297075f88e1446ddeac242500eeb4ef0735e105882ff5ba8c44c139b", + "0xb4440e48255c1644bcecf3a1e9958f1ec4901cb5b1122ee5b56ffd02cad1c29c4266999dbb85aa2605c1b125490074d4", + "0xa43304a067bede5f347775d5811cf65a6380a8d552a652a0063580b5c5ef12a0867a39c7912fa219e184f4538eba1251", + "0xa891ad67a790089ffc9f6d53e6a3d63d3556f5f693e0cd8a7d0131db06fd4520e719cfcc3934f0a8f62a95f90840f1d4", + "0xaea6df8e9bb871081aa0fc5a9bafb00be7d54012c5baf653791907d5042a326aeee966fd9012a582cc16695f5baf7042", + "0x8ffa2660dc52ed1cd4eff67d6a84a8404f358a5f713d04328922269bee1e75e9d49afeec0c8ad751620f22352a438e25", + "0x87ec6108e2d63b06abed350f8b363b7489d642486f879a6c3aa90e5b0f335efc2ff2834eef9353951a42136f8e6a1b32", + "0x865619436076c2760d9e87ddc905023c6de0a8d56eef12c98a98c87837f2ca3f27fd26a2ad752252dbcbe2b9f1d5a032", + "0x980437dce55964293cb315c650c5586ffd97e7a944a83f6618af31c9d92c37b53ca7a21bb5bc557c151b9a9e217e7098", + "0x95d128fc369df4ad8316b72aea0ca363cbc7b0620d6d7bb18f7076a8717a6a46956ff140948b0cc4f6d2ce33b5c10054", + "0x8c7212d4a67b9ec70ebbca04358ad2d36494618d2859609163526d7b3acc2fc935ca98519380f55e6550f70a9bc76862", + "0x893a2968819401bf355e85eee0f0ed0406a6d4a7d7f172d0017420f71e00bb0ba984f6020999a3cdf874d3cd8ebcd371", + "0x9103c1af82dece25d87274e89ea0acd7e68c2921c4af3d8d7c82ab0ed9990a5811231b5b06113e7fa43a6bd492b4564f", + "0x99cfd87a94eab7d35466caa4ed7d7bb45e5c932b2ec094258fb14bf205659f83c209b83b2f2c9ccb175974b2a33e7746", + "0x874b6b93e4ee61be3f00c32dd84c897ccd6855c4b6251eb0953b4023634490ed17753cd3223472873cbc6095b2945075", + "0x84a32c0dc4ea60d33aac3e03e70d6d639cc9c4cc435c539eff915017be3b7bdaba33349562a87746291ebe9bc5671f24", + "0xa7057b24208928ad67914e653f5ac1792c417f413d9176ba635502c3f9c688f7e2ee81800d7e3dc0a340c464da2fd9c5", + "0xa03fb9ed8286aacfa69fbd5d953bec591c2ae4153400983d5dbb6cd9ea37fff46ca9e5cceb9d117f73e9992a6c055ad2", + "0x863b2de04e89936c9a4a2b40380f42f20aefbae18d03750fd816c658aee9c4a03df7b12121f795c85d01f415baaeaa59", + "0x8526eb9bd31790fe8292360d7a4c3eed23be23dd6b8b8f01d2309dbfdc0cfd33ad1568ddd7f8a610f3f85a9dfafc6a92", + "0xb46ab8c5091a493d6d4d60490c40aa27950574a338ea5bbc045be3a114af87bdcb160a8c80435a9b7ad815f3cb56a3f3", + "0xaeadc47b41a8d8b4176629557646202f868b1d728b2dda58a347d937e7ffc8303f20d26d6c00b34c851b8aeec547885d", + "0xaebb19fc424d72c1f1822aa7adc744cd0ef7e55727186f8df8771c784925058c248406ebeeaf3c1a9ee005a26e9a10c6", + "0x8ff96e81c1a4a2ab1b4476c21018fae0a67e92129ee36120cae8699f2d7e57e891f5c624902cb1b845b944926a605cc3", + "0x8251b8d2c43fadcaa049a9e7aff838dae4fb32884018d58d46403ac5f3beb5c518bfd45f03b8abb710369186075eb71c", + "0xa8b2a64f865f51a5e5e86a66455c093407933d9d255d6b61e1fd81ffafc9538d73caaf342338a66ba8ee166372a3d105", + "0xaad915f31c6ba7fdc04e2aaac62e84ef434b7ee76a325f07dc430d12c84081999720181067b87d792efd0117d7ee1eab", + "0xa13db3bb60389883fd41d565c54fb5180d9c47ce2fe7a169ae96e01d17495f7f4fa928d7e556e7c74319c4c25d653eb2", + "0xa4491b0198459b3f552855d680a59214eb74e6a4d6c5fa3b309887dc50ebea2ecf6d26c040550f7dc478b452481466fb", + "0x8f017f13d4b1e3f0c087843582b52d5f8d13240912254d826dd11f8703a99a2f3166dfbdfdffd9a3492979d77524276b", + "0x96c3d5dcd032660d50d7cd9db2914f117240a63439966162b10c8f1f3cf74bc83b0f15451a43b31dbd85e4a7ce0e4bb1", + "0xb479ec4bb79573d32e0ec93b92bdd7ec8c26ddb5a2d3865e7d4209d119fd3499eaac527615ffac78c440e60ef3867ae0", + "0xb2c49c4a33aa94b52b6410b599e81ff15490aafa7e43c8031c865a84e4676354a9c81eb4e7b8be6825fdcefd1e317d44", + "0x906dc51d6a90c089b6704b47592805578a6eed106608eeb276832f127e1b8e858b72e448edcbefb497d152447e0e68ff", + "0xb0e81c63b764d7dfbe3f3fddc9905aef50f3633e5d6a4af6b340495124abedcff5700dfd1577bbbed7b6bf97d02719cb", + "0x9304c64701e3b4ed6d146e48a881f7d83a17f58357cca0c073b2bb593afd2d94f6e2a7a1ec511d0a67ad6ff4c3be5937", + "0xb6fdbd12ba05aa598d80b83f70a15ef90e5cba7e6e75fa038540ee741b644cd1f408a6cecfd2a891ef8d902de586c6b5", + "0xb80557871a6521b1b3c74a1ba083ae055b575df607f1f7b04c867ba8c8c181ea68f8d90be6031f4d25002cca27c44da2", + "0xaa7285b8e9712e06b091f64163f1266926a36607f9d624af9996856ed2aaf03a580cb22ce407d1ade436c28b44ca173f", + "0x8148d72b975238b51e6ea389e5486940d22641b48637d7dfadfa603a605bfc6d74a016480023945d0b85935e396aea5d", + "0x8a014933a6aea2684b5762af43dcf4bdbb633cd0428d42d71167a2b6fc563ece5e618bff22f1db2ddb69b845b9a2db19", + "0x990d91740041db770d0e0eb9d9d97d826f09fd354b91c41e0716c29f8420e0e8aac0d575231efba12fe831091ec38d5a", + "0x9454d0d32e7e308ddec57cf2522fb1b67a2706e33fb3895e9e1f18284129ab4f4c0b7e51af25681d248d7832c05eb698", + "0xa5bd434e75bac105cb3e329665a35bce6a12f71dd90c15165777d64d4c13a82bceedb9b48e762bd24034e0fc9fbe45f4", + "0xb09e3b95e41800d4dc29c6ffdaab2cd611a0050347f6414f154a47ee20ee59bf8cf7181454169d479ebce1eb5c777c46", + "0xb193e341d6a047d15eea33766d656d807b89393665a783a316e9ba10518e5515c8e0ade3d6e15641d917a8a172a5a635", + "0xade435ec0671b3621dde69e07ead596014f6e1daa1152707a8c18877a8b067bde2895dd47444ffa69db2bbef1f1d8816", + "0xa7fd3d6d87522dfc56fb47aef9ce781a1597c56a8bbfd796baba907afdc872f753d732bfda1d3402aee6c4e0c189f52d", + "0xa298cb4f4218d0464b2fab393e512bbc477c3225aa449743299b2c3572f065bc3a42d07e29546167ed9e1b6b3b3a3af3", + "0xa9ee57540e1fd9c27f4f0430d194b91401d0c642456c18527127d1f95e2dba41c2c86d1990432eb38a692fda058fafde", + "0x81d6c1a5f93c04e6d8e5a7e0678c1fc89a1c47a5c920bcd36180125c49fcf7c114866b90e90a165823560b19898a7c16", + "0xa4b7a1ec9e93c899b9fd9aaf264c50e42c36c0788d68296a471f7a3447af4dbc81e4fa96070139941564083ec5b5b5a1", + "0xb3364e327d381f46940c0e11e29f9d994efc6978bf37a32586636c0070b03e4e23d00650c1440f448809e1018ef9f6d8", + "0x8056e0913a60155348300e3a62e28b5e30629a90f7dd4fe11289097076708110a1d70f7855601782a3cdc5bdb1ca9626", + "0xb4980fd3ea17bac0ba9ee1c470b17e575bb52e83ebdd7d40c93f4f87bebeaff1c8a679f9d3d09d635f068d37d5bd28bd", + "0x905a9299e7e1853648e398901dfcd437aa575c826551f83520df62984f5679cb5f0ea86aa45ed3e18b67ddc0dfafe809", + "0xab99553bf31a84f2e0264eb34a08e13d8d15e2484aa9352354becf9a15999c76cc568d68274b70a65e49703fc23540d0", + "0xa43681597bc574d2dae8964c9a8dc1a07613d7a1272bdcb818d98c85d44e16d744250c33f3b5e4d552d97396b55e601f", + "0xa54e5a31716fccb50245898c99865644405b8dc920ded7a11f3d19bdc255996054b268e16f2e40273f11480e7145f41e", + "0x8134f3ad5ef2ad4ba12a8a4e4d8508d91394d2bcdc38b7c8c8c0b0a820357ac9f79d286c65220f471eb1adca1d98fc68", + "0x94e2f755e60471578ab2c1adb9e9cea28d4eec9b0e92e0140770bca7002c365fcabfe1e5fb4fe6cfe79a0413712aa3ef", + "0xad48f8d0ce7eb3cc6e2a3086ad96f562e5bed98a360721492ae2e74dc158586e77ec8c35d5fd5927376301b7741bad2b", + "0x8614f0630bdd7fbad3a31f55afd9789f1c605dc85e7dc67e2edfd77f5105f878bb79beded6e9f0b109e38ea7da67e8d5", + "0x9804c284c4c5e77dabb73f655b12181534ca877c3e1e134aa3f47c23b7ec92277db34d2b0a5d38d2b69e5d1c3008a3e3", + "0xa51b99c3088e473afdaa9e0a9f7e75a373530d3b04e44e1148da0726b95e9f5f0c7e571b2da000310817c36f84b19f7f", + "0xac4ff909933b3b76c726b0a382157cdc74ab851a1ac6cef76953c6444441804cc43abb883363f416592e8f6cfbc4550b", + "0xae7d915eb9fc928b65a29d6edbc75682d08584d0014f7bcf17d59118421ae07d26a02137d1e4de6938bcd1ab8ef48fad", + "0x852f7e453b1af89b754df6d11a40d5d41ea057376e8ecacd705aacd2f917457f4a093d6b9a8801837fa0f62986ad7149", + "0x92c6bf5ada5d0c3d4dd8058483de36c215fa98edab9d75242f3eff9db07c734ad67337da6f0eefe23a487bf75a600dee", + "0xa2b42c09d0db615853763552a48d2e704542bbd786aae016eb58acbf6c0226c844f5fb31e428cb6450b9db855f8f2a6f", + "0x880cc07968266dbfdcfbc21815cd69e0eddfee239167ac693fb0413912d816f2578a74f7716eecd6deefa68c6eccd394", + "0xb885b3ace736cd373e8098bf75ba66fa1c6943ca1bc4408cd98ac7074775c4478594f91154b8a743d9c697e1b29f5840", + "0xa51ce78de512bd87bfa0835de819941dffbf18bec23221b61d8096fc9436af64e0693c335b54e7bfc763f287bdca2db6", + "0xa3c76166a3bdb9b06ef696e57603b58871bc72883ee9d45171a30fe6e1d50e30bc9c51b4a0f5a7270e19a77b89733850", + "0xacefc5c6f8a1e7c24d7b41e0fc7f6f3dc0ede6cf3115ffb9a6e54b1d954cbca9bda8ad7a084be9be245a1b8e9770d141", + "0xb420ed079941842510e31cfad117fa11fb6b4f97dfbc6298cb840f27ebaceba23eeaf3f513bcffbf5e4aae946310182d", + "0x95c3bb5ef26c5ed2f035aa5d389c6b3c15a6705b9818a3fefaed28922158b35642b2e8e5a1a620fdad07e75ad4b43af4", + "0x825149f9081ecf07a2a4e3e8b5d21bade86c1a882475d51c55ee909330b70c5a2ac63771c8600c6f38df716af61a3ea1", + "0x873b935aae16d9f08adbc25353cee18af2f1b8d5f26dec6538d6bbddc515f2217ed7d235dcfea59ae61b428798b28637", + "0x9294150843a2bedcedb3bb74c43eb28e759cf9499582c5430bccefb574a8ddd4f11f9929257ff4c153990f9970a2558f", + "0xb619563a811cc531da07f4f04e5c4c6423010ff9f8ed7e6ec9449162e3d501b269fb1c564c09c0429431879b0f45df02", + "0x91b509b87eb09f007d839627514658c7341bc76d468920fe8a740a8cb96a7e7e631e0ea584a7e3dc1172266f641d0f5c", + "0x8b8aceace9a7b9b4317f1f01308c3904d7663856946afbcea141a1c615e21ccad06b71217413e832166e9dd915fbe098", + "0x87b3b36e725833ea0b0f54753c3728c0dbc87c52d44d705ffc709f2d2394414c652d3283bab28dcce09799504996cee0", + "0xb2670aad5691cbf308e4a6a77a075c4422e6cbe86fdba24e9f84a313e90b0696afb6a067eebb42ba2d10340d6a2f6e51", + "0x876784a9aff3d54faa89b2bacd3ff5862f70195d0b2edc58e8d1068b3c9074c0da1cfa23671fe12f35e33b8a329c0ccd", + "0x8b48b9e758e8a8eae182f5cbec96f67d20cca6d3eee80a2d09208eb1d5d872e09ef23d0df8ebbb9b01c7449d0e3e3650", + "0xb79303453100654c04a487bdcadc9e3578bc80930c489a7069a52e8ca1dba36c492c8c899ce025f8364599899baa287d", + "0x961b35a6111da54ece6494f24dacd5ea46181f55775b5f03df0e370c34a5046ac2b4082925855325bb42bc2a2c98381d", + "0xa31feb1be3f5a0247a1f7d487987eb622e34fca817832904c6ee3ee60277e5847945a6f6ea1ac24542c72e47bdf647df", + "0xa12a2aa3e7327e457e1aae30e9612715dd2cfed32892c1cd6dcda4e9a18203af8a44afb46d03b2eed89f6b9c5a2c0c23", + "0xa08265a838e69a2ca2f80fead6ccf16f6366415b920c0b22ee359bcd8d4464ecf156f400a16a7918d52e6d733dd64211", + "0xb723d6344e938d801cca1a00032af200e541d4471fd6cbd38fb9130daa83f6a1dffbbe7e67fc20f9577f884acd7594b2", + "0xa6733d83ec78ba98e72ddd1e7ff79b7adb0e559e256760d0c590a986e742445e8cdf560d44b29439c26d87edd0b07c8c", + "0xa61c2c27d3f7b9ff4695a17afedf63818d4bfba390507e1f4d0d806ce8778d9418784430ce3d4199fd3bdbc2504d2af3", + "0x8332f3b63a6dc985376e8b1b25eeae68be6160fbe40053ba7bcf6f073204f682da72321786e422d3482fd60c9e5aa034", + "0xa280f44877583fbb6b860d500b1a3f572e3ee833ec8f06476b3d8002058e25964062feaa1e5bec1536d734a5cfa09145", + "0xa4026a52d277fcea512440d2204f53047718ebfcae7b48ac57ea7f6bfbc5de9d7304db9a9a6cbb273612281049ddaec5", + "0x95cdf69c831ab2fad6c2535ede9c07e663d2ddccc936b64e0843d2df2a7b1c31f1759c3c20f1e7a57b1c8f0dbb21b540", + "0x95c96cec88806469c277ab567863c5209027cecc06c7012358e5f555689c0d9a5ffb219a464f086b45817e8536b86d2f", + "0xafe38d4684132a0f03d806a4c8df556bf589b25271fbc6fe2e1ed16de7962b341c5003755da758d0959d2e6499b06c68", + "0xa9b77784fda64987f97c3a23c5e8f61b918be0f7c59ba285084116d60465c4a2aaafc8857eb16823282cc83143eb9126", + "0xa830f05881ad3ce532a55685877f529d32a5dbe56cea57ffad52c4128ee0fad0eeaf0da4362b55075e77eda7babe70e5", + "0x992b3ad190d6578033c13ed5abfee4ef49cbc492babb90061e3c51ee4b5790cdd4c8fc1abff1fa2c00183b6b64f0bbbe", + "0xb1015424d9364aeff75de191652dc66484fdbec3e98199a9eb9671ec57bec6a13ff4b38446e28e4d8aedb58dd619cd90", + "0xa745304604075d60c9db36cada4063ac7558e7ec2835d7da8485e58d8422e817457b8da069f56511b02601289fbb8981", + "0xa5ba4330bc5cb3dbe0486ddf995632a7260a46180a08f42ae51a2e47778142132463cc9f10021a9ad36986108fefa1a9", + "0xb419e9fd4babcaf8180d5479db188bb3da232ae77a1c4ed65687c306e6262f8083070a9ac32220cddb3af2ec73114092", + "0xa49e23dc5f3468f3bf3a0bb7e4a114a788b951ff6f23a3396ae9e12cbff0abd1240878a3d1892105413dbc38818e807c", + "0xb7ecc7b4831f650202987e85b86bc0053f40d983f252e9832ef503aea81c51221ce93279da4aa7466c026b2d2070e55d", + "0x96a8c35cb87f84fa84dcd6399cc2a0fd79cc9158ef4bdde4bae31a129616c8a9f2576cd19baa3f497ca34060979aed7d", + "0x8681b2c00aa62c2b519f664a95dcb8faef601a3b961bb4ce5d85a75030f40965e2983871d41ea394aee934e859581548", + "0x85c229a07efa54a713d0790963a392400f55fbb1a43995a535dc6c929f20d6a65cf4efb434e0ad1cb61f689b8011a3bc", + "0x90856f7f3444e5ad44651c28e24cc085a5db4d2ffe79aa53228c26718cf53a6e44615f3c5cda5aa752d5f762c4623c66", + "0x978999b7d8aa3f28a04076f74d11c41ef9c89fdfe514936c4238e0f13c38ec97e51a5c078ebc6409e517bfe7ccb42630", + "0xa099914dd7ed934d8e0d363a648e9038eb7c1ec03fa04dbcaa40f7721c618c3ef947afef7a16b4d7ac8c12aa46637f03", + "0xab2a104fed3c83d16f2cda06878fa5f30c8c9411de71bfb67fd2fc9aa454dcbcf3d299d72f8cc12e919466a50fcf7426", + "0xa4471d111db4418f56915689482f6144efc4664cfb0311727f36c864648d35734351becc48875df96f4abd3cfcf820f9", + "0x83be11727cd30ea94ccc8fa31b09b81c9d6a9a5d3a4686af9da99587332fe78c1f94282f9755854bafd6033549afec91", + "0x88020ff971dc1a01a9e993cd50a5d2131ffdcbb990c1a6aaa54b20d8f23f9546a70918ea57a21530dcc440c1509c24ad", + "0xae24547623465e87905eaffa1fa5d52bb7c453a8dbd89614fa8819a2abcedaf455c2345099b7324ae36eb0ad7c8ef977", + "0xb59b0c60997de1ee00b7c388bc7101d136c9803bf5437b1d589ba57c213f4f835a3e4125b54738e78abbc21b000f2016", + "0xa584c434dfe194546526691b68fa968c831c31da42303a1d735d960901c74011d522246f37f299555416b8cf25c5a548", + "0x80408ce3724f4837d4d52376d255e10f69eb8558399ae5ca6c11b78b98fe67d4b93157d2b9b639f1b5b64198bfe87713", + "0xabb941e8d406c2606e0ddc35c113604fdd9d249eacc51cb64e2991e551b8639ce44d288cc92afa7a1e7fc599cfc84b22", + "0xb223173f560cacb1c21dba0f1713839e348ad02cbfdef0626748604c86f89e0f4c919ed40b583343795bdd519ba952c8", + "0xaf1c70512ec3a19d98b8a1fc3ff7f7f5048a27d17d438d43f561974bbdd116fcd5d5c21040f3447af3f0266848d47a15", + "0x8a44809568ebe50405bede19b4d2607199159b26a1b33e03d180e6840c5cf59d991a4fb150d111443235d75ecad085b7", + "0xb06207cdca46b125a27b3221b5b50cf27af4c527dd7c80e2dbcebbb09778a96df3af67e50f07725239ce3583dad60660", + "0x993352d9278814ec89b26a11c4a7c4941bf8f0e6781ae79559d14749ee5def672259792db4587f85f0100c7bb812f933", + "0x9180b8a718b971fd27bc82c8582d19c4b4f012453e8c0ffeeeffe745581fc6c07875ab28be3af3fa3896d19f0c89ac5b", + "0x8b8e1263eb48d0fe304032dd5ea1f30e73f0121265f7458ba9054d3626894e8a5fef665340abd2ede9653045c2665938", + "0x99a2beee4a10b7941c24b2092192faf52b819afd033e4a2de050fd6c7f56d364d0cf5f99764c3357cf32399e60fc5d74", + "0x946a4aad7f8647ea60bee2c5fcdeb6f9a58fb2cfca70c4d10e458027a04846e13798c66506151be3df9454b1e417893f", + "0xa672a88847652d260b5472d6908d1d57e200f1e492d30dd1cecc441cdfc9b76e016d9bab560efd4d7f3c30801de884a9", + "0x9414e1959c156cde1eb24e628395744db75fc24b9df4595350aaad0bc38e0246c9b4148f6443ef68b8e253a4a6bcf11c", + "0x9316e9e4ec5fab4f80d6540df0e3a4774db52f1d759d2e5b5bcd3d7b53597bb007eb1887cb7dc61f62497d51ffc8d996", + "0x902d6d77bb49492c7a00bc4b70277bc28c8bf9888f4307bb017ac75a962decdedf3a4e2cf6c1ea9f9ba551f4610cbbd7", + "0xb07025a18b0e32dd5e12ec6a85781aa3554329ea12c4cd0d3b2c22e43d777ef6f89876dd90a9c8fb097ddf61cf18adc5", + "0xb355a849ad3227caa4476759137e813505ec523cbc2d4105bc7148a4630f9e81918d110479a2d5f5e4cd9ccec9d9d3e3", + "0xb49532cfdf02ee760109881ad030b89c48ee3bb7f219ccafc13c93aead754d29bdafe345be54c482e9d5672bd4505080", + "0x9477802410e263e4f938d57fa8f2a6cac7754c5d38505b73ee35ea3f057aad958cb9722ba6b7b3cfc4524e9ca93f9cdc", + "0x9148ea83b4436339580f3dbc9ba51509e9ab13c03063587a57e125432dd0915f5d2a8f456a68f8fff57d5f08c8f34d6e", + "0xb00b6b5392b1930b54352c02b1b3b4f6186d20bf21698689bbfc7d13e86538a4397b90e9d5c93fd2054640c4dbe52a4f", + "0x926a9702500441243cd446e7cbf15dde16400259726794694b1d9a40263a9fc9e12f7bcbf12a27cb9aaba9e2d5848ddc", + "0xa0c6155f42686cbe7684a1dc327100962e13bafcf3db97971fc116d9f5c0c8355377e3d70979cdbd58fd3ea52440901c", + "0xa277f899f99edb8791889d0817ea6a96c24a61acfda3ad8c3379e7c62b9d4facc4b965020b588651672fd261a77f1bfc", + "0x8f528cebb866b501f91afa50e995234bef5bf20bff13005de99cb51eaac7b4f0bf38580cfd0470de40f577ead5d9ba0f", + "0x963fc03a44e9d502cc1d23250efef44d299befd03b898d07ce63ca607bb474b5cf7c965a7b9b0f32198b04a8393821f7", + "0xab087438d0a51078c378bf4a93bd48ef933ff0f1fa68d02d4460820df564e6642a663b5e50a5fe509527d55cb510ae04", + "0xb0592e1f2c54746bb076be0fa480e1c4bebc4225e1236bcda3b299aa3853e3afb401233bdbcfc4a007b0523a720fbf62", + "0x851613517966de76c1c55a94dc4595f299398a9808f2d2f0a84330ba657ab1f357701d0895f658c18a44cb00547f6f57", + "0xa2fe9a1dd251e72b0fe4db27be508bb55208f8f1616b13d8be288363ec722826b1a1fd729fc561c3369bf13950bf1fd6", + "0xb896cb2bc2d0c77739853bc59b0f89b2e008ba1f701c9cbe3bef035f499e1baee8f0ff1e794854a48c320586a2dfc81a", + "0xa1b60f98e5e5106785a9b81a85423452ee9ef980fa7fa8464f4366e73f89c50435a0c37b2906052b8e58e212ebd366cf", + "0xa853b0ebd9609656636df2e6acd5d8839c0fda56f7bf9288a943b06f0b67901a32b95e016ca8bc99bd7b5eab31347e72", + "0xb290fa4c1346963bd5225235e6bdf7c542174dab4c908ab483d1745b9b3a6015525e398e1761c90e4b49968d05e30eea", + "0xb0f65a33ad18f154f1351f07879a183ad62e5144ad9f3241c2d06533dad09cbb2253949daff1bb02d24d16a3569f7ef0", + "0xa00db59b8d4218faf5aeafcd39231027324408f208ec1f54d55a1c41228b463b88304d909d16b718cfc784213917b71e", + "0xb8d695dd33dc2c3bc73d98248c535b2770ad7fa31aa726f0aa4b3299efb0295ba9b4a51c71d314a4a1bd5872307534d1", + "0xb848057cca2ca837ee49c42b88422303e58ea7d2fc76535260eb5bd609255e430514e927cc188324faa8e657396d63ec", + "0x92677836061364685c2aaf0313fa32322746074ed5666fd5f142a7e8f87135f45cd10e78a17557a4067a51dfde890371", + "0xa854b22c9056a3a24ab164a53e5c5cf388616c33e67d8ebb4590cb16b2e7d88b54b1393c93760d154208b5ca822dc68f", + "0x86fff174920388bfab841118fb076b2b0cdec3fdb6c3d9a476262f82689fb0ed3f1897f7be9dbf0932bb14d346815c63", + "0x99661cf4c94a74e182752bcc4b98a8c2218a8f2765642025048e12e88ba776f14f7be73a2d79bd21a61def757f47f904", + "0x8a8893144d771dca28760cba0f950a5d634195fd401ec8cf1145146286caffb0b1a6ba0c4c1828d0a5480ce49073c64c", + "0x938a59ae761359ee2688571e7b7d54692848eb5dde57ffc572b473001ea199786886f8c6346a226209484afb61d2e526", + "0x923f68a6aa6616714cf077cf548aeb845bfdd78f2f6851d8148cba9e33a374017f2f3da186c39b82d14785a093313222", + "0xac923a93d7da7013e73ce8b4a2b14b8fd0cc93dc29d5de941a70285bdd19be4740fedfe0c56b046689252a3696e9c5bc", + "0xb49b32c76d4ec1a2c68d4989285a920a805993bc6fcce6dacd3d2ddae73373050a5c44ba8422a3781050682fa0ef6ba2", + "0x8a367941c07c3bdca5712524a1411bad7945c7c48ffc7103b1d4dff2c25751b0624219d1ccde8c3f70c465f954be5445", + "0xb838f029df455efb6c530d0e370bbbf7d87d61a9aea3d2fe5474c5fe0a39cf235ceecf9693c5c6c5820b1ba8f820bd31", + "0xa8983b7c715eaac7f13a001d2abc462dfc1559dab4a6b554119c271aa8fe00ffcf6b6949a1121f324d6d26cb877bcbae", + "0xa2afb24ad95a6f14a6796315fbe0d8d7700d08f0cfaf7a2abe841f5f18d4fecf094406cbd54da7232a159f9c5b6e805e", + "0x87e8e95ad2d62f947b2766ff405a23f7a8afba14e7f718a691d95369c79955cdebe24c54662553c60a3f55e6322c0f6f", + "0x87c2cbcecb754e0cc96128e707e5c5005c9de07ffd899efa3437cadc23362f5a1d3fcdd30a1f5bdc72af3fb594398c2a", + "0x91afd6ee04f0496dc633db88b9370d41c428b04fd991002502da2e9a0ef051bcd7b760e860829a44fbe5539fa65f8525", + "0x8c50e5d1a24515a9dd624fe08b12223a75ca55196f769f24748686315329b337efadca1c63f88bee0ac292dd0a587440", + "0x8a07e8f912a38d94309f317c32068e87f68f51bdfa082d96026f5f5f8a2211621f8a3856dda8069386bf15fb2d28c18f", + "0x94ad1dbe341c44eeaf4dc133eed47d8dbfe752575e836c075745770a6679ff1f0e7883b6aa917462993a7f469d74cab5", + "0x8745f8bd86c2bb30efa7efb7725489f2654f3e1ac4ea95bd7ad0f3cfa223055d06c187a16192d9d7bdaea7b050c6a324", + "0x900d149c8d79418cda5955974c450a70845e02e5a4ecbcc584a3ca64d237df73987c303e3eeb79da1af83bf62d9e579f", + "0x8f652ab565f677fb1a7ba03b08004e3cda06b86c6f1b0b9ab932e0834acf1370abb2914c15b0d08327b5504e5990681c", + "0x9103097d088be1f75ab9d3da879106c2f597e2cc91ec31e73430647bdd5c33bcfd771530d5521e7e14df6acda44f38a6", + "0xb0fec7791cfb0f96e60601e1aeced9a92446b61fedab832539d1d1037558612d78419efa87ff5f6b7aab8fd697d4d9de", + "0xb9d2945bdb188b98958854ba287eb0480ef614199c4235ce5f15fc670b8c5ffe8eeb120c09c53ea8a543a022e6a321ac", + "0xa9461bb7d5490973ebaa51afc0bb4a5e42acdccb80e2f939e88b77ac28a98870e103e1042899750f8667a8cc9123bae9", + "0xa37fdf11d4bcb2aed74b9f460a30aa34afea93386fa4cdb690f0a71bc58f0b8df60bec56e7a24f225978b862626fa00e", + "0xa214420e183e03d531cf91661466ea2187d84b6e814b8b20b3730a9400a7d25cf23181bb85589ebc982cec414f5c2923", + "0xad09a45a698a6beb3e0915f540ef16e9af7087f53328972532d6b5dfe98ce4020555ece65c6cbad8bd6be8a4dfefe6fd", + "0xab6742800b02728c92d806976764cb027413d6f86edd08ad8bb5922a2969ee9836878cd39db70db0bd9a2646862acc4f", + "0x974ca9305bd5ea1dc1755dff3b63e8bfe9f744321046c1395659bcea2a987b528e64d5aa96ac7b015650b2253b37888d", + "0x84eee9d6bce039c52c2ebc4fccc0ad70e20c82f47c558098da4be2f386a493cbc76adc795b5488c8d11b6518c2c4fab8", + "0x875d7bda46efcb63944e1ccf760a20144df3b00d53282b781e95f12bfc8f8316dfe6492c2efbf796f1150e36e436e9df", + "0xb68a2208e0c587b5c31b5f6cb32d3e6058a9642e2d9855da4f85566e1412db528475892060bb932c55b3a80877ad7b4a", + "0xba006368ecab5febb6ab348644d9b63de202293085ed468df8bc24d992ae8ce468470aa37f36a73630c789fb9c819b30", + "0x90a196035150846cd2b482c7b17027471372a8ce7d914c4d82b6ea7fa705d8ed5817bd42d63886242585baf7d1397a1c", + "0xa223b4c85e0daa8434b015fd9170b5561fe676664b67064974a1e9325066ecf88fc81f97ab5011c59fad28cedd04b240", + "0x82e8ec43139cf15c6bbeed484b62e06cded8a39b5ce0389e4cbe9c9e9c02f2f0275d8d8d4e8dfec8f69a191bef220408", + "0x81a3fc07a7b68d92c6ee4b6d28f5653ee9ec85f7e2ee1c51c075c1b130a8c5097dc661cf10c5aff1c7114b1a6a19f11a", + "0x8ed2ef8331546d98819a5dd0e6c9f8cb2630d0847671314a28f277faf68da080b53891dd75c82cbcf7788b255490785d", + "0xacecabf84a6f9bbed6b2fc2e7e4b48f02ef2f15e597538a73aea8f98addc6badda15e4695a67ecdb505c1554e8f345ec", + "0xb8f51019b2aa575f8476e03dcadf86cc8391f007e5f922c2a36b2daa63f5a503646a468990cd5c65148d323942193051", + "0xaaa595a84b403ec65729bc1c8055a94f874bf9adddc6c507b3e1f24f79d3ad359595a672b93aab3394db4e2d4a7d8970", + "0x895144c55fcbd0f64d7dd69e6855cfb956e02b5658eadf0f026a70703f3643037268fdd673b0d21b288578a83c6338dd", + "0xa2e92ae6d0d237d1274259a8f99d4ea4912a299816350b876fba5ebc60b714490e198a916e1c38c6e020a792496fa23c", + "0xa45795fda3b5bb0ad1d3c628f6add5b2a4473a1414c1a232e80e70d1cfffd7f8a8d9861f8df2946999d7dbb56bf60113", + "0xb6659bf7f6f2fef61c39923e8c23b8c70e9c903028d8f62516d16755cd3fba2fe41c285aa9432dc75ab08f8a1d8a81fc", + "0xa735609a6bc5bfd85e58234fc439ff1f58f1ff1dd966c5921d8b649e21f006bf2b8642ad8a75063c159aaf6935789293", + "0xa3c622eb387c9d15e7bda2e3e84d007cb13a6d50d655c3f2f289758e49d3b37b9a35e4535d3cc53d8efd51f407281f19", + "0x8afe147b53ad99220f5ef9d763bfc91f9c20caecbcf823564236fb0e6ede49414c57d71eec4772c8715cc65a81af0047", + "0xb5f0203233cf71913951e9c9c4e10d9243e3e4a1f2cb235bf3f42009120ba96e04aa414c9938ea8873b63148478927e8", + "0x93c52493361b458d196172d7ba982a90a4f79f03aa8008edc322950de3ce6acf4c3977807a2ffa9e924047e02072b229", + "0xb9e72b805c8ac56503f4a86c82720afbd5c73654408a22a2ac0b2e5caccdfb0e20b59807433a6233bc97ae58cf14c70a", + "0xaf0475779b5cee278cca14c82da2a9f9c8ef222eb885e8c50cca2315fea420de6e04146590ed0dd5a29c0e0812964df5", + "0xb430ccab85690db02c2d0eb610f3197884ca12bc5f23c51e282bf3a6aa7e4a79222c3d8761454caf55d6c01a327595f9", + "0x830032937418b26ee6da9b5206f3e24dc76acd98589e37937e963a8333e5430abd6ce3dd93ef4b8997bd41440eed75d6", + "0x8820a6d73180f3fe255199f3f175c5eb770461ad5cfdde2fb11508041ed19b8c4ce66ad6ecebf7d7e836cc2318df47ca", + "0xaef1393e7d97278e77bbf52ef6e1c1d5db721ccf75fe753cf47a881fa034ca61eaa5098ee5a344c156d2b14ff9e284ad", + "0x8a4a26c07218948c1196c45d927ef4d2c42ade5e29fe7a91eaebe34a29900072ce5194cf28d51f746f4c4c649daf4396", + "0x84011dc150b7177abdcb715efbd8c201f9cb39c36e6069af5c50a096021768ba40cef45b659c70915af209f904ede3b6", + "0xb1bd90675411389bb66910b21a4bbb50edce5330850c5ab0b682393950124252766fc81f5ecfc72fb7184387238c402e", + "0x8dfdcd30583b696d2c7744655f79809f451a60c9ad5bf1226dc078b19f4585d7b3ef7fa9d54e1ac09520d95cbfd20928", + "0xb351b4dc6d98f75b8e5a48eb7c6f6e4b78451991c9ba630e5a1b9874c15ac450cd409c1a024713bf2cf82dc400e025ef", + "0xa462b8bc97ac668b97b28b3ae24b9f5de60e098d7b23ecb600d2194cd35827fb79f77c3e50d358f5bd72ee83fef18fa0", + "0xa183753265c5f7890270821880cce5f9b2965b115ba783c6dba9769536f57a04465d7da5049c7cf8b3fcf48146173c18", + "0xa8a771b81ed0d09e0da4d79f990e58eabcd2be3a2680419502dd592783fe52f657fe55125b385c41d0ba3b9b9cf54a83", + "0xa71ec577db46011689d073245e3b1c3222a9b1fe6aa5b83629adec5733dd48617ebea91346f0dd0e6cdaa86e4931b168", + "0xa334b8b244f0d598a02da6ae0f918a7857a54dce928376c4c85df15f3b0f2ba3ac321296b8b7c9dd47d770daf16c8f8c", + "0xa29037f8ef925c417c90c4df4f9fb27fb977d04e2b3dd5e8547d33e92ab72e7a00f5461de21e28835319eae5db145eb7", + "0xb91054108ae78b00e3298d667b913ebc44d8f26e531eae78a8fe26fdfb60271c97efb2dee5f47ef5a3c15c8228138927", + "0x926c13efbe90604f6244be9315a34f72a1f8d1aab7572df431998949c378cddbf2fe393502c930fff614ff06ae98a0ce", + "0x995c758fd5600e6537089b1baa4fbe0376ab274ff3e82a17768b40df6f91c2e443411de9cafa1e65ea88fb8b87d504f4", + "0x9245ba307a7a90847da75fca8d77ec03fdfc812c871e7a2529c56a0a79a6de16084258e7a9ac4ae8a3756f394336e21c", + "0x99e0cfa2bb57a7e624231317044c15e52196ecce020db567c8e8cb960354a0be9862ee0c128c60b44777e65ac315e59f", + "0xad4f6b3d27bbbb744126601053c3dc98c07ff0eb0b38a898bd80dce778372846d67e5ab8fb34fb3ad0ef3f235d77ba7f", + "0xa0f12cae3722bbbca2e539eb9cc7614632a2aefe51410430070a12b5bc5314ecec5857b7ff8f41e9980cac23064f7c56", + "0xb487f1bc59485848c98222fd3bc36c8c9bb3d2912e2911f4ceca32c840a7921477f9b1fe00877e05c96c75d3eecae061", + "0xa6033db53925654e18ecb3ce715715c36165d7035db9397087ac3a0585e587998a53973d011ac6d48af439493029cee6", + "0xa6b4d09cd01c70a3311fd131d3710ccf97bde3e7b80efd5a8c0eaeffeb48cca0f951ced905290267b115b06d46f2693b", + "0xa9dff1df0a8f4f218a98b6f818a693fb0d611fed0fc3143537cbd6578d479af13a653a8155e535548a2a0628ae24fa58", + "0xa58e469f65d366b519f9a394cacb7edaddac214463b7b6d62c2dbc1316e11c6c5184ce45c16de2d77f990dcdd8b55430", + "0x989e71734f8119103586dc9a3c5f5033ddc815a21018b34c1f876cdfc112efa868d5751bf6419323e4e59fa6a03ece1c", + "0xa2da00e05036c884369e04cf55f3de7d659cd5fa3f849092b2519dd263694efe0f051953d9d94b7e121f0aee8b6174d7", + "0x968f3c029f57ee31c4e1adea89a7f92e28483af9a74f30fbdb995dc2d40e8e657dff8f8d340d4a92bf65f54440f2859f", + "0x932778df6f60ac1639c1453ef0cbd2bf67592759dcccb3e96dcc743ff01679e4c7dd0ef2b0833dda548d32cb4eba49e2", + "0xa805a31139f8e0d6dae1ac87d454b23a3dc9fc653d4ca18d4f8ebab30fc189c16e73981c2cb7dd6f8c30454a5208109d", + "0xa9ba0991296caa2aaa4a1ceacfb205544c2a2ec97088eace1d84ee5e2767656a172f75d2f0c4e16a3640a0e0dec316e0", + "0xb1e49055c968dced47ec95ae934cf45023836d180702e20e2df57e0f62fb85d7ac60d657ba3ae13b8560b67210449459", + "0xa94e1da570a38809c71e37571066acabff7bf5632737c9ab6e4a32856924bf6211139ab3cedbf083850ff2d0e0c0fcfc", + "0x88ef1bb322000c5a5515b310c838c9af4c1cdbb32eab1c83ac3b2283191cd40e9573747d663763a28dad0d64adc13840", + "0xa987ce205f923100df0fbd5a85f22c9b99b9b9cbe6ddfa8dfda1b8fe95b4f71ff01d6c5b64ca02eb24edb2b255a14ef0", + "0x84fe8221a9e95d9178359918a108de4763ebfa7a6487facb9c963406882a08a9a93f492f8e77cf9e7ea41ae079c45993", + "0xaa1cf3dc7c5dcfa15bbbc811a4bb6dbac4fba4f97fb1ed344ab60264d7051f6eef19ea9773441d89929ee942ed089319", + "0x8f6a7d610d59d9f54689bbe6a41f92d9f6096cde919c1ab94c3c7fcecf0851423bc191e5612349e10f855121c0570f56", + "0xb5af1fa7894428a53ea520f260f3dc3726da245026b6d5d240625380bfb9c7c186df0204bb604efac5e613a70af5106e", + "0xa5bce6055ff812e72ce105f147147c7d48d7a2313884dd1f488b1240ee320f13e8a33f5441953a8e7a3209f65b673ce1", + "0xb9b55b4a1422677d95821e1d042ab81bbf0bf087496504021ec2e17e238c2ca6b44fb3b635a5c9eac0871a724b8d47c3", + "0x941c38e533ce4a673a3830845b56786585e5fe49c427f2e5c279fc6db08530c8f91db3e6c7822ec6bb4f956940052d18", + "0xa38e191d66c625f975313c7007bbe7431b5a06ed2da1290a7d5d0f2ec73770d476efd07b8e632de64597d47df175cbb0", + "0x94ba76b667abf055621db4c4145d18743a368d951565632ed4e743dd50dd3333507c0c34f286a5c5fdbf38191a2255cd", + "0xa5ca38c60be5602f2bfa6e00c687ac96ac36d517145018ddbee6f12eb0faa63dd57909b9eeed26085fe5ac44e55d10ab", + "0xb00fea3b825e60c1ed1c5deb4b551aa65a340e5af36b17d5262c9cd2c508711e4dc50dc2521a2c16c7c901902266e64a", + "0x971b86fc4033485e235ccb0997a236206ba25c6859075edbcdf3c943116a5030b7f75ebca9753d863a522ba21a215a90", + "0xb3b31f52370de246ee215400975b674f6da39b2f32514fe6bd54e747752eedca22bb840493b44a67df42a3639c5f901f", + "0xaffbbfac9c1ba7cbfa1839d2ae271dd6149869b75790bf103230637da41857fc326ef3552ff31c15bda0694080198143", + "0xa95d42aa7ef1962520845aa3688f2752d291926f7b0d73ea2ee24f0612c03b43f2b0fe3c9a9a99620ffc8d487b981bc2", + "0x914a266065caf64985e8c5b1cb2e3f4e3fe94d7d085a1881b1fefa435afef4e1b39a98551d096a62e4f5cc1a7f0fdc2e", + "0x81a0b4a96e2b75bc1bf2dbd165d58d55cfd259000a35504d1ffb18bc346a3e6f07602c683723864ffb980f840836fd8d", + "0x91c1556631cddd4c00b65b67962b39e4a33429029d311c8acf73a18600e362304fb68bccb56fde40f49e95b7829e0b87", + "0x8befbacc19e57f7c885d1b7a6028359eb3d80792fe13b92a8400df21ce48deb0bb60f2ddb50e3d74f39f85d7eab23adc", + "0x92f9458d674df6e990789690ec9ca73dacb67fc9255b58c417c555a8cc1208ace56e8e538f86ba0f3615573a0fbac00d", + "0xb4b1b3062512d6ae7417850c08c13f707d5838e43d48eb98dd4621baf62eee9e82348f80fe9b888a12874bfa538771f8", + "0xa13c4a3ac642ede37d9c883f5319e748d2b938f708c9d779714108a449b343f7b71a6e3ef4080fee125b416762920273", + "0xaf44983d5fc8cceee0551ef934e6e653f2d3efa385e5c8a27a272463a6f333e290378cc307c2b664eb923c78994e706e", + "0xa389fd6c59fe2b4031cc244e22d3991e541bd203dd5b5e73a6159e72df1ab41d49994961500dcde7989e945213184778", + "0x8d2141e4a17836c548de9598d7b298b03f0e6c73b7364979a411c464e0628e21cff6ac3d6decdba5d1c4909eff479761", + "0x980b22ef53b7bdf188a3f14bc51b0dbfdf9c758826daa3cbc1e3986022406a8aa9a6a79e400567120b88c67faa35ce5f", + "0xa28882f0a055f96df3711de5d0aa69473e71245f4f3e9aa944e9d1fb166e02caa50832e46da6d3a03b4801735fd01b29", + "0x8db106a37d7b88f5d995c126abb563934dd8de516af48e85695d02b1aea07f79217e3cdd03c6f5ca57421830186c772b", + "0xb5a7e50da0559a675c472f7dfaee456caab6695ab7870541b2be8c2b118c63752427184aad81f0e1afc61aef1f28c46f", + "0x9962118780e20fe291d10b64f28d09442a8e1b5cffd0f3dd68d980d0614050a626c616b44e9807fbee7accecae00686a", + "0xb38ddf33745e8d2ad6a991aefaf656a33c5f8cbe5d5b6b6fd03bd962153d8fd0e01b5f8f96d80ae53ab28d593ab1d4e7", + "0x857dc12c0544ff2c0c703761d901aba636415dee45618aba2e3454ff9cbc634a85c8b05565e88520ff9be2d097c8b2b1", + "0xa80d465c3f8cc63af6d74a6a5086b626c1cb4a8c0fee425964c3bd203d9d7094e299f81ce96d58afc20c8c9a029d9dae", + "0x89e1c8fbde8563763be483123a3ed702efac189c6d8ab4d16c85e74bbaf856048cc42d5d6e138633a38572ba5ec3f594", + "0x893a594cf495535f6d216508f8d03c317dcf03446668cba688da90f52d0111ac83d76ad09bf5ea47056846585ee5c791", + "0xaadbd8be0ae452f7f9450c7d2957598a20cbf10139a4023a78b4438172d62b18b0de39754dd2f8862dbd50a3a0815e53", + "0xae7d39670ecca3eb6db2095da2517a581b0e8853bdfef619b1fad9aacd443e7e6a40f18209fadd44038a55085c5fe8b2", + "0x866ef241520eacb6331593cfcb206f7409d2f33d04542e6e52cba5447934e02d44c471f6c9a45963f9307e9809ab91d9", + "0xb1a09911ad3864678f7be79a9c3c3eb5c84a0a45f8dcb52c67148f43439aeaaa9fd3ed3471276b7e588b49d6ebe3033a", + "0xadd07b7f0dbb34049cd8feeb3c18da5944bf706871cfd9f14ff72f6c59ad217ebb1f0258b13b167851929387e4e34cfe", + "0xae048892d5c328eefbdd4fba67d95901e3c14d974bfc0a1fc68155ca9f0d59e61d7ba17c6c9948b120cf35fd26e6fee9", + "0x9185b4f3b7da0ddb4e0d0f09b8a9e0d6943a4611e43f13c3e2a767ed8592d31e0ba3ebe1914026a3627680274291f6e5", + "0xa9c022d4e37b0802284ce3b7ee9258628ab4044f0db4de53d1c3efba9de19d15d65cc5e608dbe149c21c2af47d0b07b5", + "0xb24dbd5852f8f24921a4e27013b6c3fa8885b973266cb839b9c388efad95821d5d746348179dcc07542bd0d0aefad1ce", + "0xb5fb4f279300876a539a27a441348764908bc0051ebd66dc51739807305e73db3d2f6f0f294ffb91b508ab150eaf8527", + "0xace50841e718265b290c3483ed4b0fdd1175338c5f1f7530ae9a0e75d5f80216f4de37536adcbc8d8c95982e88808cd0", + "0xb19cadcde0f63bd1a9c24bd9c2806f53c14c0b9735bf351601498408ba503ddbd2037c891041cbba47f58b8c483f3b21", + "0xb6061e63558d312eb891b97b39aa552fa218568d79ee26fe6dd5b864aea9e3216d8f2e2f3b093503be274766dac41426", + "0x89730fdb2876ab6f0fe780d695f6e12090259027e789b819956d786e977518057e5d1d7f5ab24a3ae3d5d4c97773bd2b", + "0xb6fa841e81f9f2cad0163a02a63ae96dc341f7ae803b616efc6e1da2fbea551c1b96b11ad02c4afbdf6d0cc9f23da172", + "0x8fb66187182629c861ddb6896d7ed3caf2ad050c3dba8ab8eb0d7a2c924c3d44c48d1a148f9e33fb1f061b86972f8d21", + "0x86022ac339c1f84a7fa9e05358c1a5b316b4fc0b83dbe9c8c7225dc514f709d66490b539359b084ce776e301024345fa", + "0xb50b9c321468da950f01480bb62b6edafd42f83c0001d6e97f2bd523a1c49a0e8574fb66380ea28d23a7c4d54784f9f0", + "0xa31c05f7032f30d1dac06678be64d0250a071fd655e557400e4a7f4c152be4d5c7aa32529baf3e5be7c4bd49820054f6", + "0xb95ac0848cd322684772119f5b682d90a66bbf9dac411d9d86d2c34844bbd944dbaf8e47aa41380455abd51687931a78", + "0xae4a6a5ce9553b65a05f7935e61e496a4a0f6fd8203367a2c627394c9ce1e280750297b74cdc48fd1d9a31e93f97bef4", + "0xa22daf35f6e9b05e52e0b07f7bd1dbbebd2c263033fb0e1b2c804e2d964e2f11bc0ece6aca6af079dd3a9939c9c80674", + "0x902150e0cb1f16b9b59690db35281e28998ce275acb313900da8b2d8dfd29fa1795f8ca3ff820c31d0697de29df347c1", + "0xb17b5104a5dc665cdd7d47e476153d715eb78c6e5199303e4b5445c21a7fa7cf85fe7cfd08d7570f4e84e579b005428c", + "0xa03f49b81c15433f121680aa02d734bb9e363af2156654a62bcb5b2ba2218398ccb0ff61104ea5d7df5b16ea18623b1e", + "0x802101abd5d3c88876e75a27ffc2f9ddcce75e6b24f23dba03e5201281a7bd5cc7530b6a003be92d225093ca17d3c3bb", + "0xa4d183f63c1b4521a6b52226fc19106158fc8ea402461a5cccdaa35fee93669df6a8661f45c1750cd01308149b7bf08e", + "0x8d17c22e0c8403b69736364d460b3014775c591032604413d20a5096a94d4030d7c50b9fe3240e31d0311efcf9816a47", + "0x947225acfcce5992eab96276f668c3cbe5f298b90a59f2bb213be9997d8850919e8f496f182689b5cbd54084a7332482", + "0x8df6f4ed216fc8d1905e06163ba1c90d336ab991a18564b0169623eb39b84e627fa267397da15d3ed754d1f3423bff07", + "0x83480007a88f1a36dea464c32b849a3a999316044f12281e2e1c25f07d495f9b1710b4ba0d88e9560e72433addd50bc2", + "0xb3019d6e591cf5b33eb972e49e06c6d0a82a73a75d78d383dd6f6a4269838289e6e07c245f54fed67f5c9bb0fd5e1c5f", + "0x92e8ce05e94927a9fb02debadb99cf30a26172b2705003a2c0c47b3d8002bf1060edb0f6a5750aad827c98a656b19199", + "0xac2aff801448dbbfc13cca7d603fd9c69e82100d997faf11f465323b97255504f10c0c77401e4d1890339d8b224f5803", + "0xb0453d9903d08f508ee27e577445dc098baed6cde0ac984b42e0f0efed62760bd58d5816cf1e109d204607b7b175e30c", + "0xae68dc4ba5067e825d46d2c7c67f1009ceb49d68e8d3e4c57f4bcd299eb2de3575d42ea45e8722f8f28497a6e14a1cfe", + "0xb22486c2f5b51d72335ce819bbafb7fa25eb1c28a378a658f13f9fc79cd20083a7e573248d911231b45a5cf23b561ca7", + "0x89d1201d1dbd6921867341471488b4d2fd0fc773ae1d4d074c78ae2eb779a59b64c00452c2a0255826fca6b3d03be2b1", + "0xa2998977c91c7a53dc6104f5bc0a5b675e5350f835e2f0af69825db8af4aeb68435bdbcc795f3dd1f55e1dd50bc0507f", + "0xb0be4937a925b3c05056ed621910d535ccabf5ab99fd3b9335080b0e51d9607d0fd36cb5781ff340018f6acfca4a9736", + "0xaea145a0f6e0ba9df8e52e84bb9c9de2c2dc822f70d2724029b153eb68ee9c17de7d35063dcd6a39c37c59fdd12138f7", + "0x91cb4545d7165ee8ffbc74c874baceca11fdebbc7387908d1a25877ca3c57f2c5def424dab24148826832f1e880bede0", + "0xb3b579cb77573f19c571ad5eeeb21f65548d7dff9d298b8d7418c11f3e8cd3727c5b467f013cb87d6861cfaceee0d2e3", + "0xb98a1eeec2b19fecc8378c876d73645aa52fb99e4819903735b2c7a885b242787a30d1269a04bfb8573d72d9bbc5f0f0", + "0x940c1f01ed362bd588b950c27f8cc1d52276c71bb153d47f07ec85b038c11d9a8424b7904f424423e714454d5e80d1cd", + "0xaa343a8ecf09ce11599b8cf22f7279cf80f06dbf9f6d62cb05308dbbb39c46fd0a4a1240b032665fbb488a767379b91b", + "0x87c3ac72084aca5974599d3232e11d416348719e08443acaba2b328923af945031f86432e170dcdd103774ec92e988c9", + "0x91d6486eb5e61d2b9a9e742c20ec974a47627c6096b3da56209c2b4e4757f007e793ebb63b2b246857c9839b64dc0233", + "0xaebcd3257d295747dd6fc4ff910d839dd80c51c173ae59b8b2ec937747c2072fa85e3017f9060aa509af88dfc7529481", + "0xb3075ba6668ca04eff19efbfa3356b92f0ab12632dcda99cf8c655f35b7928c304218e0f9799d68ef9f809a1492ff7db", + "0x93ba7468bb325639ec2abd4d55179c69fd04eaaf39fc5340709227bbaa4ad0a54ea8b480a1a3c8d44684e3be0f8d1980", + "0xa6aef86c8c0d92839f38544d91b767c582568b391071228ff5a5a6b859c87bf4f81a7d926094a4ada1993ddbd677a920", + "0x91dcd6d14207aa569194aa224d1e5037b999b69ade52843315ca61ba26abe9a76412c9e88259bc5cf5d7b95b97d9c3bc", + "0xb3b483d31c88f78d49bd065893bc1e3d2aa637e27dedb46d9a7d60be7660ce7a10aaaa7deead362284a52e6d14021178", + "0x8e5730070acf8371461ef301cc4523e8e672aa0e3d945d438a0e0aa6bdf8cb9c685dcf38df429037b0c8aff3955c6f5b", + "0xb8c6d769890a8ee18dc4f9e917993315877c97549549b34785a92543cbeec96a08ae3a28d6e809c4aacd69de356c0012", + "0x95ca86cd384eaceaa7c077c5615736ca31f36824bd6451a16142a1edc129fa42b50724aeed7c738f08d7b157f78b569e", + "0x94df609c6d71e8eee7ab74226e371ccc77e01738fe0ef1a6424435b4570fe1e5d15797b66ed0f64eb88d4a3a37631f0e", + "0x89057b9783212add6a0690d6bb99097b182738deff2bd9e147d7fd7d6c8eacb4c219923633e6309ad993c24572289901", + "0x83a0f9f5f265c5a0e54defa87128240235e24498f20965009fef664f505a360b6fb4020f2742565dfc7746eb185bcec0", + "0x91170da5306128931349bc3ed50d7df0e48a68b8cc8420975170723ac79d8773e4fa13c5f14dc6e3fafcad78379050b1", + "0xb7178484d1b55f7e56a4cc250b6b2ec6040437d96bdfddfa7b35ed27435860f3855c2eb86c636f2911b012eb83b00db8", + "0xac0b00c4322d1e4208e09cd977b4e54d221133ff09551f75b32b0b55d0e2be80941dda26257b0e288c162e63c7e9cf68", + "0x9690ed9e7e53ed37ff362930e4096b878b12234c332fd19d5d064824084245952eda9f979e0098110d6963e468cf513e", + "0xb6fa547bb0bb83e5c5be0ed462a8783fba119041c136a250045c09d0d2af330c604331e7de960df976ff76d67f8000cd", + "0x814603907c21463bcf4e59cfb43066dfe1a50344ae04ef03c87c0f61b30836c3f4dea0851d6fa358c620045b7f9214c8", + "0x9495639e3939fad2a3df00a88603a5a180f3c3a0fe4d424c35060e2043e0921788003689887b1ed5be424d9a89bb18bb", + "0xaba4c02d8d57f2c92d5bc765885849e9ff8393d6554f5e5f3e907e5bfac041193a0d8716d7861104a4295d5a03c36b03", + "0x8ead0b56c1ca49723f94a998ba113b9058059321da72d9e395a667e6a63d5a9dac0f5717cec343f021695e8ced1f72af", + "0xb43037f7e3852c34ed918c5854cd74e9d5799eeddfe457d4f93bb494801a064735e326a76e1f5e50a339844a2f4a8ec9", + "0x99db8422bb7302199eb0ff3c3d08821f8c32f53a600c5b6fb43e41205d96adae72be5b460773d1280ad1acb806af9be8", + "0x8a9be08eae0086c0f020838925984df345c5512ff32e37120b644512b1d9d4fecf0fd30639ca90fc6cf334a86770d536", + "0x81b43614f1c28aa3713a309a88a782fb2bdfc4261dd52ddc204687791a40cf5fd6a263a8179388596582cccf0162efc2", + "0xa9f3a8b76912deb61d966c75daf5ddb868702ebec91bd4033471c8e533183df548742a81a2671de5be63a502d827437d", + "0x902e2415077f063e638207dc7e14109652e42ab47caccd6204e2870115791c9defac5425fd360b37ac0f7bd8fe7011f8", + "0xaa18e4fdc1381b59c18503ae6f6f2d6943445bd00dd7d4a2ad7e5adad7027f2263832690be30d456e6d772ad76f22350", + "0xa348b40ba3ba7d81c5d4631f038186ebd5e5f314f1ea737259151b07c3cc8cf0c6ed4201e71bcc1c22fefda81a20cde6", + "0xaa1306f7ac1acbfc47dc6f7a0cb6d03786cec8c8dc8060388ccda777bca24bdc634d03e53512c23dba79709ff64f8620", + "0x818ccfe46e700567b7f3eb400e5a35f6a5e39b3db3aa8bc07f58ace35d9ae5a242faf8dbccd08d9a9175bbce15612155", + "0xb7e3da2282b65dc8333592bb345a473f03bd6df69170055fec60222de9897184536bf22b9388b08160321144d0940279", + "0xa4d976be0f0568f4e57de1460a1729129252b44c552a69fceec44e5b97c96c711763360d11f9e5bf6d86b4976bf40d69", + "0x85d185f0397c24c2b875b09b6328a23b87982b84ee880f2677a22ff4c9a1ba9f0fea000bb3f7f66375a00d98ebafce17", + "0xb4ccbb8c3a2606bd9b87ce022704663af71d418351575f3b350d294f4efc68c26f9a2ce49ff81e6ff29c3b63d746294e", + "0x93ffd3265fddb63724dfde261d1f9e22f15ecf39df28e4d89e9fea03221e8e88b5dd9b77628bacaa783c6f91802d47cc", + "0xb1fd0f8d7a01378e693da98d03a2d2fda6b099d03454b6f2b1fa6472ff6bb092751ce6290059826b74ac0361eab00e1e", + "0xa89f440c71c561641589796994dd2769616b9088766e983c873fae0716b95c386c8483ab8a4f367b6a68b72b7456dd32", + "0xaf4fe92b01d42d03dd5d1e7fa55e96d4bbcb7bf7d4c8c197acd16b3e0f3455807199f683dcd263d74547ef9c244b35cc", + "0xa8227f6e0a344dfe76bfbe7a1861be32c4f4bed587ccce09f9ce2cf481b2dda8ae4f566154bc663d15f962f2d41761bd", + "0xa7b361663f7495939ed7f518ba45ea9ff576c4e628995b7aea026480c17a71d63fc2c922319f0502eb7ef8f14a406882", + "0x8ddcf382a9f39f75777160967c07012cfa89e67b19714a7191f0c68eaf263935e5504e1104aaabd0899348c972a8d3c6", + "0x98c95b9f6f5c91f805fb185eedd06c6fc4457d37dd248d0be45a6a168a70031715165ea20606245cbdf8815dc0ac697f", + "0x805b44f96e001e5909834f70c09be3efcd3b43632bcac5b6b66b6d227a03a758e4b1768ce2a723045681a1d34562aaeb", + "0xb0e81b07cdc45b3dca60882676d9badb99f25c461b7efe56e3043b80100bb62d29e1873ae25eb83087273160ece72a55", + "0xb0c53f0abe78ee86c7b78c82ae1f7c070bb0b9c45c563a8b3baa2c515d482d7507bb80771e60b38ac13f78b8af92b4a9", + "0xa7838ef6696a9e4d2e5dfd581f6c8d6a700467e8fd4e85adabb5f7a56f514785dd4ab64f6f1b48366f7d94728359441b", + "0x88c76f7700a1d23c30366a1d8612a796da57b2500f97f88fdf2d76b045a9d24e7426a8ffa2f4e86d3046937a841dad58", + "0xad8964baf98c1f02e088d1d9fcb3af6b1dfa44cdfe0ed2eae684e7187c33d3a3c28c38e8f4e015f9c04d451ed6f85ff6", + "0x90e9d00a098317ececaa9574da91fc149eda5b772dedb3e5a39636da6603aa007804fa86358550cfeff9be5a2cb7845e", + "0xa56ff4ddd73d9a6f5ab23bb77efa25977917df63571b269f6a999e1ad6681a88387fcc4ca3b26d57badf91b236503a29", + "0x97ad839a6302c410a47e245df84c01fb9c4dfef86751af3f9340e86ff8fc3cd52fa5ff0b9a0bd1d9f453e02ca80658a6", + "0xa4c8c44cbffa804129e123474854645107d1f0f463c45c30fd168848ebea94880f7c0c5a45183e9eb837f346270bdb35", + "0xa72e53d0a1586d736e86427a93569f52edd2f42b01e78aee7e1961c2b63522423877ae3ac1227a2cf1e69f8e1ff15bc3", + "0x8559f88a7ef13b4f09ac82ae458bbae6ab25671cfbf52dae7eac7280d6565dd3f0c3286aec1a56a8a16dc3b61d78ce47", + "0x8221503f4cdbed550876c5dc118a3f2f17800c04e8be000266633c83777b039a432d576f3a36c8a01e8fd18289ebc10b", + "0x99bfbe5f3e46d4d898a578ba86ed26de7ed23914bd3bcdf3c791c0bcd49398a52419077354a5ab75cea63b6c871c6e96", + "0xaa134416d8ff46f2acd866c1074af67566cfcf4e8be8d97329dfa0f603e1ff208488831ce5948ac8d75bfcba058ddcaa", + "0xb02609d65ebfe1fe8e52f21224a022ea4b5ea8c1bd6e7b9792eed8975fc387cdf9e3b419b8dd5bcce80703ab3a12a45f", + "0xa4f14798508698fa3852e5cac42a9db9797ecee7672a54988aa74037d334819aa7b2ac7b14efea6b81c509134a6b7ad2", + "0x884f01afecbcb987cb3e7c489c43155c416ed41340f61ecb651d8cba884fb9274f6d9e7e4a46dd220253ae561614e44c", + "0xa05523c9e71dce1fe5307cc71bd721feb3e1a0f57a7d17c7d1c9fb080d44527b7dbaa1f817b1af1c0b4322e37bc4bb1e", + "0x8560aec176a4242b39f39433dd5a02d554248c9e49d3179530815f5031fee78ba9c71a35ceeb2b9d1f04c3617c13d8f0", + "0x996aefd402748d8472477cae76d5a2b92e3f092fc834d5222ae50194dd884c9fb8b6ed8e5ccf8f6ed483ddbb4e80c747", + "0x8fd09900320000cbabc40e16893e2fcf08815d288ec19345ad7b6bb22f7d78a52b6575a3ca1ca2f8bc252d2eafc928ec", + "0x939e51f73022bc5dc6862a0adf8fb8a3246b7bfb9943cbb4b27c73743926cc20f615a036c7e5b90c80840e7f1bfee0e7", + "0xa0a6258700cadbb9e241f50766573bf9bdb7ad380b1079dc3afb4054363d838e177b869cad000314186936e40359b1f2", + "0x972699a4131c8ed27a2d0e2104d54a65a7ff1c450ad9da3a325c662ab26869c21b0a84d0700b98c8b5f6ce3b746873d7", + "0xa454c7fe870cb8aa6491eafbfb5f7872d6e696033f92e4991d057b59d70671f2acdabef533e229878b60c7fff8f748b1", + "0xa167969477214201f09c79027b10221e4707662e0c0fde81a0f628249f2f8a859ce3d30a7dcc03b8ecca8f7828ad85c7", + "0x8ff6b7265175beb8a63e1dbf18c9153fb2578c207c781282374f51b40d57a84fd2ef2ea2b9c6df4a54646788a62fd17f", + "0xa3d7ebeccde69d73d8b3e76af0da1a30884bb59729503ff0fb0c3bccf9221651b974a6e72ea33b7956fc3ae758226495", + "0xb71ef144c9a98ce5935620cb86c1590bd4f48e5a2815d25c0cdb008fde628cf628c31450d3d4f67abbfeb16178a74cfd", + "0xb5e0a16d115134f4e2503990e3f2035ed66b9ccf767063fe6747870d97d73b10bc76ed668550cb82eedc9a2ca6f75524", + "0xb30ffaaf94ee8cbc42aa2c413175b68afdb207dbf351fb20be3852cb7961b635c22838da97eaf43b103aff37e9e725cc", + "0x98aa7d52284f6c1f22e272fbddd8c8698cf8f5fbb702d5de96452141fafb559622815981e50b87a72c2b1190f59a7deb", + "0x81fbacda3905cfaf7780bb4850730c44166ed26a7c8d07197a5d4dcd969c09e94a0461638431476c16397dd7bdc449f9", + "0x95e47021c1726eac2e5853f570d6225332c6e48e04c9738690d53e07c6b979283ebae31e2af1fc9c9b3e59f87e5195b1", + "0xac024a661ba568426bb8fce21780406537f518075c066276197300841e811860696f7588188bc01d90bace7bc73d56e3", + "0xa4ebcaf668a888dd404988ab978594dee193dad2d0aec5cdc0ccaf4ec9a7a8228aa663db1da8ddc52ec8472178e40c32", + "0xa20421b8eaf2199d93b083f2aff37fb662670bd18689d046ae976d1db1fedd2c2ff897985ecc6277b396db7da68bcb27", + "0x8bc33d4b40197fd4d49d1de47489d10b90d9b346828f53a82256f3e9212b0cbc6930b895e879da9cec9fedf026aadb3e", + "0xaaafdd1bec8b757f55a0433eddc0a39f818591954fd4e982003437fcceb317423ad7ee74dbf17a2960380e7067a6b4e2", + "0xaad34277ebaed81a6ec154d16736866f95832803af28aa5625bf0461a71d02b1faba02d9d9e002be51c8356425a56867", + "0x976e9c8b150d08706079945bd0e84ab09a648ecc6f64ded9eb5329e57213149ae409ae93e8fbd8eda5b5c69f5212b883", + "0x8097fae1653247d2aed4111533bc378171d6b2c6d09cbc7baa9b52f188d150d645941f46d19f7f5e27b7f073c1ebd079", + "0x83905f93b250d3184eaba8ea7d727c4464b6bdb027e5cbe4f597d8b9dc741dcbea709630bd4fd59ce24023bec32fc0f3", + "0x8095030b7045cff28f34271386e4752f9a9a0312f8df75de4f424366d78534be2b8e1720a19cb1f9a2d21105d790a225", + "0xa7b7b73a6ae2ed1009c49960374b0790f93c74ee03b917642f33420498c188a169724945a975e5adec0a1e83e07fb1b2", + "0x856a41c54df393b6660b7f6354572a4e71c8bfca9cabaffb3d4ef2632c015e7ee2bc10056f3eccb3dbed1ad17d939178", + "0xa8f7a55cf04b38cd4e330394ee6589da3a07dc9673f74804fdf67b364e0b233f14aec42e783200a2e4666f7c5ff62490", + "0x82c529f4e543c6bca60016dc93232c115b359eaee2798a9cf669a654b800aafe6ab4ba58ea8b9cdda2b371c8d62fa845", + "0x8caab020c1baddce77a6794113ef1dfeafc5f5000f48e97f4351b588bf02f1f208101745463c480d37f588d5887e6d8c", + "0x8fa91b3cc400f48b77b6fd77f3b3fbfb3f10cdff408e1fd22d38f77e087b7683adad258804409ba099f1235b4b4d6fea", + "0x8aa02787663d6be9a35677d9d8188b725d5fcd770e61b11b64e3def8808ea5c71c0a9afd7f6630c48634546088fcd8e2", + "0xb5635b7b972e195cab878b97dea62237c7f77eb57298538582a330b1082f6207a359f2923864630136d8b1f27c41b9aa", + "0x8257bb14583551a65975946980c714ecd6e5b629672bb950b9caacd886fbd22704bc9e3ba7d30778adab65dc74f0203a", + "0xab5fe1cd12634bfa4e5c60d946e2005cbd38f1063ec9a5668994a2463c02449a0a185ef331bd86b68b6e23a8780cb3ba", + "0xa7d3487da56cda93570cc70215d438204f6a2709bfb5fda6c5df1e77e2efc80f4235c787e57fbf2c74aaff8cbb510a14", + "0xb61cff7b4c49d010e133319fb828eb900f8a7e55114fc86b39c261a339c74f630e1a7d7e1350244ada566a0ff3d46c4b", + "0x8d4d1d55d321d278db7a85522ccceca09510374ca81d4d73e3bb5249ace7674b73900c35a531ec4fa6448fabf7ad00dc", + "0x966492248aee24f0f56c8cfca3c8ec6ba3b19abb69ae642041d4c3be8523d22c65c4dafcab4c58989ccc4e0bd2f77919", + "0xb20c320a90cb220b86e1af651cdc1e21315cd215da69f6787e28157172f93fc8285dcd59b039c626ed8ca4633cba1a47", + "0xaae9e6b22f018ceb5c0950210bb8182cb8cb61014b7e14581a09d36ebd1bbfebdb2b82afb7fdb0cf75e58a293d9c456d", + "0x875547fb67951ad37b02466b79f0c9b985ccbc500cfb431b17823457dc79fb9597ec42cd9f198e15523fcd88652e63a4", + "0x92afce49773cb2e20fb21e4f86f18e0959ebb9c33361547ddb30454ee8e36b1e234019cbdca0e964cb292f7f77df6b90", + "0x8af85343dfe1821464c76ba11c216cbef697b5afc69c4d821342e55afdac047081ec2e3f7b09fc14b518d9a23b78c003", + "0xb7de4a1648fd63f3a918096ea669502af5357438e69dac77cb8102b6e6c15c76e033cfaa80dafc806e535ede5c1a20aa", + "0xac80e9b545e8bd762951d96c9ce87f629d01ffcde07efc2ef7879ca011f1d0d8a745abf26c9d452541008871304fac00", + "0xa4cf0f7ed724e481368016c38ea5816698a5f68eb21af4d3c422d2ba55f96a33e427c2aa40de1b56a7cfac7f7cf43ab0", + "0x899b0a678bb2db2cae1b44e75a661284844ebcdd87abf308fedeb2e4dbe5c5920c07db4db7284a7af806a2382e8b111a", + "0xaf0588a2a4afce2b1b13c1230816f59e8264177e774e4a341b289a101dcf6af813638fed14fb4d09cb45f35d5d032609", + "0xa4b8df79e2be76e9f5fc5845f06fe745a724cf37c82fcdb72719b77bdebea3c0e763f37909373e3a94480cc5e875cba0", + "0x83e42c46d88930c8f386b19fd999288f142d325e2ebc86a74907d6d77112cb0d449bc511c95422cc810574031a8cbba9", + "0xb5e39534070de1e5f6e27efbdd3dc917d966c2a9b8cf2d893f964256e95e954330f2442027dc148c776d63a95bcde955", + "0x958607569dc28c075e658cd4ae3927055c6bc456eef6212a6fea8205e48ed8777a8064f584cda38fe5639c371e2e7fba", + "0x812adf409fa63575113662966f5078a903212ffb65c9b0bbe62da0f13a133443a7062cb8fd70f5e5dd5559a32c26d2c8", + "0xa679f673e5ce6a3cce7fa31f22ee3785e96bcb55e5a776e2dd3467bef7440e3555d1a9b87cb215e86ee9ed13a090344b", + "0xafedbb34508b159eb25eb2248d7fe328f86ef8c7d84c62d5b5607d74aae27cc2cc45ee148eb22153b09898a835c58df4", + "0xb75505d4f6b67d31e665cfaf5e4acdb5838ae069166b7fbcd48937c0608a59e40a25302fcc1873d2e81c1782808c70f0", + "0xb62515d539ec21a155d94fc00ea3c6b7e5f6636937bce18ed5b618c12257fb82571886287fd5d1da495296c663ebc512", + "0xab8e1a9446bbdd588d1690243b1549d230e6149c28f59662b66a8391a138d37ab594df38e7720fae53217e5c3573b5be", + "0xb31e8abf4212e03c3287bb2c0a153065a7290a16764a0bac8f112a72e632185a654bb4e88fdd6053e6c7515d9719fadb", + "0xb55165477fe15b6abd2d0f4fddaa9c411710dcc4dd712daba3d30e303c9a3ee5415c256f9dc917ecf18c725b4dbab059", + "0xa0939d4f57cacaae549b78e87cc234de4ff6a35dc0d9cd5d7410abc30ebcd34c135e008651c756e5a9d2ca79c40ef42b", + "0x8cf10e50769f3443340844aad4d56ec790850fed5a41fcbd739abac4c3015f0a085a038fbe7fae9f5ad899cce5069f6b", + "0x924055e804d82a99ea4bb160041ea4dc14b568abf379010bc1922fde5d664718c31d103b8b807e3a1ae809390e708c73", + "0x8ec0f9d26f71b0f2e60a179e4fd1778452e2ffb129d50815e5d7c7cb9415fa69ae5890578086e8ef6bfde35ad2a74661", + "0x98c7f12b15ec4426b59f737f73bf5faea4572340f4550b7590dfb7f7ffedb2372e3e555977c63946d579544c53210ad0", + "0x8a935f7a955c78f69d66f18eee0092e5e833fa621781c9581058e219af4d7ceee48b84e472e159dda6199715fb2f9acf", + "0xb78d4219f95a2dbfaa7d0c8a610c57c358754f4f43c2af312ab0fe8f10a5f0177e475332fb8fd23604e474fc2abeb051", + "0x8d086a14803392b7318c28f1039a17e3cfdcece8abcaca3657ec3d0ac330842098a85c0212f889fabb296dfb133ce9aa", + "0xa53249f417aac82f2c2a50c244ce21d3e08a5e5a8bd33bec2a5ab0d6cd17793e34a17edfa3690899244ce201e2fb9986", + "0x8619b0264f9182867a1425be514dc4f1ababc1093138a728a28bd7e4ecc99b9faaff68c23792264bc6e4dce5f52a5c52", + "0x8c171edbbbde551ec19e31b2091eb6956107dd9b1f853e1df23bff3c10a3469ac77a58335eee2b79112502e8e163f3de", + "0xa9d19ec40f0ca07c238e9337c6d6a319190bdba2db76fb63902f3fb459aeeb50a1ac30db5b25ee1b4201f3ca7164a7f4", + "0xb9c6ec14b1581a03520b8d2c1fbbc31fb8ceaef2c0f1a0d0080b6b96e18442f1734bea7ef7b635d787c691de4765d469", + "0x8cb437beb4cfa013096f40ccc169a713dc17afee6daa229a398e45fd5c0645a9ad2795c3f0cd439531a7151945d7064d", + "0xa6e8740cc509126e146775157c2eb278003e5bb6c48465c160ed27888ca803fa12eee1f6a8dd7f444f571664ed87fdc1", + "0xb75c1fecc85b2732e96b3f23aefb491dbd0206a21d682aee0225838dc057d7ed3b576176353e8e90ae55663f79e986e4", + "0xad8d249b0aea9597b08358bce6c77c1fd552ef3fbc197d6a1cfe44e5e6f89b628b12a6fb04d5dcfcbacc51f46e4ae7bb", + "0xb998b2269932cbd58d04b8e898d373ac4bb1a62e8567484f4f83e224061bc0f212459f1daae95abdbc63816ae6486a55", + "0x827988ef6c1101cddc96b98f4a30365ff08eea2471dd949d2c0a9b35c3bbfa8c07054ad1f4c88c8fbf829b20bb5a9a4f", + "0x8692e638dd60babf7d9f2f2d2ce58e0ac689e1326d88311416357298c6a2bffbfebf55d5253563e7b3fbbf5072264146", + "0xa685d75b91aea04dbc14ab3c1b1588e6de96dae414c8e37b8388766029631b28dd860688079b12d09cd27f2c5af11adf", + "0xb57eced93eec3371c56679c259b34ac0992286be4f4ff9489d81cf9712403509932e47404ddd86f89d7c1c3b6391b28c", + "0xa1c8b4e42ebcbd8927669a97f1b72e236fb19249325659e72be7ddaaa1d9e81ca2abb643295d41a8c04a2c01f9c0efd7", + "0x877c33de20d4ed31674a671ba3e8f01a316581e32503136a70c9c15bf0b7cb7b1cba6cd4eb641fad165fb3c3c6c235fd", + "0xa2a469d84ec478da40838f775d11ad38f6596eb41caa139cc190d6a10b5108c09febae34ffdafac92271d2e73c143693", + "0x972f817caedb254055d52e963ed28c206848b6c4cfdb69dbc961c891f8458eaf582a6d4403ce1177d87bc2ea410ef60a", + "0xaccbd739e138007422f28536381decc54bb6bd71d93edf3890e54f9ef339f83d2821697d1a4ac1f5a98175f9a9ecb9b5", + "0x8940f8772e05389f823b62b3adc3ed541f91647f0318d7a0d3f293aeeb421013de0d0a3664ea53dd24e5fbe02d7efef6", + "0x8ecce20f3ef6212edef07ec4d6183fda8e0e8cad2c6ccd0b325e75c425ee1faba00b5c26b4d95204238931598d78f49d", + "0x97cc72c36335bd008afbed34a3b0c7225933faba87f7916d0a6d2161e6f82e0cdcda7959573a366f638ca75d30e9dab1", + "0x9105f5de8699b5bdb6bd3bb6cc1992d1eac23929c29837985f83b22efdda92af64d9c574aa9640475087201bbbe5fd73", + "0x8ffb33c4f6d05c413b9647eb6933526a350ed2e4278ca2ecc06b0e8026d8dbe829c476a40e45a6df63a633090a3f82ef", + "0x8bfc6421fdc9c2d2aaa68d2a69b1a2728c25b84944cc3e6a57ff0c94bfd210d1cbf4ff3f06702d2a8257024d8be7de63", + "0xa80e1dc1dddfb41a70220939b96dc6935e00b32fb8be5dff4eed1f1c650002ff95e4af481c43292e3827363b7ec4768a", + "0x96f714ebd54617198bd636ba7f7a7f8995a61db20962f2165078d9ed8ee764d5946ef3cbdc7ebf8435bb8d5dd4c1deac", + "0x8cdb0890e33144d66391d2ae73f5c71f5a861f72bc93bff6cc399fc25dd1f9e17d8772592b44593429718784802ac377", + "0x8ccf9a7f80800ee770b92add734ed45a73ecc31e2af0e04364eefc6056a8223834c7c0dc9dfc52495bdec6e74ce69994", + "0xaa0875f423bd68b5f10ba978ddb79d3b96ec093bfbac9ff366323193e339ed7c4578760fb60f60e93598bdf1e5cc4995", + "0xa9214f523957b59c7a4cb61a40251ad72aba0b57573163b0dc0f33e41d2df483fb9a1b85a5e7c080e9376c866790f8cb", + "0xb6224b605028c6673a536cc8ff9aeb94e7a22e686fda82cf16068d326469172f511219b68b2b3affb7933af0c1f80d07", + "0xb6d58968d8a017c6a34e24c2c09852f736515a2c50f37232ac6b43a38f8faa7572cc31dade543b594b61b5761c4781d0", + "0x8a97cefe5120020c38deeb861d394404e6c993c6cbd5989b6c9ebffe24f46ad11b4ba6348e2991cbf3949c28cfc3c99d", + "0x95bf046f8c3a9c0ce2634be4de3713024daec3fc4083e808903b25ce3ac971145af90686b451efcc72f6b22df0216667", + "0xa6a4e2f71b8fa28801f553231eff2794c0f10d12e7e414276995e21195abc9c2983a8997e41af41e78d19ff6fbb2680b", + "0x8e5e62a7ca9c2f58ebaab63db2ff1fb1ff0877ae94b7f5e2897f273f684ae639dff44cc65718f78a9c894787602ab26a", + "0x8542784383eec4f565fcb8b9fc2ad8d7a644267d8d7612a0f476fc8df3aff458897a38003d506d24142ad18f93554f2b", + "0xb7db68ba4616ea072b37925ec4fb39096358c2832cc6d35169e032326b2d6614479f765ae98913c267105b84afcb9bf2", + "0x8b31dbb9457d23d416c47542c786e07a489af35c4a87dadb8ee91bea5ac4a5315e65625d78dad2cf8f9561af31b45390", + "0xa8545a1d91ac17257732033d89e6b7111db8242e9c6ebb0213a88906d5ef407a2c6fdb444e29504b06368b6efb4f4839", + "0xb1bd85d29ebb28ccfb05779aad8674906b267c2bf8cdb1f9a0591dd621b53a4ee9f2942687ee3476740c0b4a7621a3ae", + "0xa2b54534e152e46c50d91fff03ae9cd019ff7cd9f4168b2fe7ac08ef8c3bbc134cadd3f9d6bd33d20ae476c2a8596c8a", + "0xb19b571ff4ae3e9f5d95acda133c455e72c9ea9973cae360732859836c0341c4c29ab039224dc5bc3deb824e031675d8", + "0x940b5f80478648bac025a30f3efeb47023ce20ee98be833948a248bca6979f206bb28fc0f17b90acf3bb4abd3d14d731", + "0x8f106b40588586ac11629b96d57808ad2808915d89539409c97414aded90b4ff23286a692608230a52bff696055ba5d6", + "0xae6bda03aa10da3d2abbc66d764ca6c8d0993e7304a1bdd413eb9622f3ca1913baa6da1e9f4f9e6cf847f14f44d6924d", + "0xa18e7796054a340ef826c4d6b5a117b80927afaf2ebd547794c400204ae2caf277692e2eabb55bc2f620763c9e9da66d", + "0x8d2d25180dc2c65a4844d3e66819ccfcf48858f0cc89e1c77553b463ec0f7feb9a4002ce26bc618d1142549b9850f232", + "0x863f413a394de42cc8166c1c75d513b91d545fff1de6b359037a742c70b008d34bf8e587afa2d62c844d0c6f0ea753e7", + "0x83cd0cf62d63475e7fcad18a2e74108499cdbf28af2113cfe005e3b5887794422da450b1944d0a986eb7e1f4c3b18f25", + "0xb4f8b350a6d88fea5ab2e44715a292efb12eb52df738c9b2393da3f1ddee68d0a75b476733ccf93642154bceb208f2b8", + "0xb3f52aaa4cd4221cb9fc45936cc67fd3864bf6d26bf3dd86aa85aa55ecfc05f5e392ecce5e7cf9406b4b1c4fce0398c8", + "0xb33137084422fb643123f40a6df2b498065e65230fc65dc31791c330e898c51c3a65ff738930f32c63d78f3c9315f85b", + "0x91452bfa75019363976bb7337fe3a73f1c10f01637428c135536b0cdc7da5ce558dae3dfc792aa55022292600814a8ef", + "0xad6ba94c787cd4361ca642c20793ea44f1f127d4de0bb4a77c7fbfebae0fcadbf28e2cb6f0c12c12a07324ec8c19761d", + "0x890aa6248b17f1501b0f869c556be7bf2b1d31a176f9978bb97ab7a6bd4138eed32467951c5ef1871944b7f620542f43", + "0x82111db2052194ee7dd22ff1eafffac0443cf969d3762cceae046c9a11561c0fdce9c0711f88ac01d1bed165f8a7cee3", + "0xb1527b71df2b42b55832f72e772a466e0fa05743aacc7814f4414e4bcc8d42a4010c9e0fd940e6f254cafedff3cd6543", + "0x922370fa49903679fc565f09c16a5917f8125e72acfeb060fcdbadbd1644eb9f4016229756019c93c6d609cda5d5d174", + "0xaa4c7d98a96cab138d2a53d4aee8ebff6ef903e3b629a92519608d88b3bbd94de5522291a1097e6acf830270e64c8ee1", + "0xb3dc21608a389a72d3a752883a382baaafc61ecc44083b832610a237f6a2363f24195acce529eb4aed4ef0e27a12b66e", + "0x94619f5de05e07b32291e1d7ab1d8b7337a2235e49d4fb5f3055f090a65e932e829efa95db886b32b153bdd05a53ec8c", + "0xade1e92722c2ffa85865d2426fb3d1654a16477d3abf580cfc45ea4b92d5668afc9d09275d3b79283e13e6b39e47424d", + "0xb7201589de7bed094911dd62fcd25c459a8e327ac447b69f541cdba30233063e5ddffad0b67e9c3e34adcffedfd0e13d", + "0x809d325310f862d6549e7cb40f7e5fc9b7544bd751dd28c4f363c724a0378c0e2adcb5e42ec8f912f5f49f18f3365c07", + "0xa79c20aa533de7a5d671c99eb9eb454803ba54dd4f2efa3c8fec1a38f8308e9905c71e9282955225f686146388506ff6", + "0xa85eeacb5e8fc9f3ed06a3fe2dc3108ab9f8c5877b148c73cf26e4e979bf5795edbe2e63a8d452565fd1176ed40402b2", + "0x97ef55662f8a1ec0842b22ee21391227540adf7708f491436044f3a2eb18c471525e78e1e14fa292507c99d74d7437c6", + "0x93110d64ed5886f3d16ce83b11425576a3a7a9bb831cd0de3f9a0b0f2270a730d68136b4ef7ff035ede004358f419b5c", + "0xac9ed0a071517f0ae4f61ce95916a90ba9a77a3f84b0ec50ef7298acdcd44d1b94525d191c39d6bd1bb68f4471428760", + "0x98abd6a02c7690f5a339adf292b8c9368dfc12e0f8069cf26a5e0ce54b4441638f5c66ea735142f3c28e00a0024267e6", + "0xb51efb73ba6d44146f047d69b19c0722227a7748b0e8f644d0fc9551324cf034c041a2378c56ce8b58d06038fb8a78de", + "0x8f115af274ef75c1662b588b0896b97d71f8d67986ae846792702c4742ab855952865ce236b27e2321967ce36ff93357", + "0xb3c4548f14d58b3ab03c222da09e4381a0afe47a72d18d50a94e0008797f78e39e99990e5b4757be62310d400746e35a", + "0xa9b1883bd5f31f909b8b1b6dcb48c1c60ed20aa7374b3ffa7f5b2ed036599b5bef33289d23c80a5e6420d191723b92f7", + "0x85d38dffd99487ae5bb41ab4a44d80a46157bbbe8ef9497e68f061721f74e4da513ccc3422936b059575975f6787c936", + "0xadf870fcb96e972c033ab7a35d28ae79ee795f82bc49c3bd69138f0e338103118d5529c53f2d72a9c0d947bf7d312af2", + "0xab4c7a44e2d9446c6ff303eb49aef0e367a58b22cc3bb27b4e69b55d1d9ee639c9234148d2ee95f9ca8079b1457d5a75", + "0xa386420b738aba2d7145eb4cba6d643d96bda3f2ca55bb11980b318d43b289d55a108f4bc23a9606fb0bccdeb3b3bb30", + "0x847020e0a440d9c4109773ecca5d8268b44d523389993b1f5e60e541187f7c597d79ebd6e318871815e26c96b4a4dbb1", + "0xa530aa7e5ca86fcd1bec4b072b55cc793781f38a666c2033b510a69e110eeabb54c7d8cbcb9c61fee531a6f635ffa972", + "0x87364a5ea1d270632a44269d686b2402da737948dac27f51b7a97af80b66728b0256547a5103d2227005541ca4b7ed04", + "0x8816fc6e16ea277de93a6d793d0eb5c15e9e93eb958c5ef30adaf8241805adeb4da8ce19c3c2167f971f61e0b361077d", + "0x8836a72d301c42510367181bb091e4be377777aed57b73c29ef2ce1d475feedd7e0f31676284d9a94f6db01cc4de81a2", + "0xb0d9d8b7116156d9dde138d28aa05a33e61f8a85839c1e9071ccd517b46a5b4b53acb32c2edd7150c15bc1b4bd8db9e3", + "0xae931b6eaeda790ba7f1cd674e53dc87f6306ff44951fa0df88d506316a5da240df9794ccbd7215a6470e6b31c5ea193", + "0x8c6d5bdf87bd7f645419d7c6444e244fe054d437ed1ba0c122fde7800603a5fadc061e5b836cb22a6cfb2b466f20f013", + "0x90d530c6d0cb654999fa771b8d11d723f54b8a8233d1052dc1e839ea6e314fbed3697084601f3e9bbb71d2b4eaa596df", + "0xb0d341a1422588c983f767b1ed36c18b141774f67ef6a43cff8e18b73a009da10fc12120938b8bba27f225bdfd3138f9", + "0xa131b56f9537f460d304e9a1dd75702ace8abd68cb45419695cb8dee76998139058336c87b7afd6239dc20d7f8f940cc", + "0xaa6c51fa28975f709329adee1bbd35d49c6b878041841a94465e8218338e4371f5cb6c17f44a63ac93644bf28f15d20f", + "0x88440fb584a99ebd7f9ea04aaf622f6e44e2b43bbb49fb5de548d24a238dc8f26c8da2ccf03dd43102bda9f16623f609", + "0x9777b8695b790e702159a4a750d5e7ff865425b95fa0a3c15495af385b91c90c00a6bd01d1b77bffe8c47d01baae846f", + "0x8b9d764ece7799079e63c7f01690c8eff00896a26a0d095773dea7a35967a8c40db7a6a74692f0118bf0460c26739af4", + "0x85808c65c485520609c9e61fa1bb67b28f4611d3608a9f7a5030ee61c3aa3c7e7dc17fff48af76b4aecee2cb0dbd22ac", + "0xad2783a76f5b3db008ef5f7e67391fda4e7e36abde6b3b089fc4835b5c339370287935af6bd53998bed4e399eda1136d", + "0x96f18ec03ae47c205cc4242ca58e2eff185c9dca86d5158817e2e5dc2207ab84aadda78725f8dc080a231efdc093b940", + "0x97de1ab6c6cc646ae60cf7b86df73b9cf56cc0cd1f31b966951ebf79fc153531af55ca643b20b773daa7cab784b832f7", + "0x870ba266a9bfa86ef644b1ef025a0f1b7609a60de170fe9508de8fd53170c0b48adb37f19397ee8019b041ce29a16576", + "0xad990e888d279ac4e8db90619d663d5ae027f994a3992c2fbc7d262b5990ae8a243e19157f3565671d1cb0de17fe6e55", + "0x8d9d5adcdd94c5ba3be4d9a7428133b42e485f040a28d16ee2384758e87d35528f7f9868de9bd23d1a42a594ce50a567", + "0x85a33ed75d514ece6ad78440e42f7fcdb59b6f4cff821188236d20edae9050b3a042ce9bc7d2054296e133d033e45022", + "0x92afd2f49a124aaba90de59be85ff269457f982b54c91b06650c1b8055f9b4b0640fd378df02a00e4fc91f7d226ab980", + "0x8c0ee09ec64bd831e544785e3d65418fe83ed9c920d9bb4d0bf6dd162c1264eb9d6652d2def0722e223915615931581c", + "0x8369bedfa17b24e9ad48ebd9c5afea4b66b3296d5770e09b00446c5b0a8a373d39d300780c01dcc1c6752792bccf5fd0", + "0x8b9e960782576a59b2eb2250d346030daa50bbbec114e95cdb9e4b1ba18c3d34525ae388f859708131984976ca439d94", + "0xb682bface862008fea2b5a07812ca6a28a58fd151a1d54c708fc2f8572916e0d678a9cb8dc1c10c0470025c8a605249e", + "0xa38d5e189bea540a824b36815fc41e3750760a52be0862c4cac68214febdc1a754fb194a7415a8fb7f96f6836196d82a", + "0xb9e7fbda650f18c7eb8b40e42cc42273a7298e65e8be524292369581861075c55299ce69309710e5b843cb884de171bd", + "0xb6657e5e31b3193874a1bace08f42faccbd3c502fb73ad87d15d18a1b6c2a146f1baa929e6f517db390a5a47b66c0acf", + "0xae15487312f84ed6265e4c28327d24a8a0f4d2d17d4a5b7c29b974139cf93223435aaebe3af918f5b4bb20911799715f", + "0x8bb4608beb06bc394e1a70739b872ce5a2a3ffc98c7547bf2698c893ca399d6c13686f6663f483894bccaabc3b9c56ad", + "0xb58ac36bc6847077584308d952c5f3663e3001af5ecf2e19cb162e1c58bd6c49510205d453cffc876ca1dc6b8e04a578", + "0x924f65ced61266a79a671ffb49b300f0ea44c50a0b4e3b02064faa99fcc3e4f6061ea8f38168ab118c5d47bd7804590e", + "0x8d67d43b8a06b0ff4fafd7f0483fa9ed1a9e3e658a03fb49d9d9b74e2e24858dc1bed065c12392037b467f255d4e5643", + "0xb4d4f87813125a6b355e4519a81657fa97c43a6115817b819a6caf4823f1d6a1169683fd68f8d025cdfa40ebf3069acb", + "0xa7fd4d2c8e7b59b8eed3d4332ae94b77a89a2616347402f880bc81bde072220131e6dbec8a605be3a1c760b775375879", + "0x8d4a7d8fa6f55a30df37bcf74952e2fa4fd6676a2e4606185cf154bdd84643fd01619f8fb8813a564f72e3f574f8ce30", + "0x8086fb88e6260e9a9c42e9560fde76315ff5e5680ec7140f2a18438f15bc2cc7d7d43bfb5880b180b738c20a834e6134", + "0x916c4c54721de03934fee6f43de50bb04c81f6f8dd4f6781e159e71c40c60408aa54251d457369d133d4ba3ed7c12cb4", + "0x902e5bf468f11ed9954e2a4a595c27e34abe512f1d6dc08bbca1c2441063f9af3dc5a8075ab910a10ff6c05c1c644a35", + "0xa1302953015e164bf4c15f7d4d35e3633425a78294406b861675667eec77765ff88472306531e5d3a4ec0a2ff0dd6a9e", + "0x87874461df3c9aa6c0fa91325576c0590f367075f2f0ecfeb34afe162c04c14f8ce9d608c37ac1adc8b9985bc036e366", + "0x84b50a8a61d3cc609bfb0417348133e698fe09a6d37357ce3358de189efcf35773d78c57635c2d26c3542b13cc371752", + "0xacaed2cff8633d12c1d12bb7270c54d65b0b0733ab084fd47f81d0a6e1e9b6f300e615e79538239e6160c566d8bb8d29", + "0x889e6a0e136372ca4bac90d1ab220d4e1cad425a710e8cdd48b400b73bb8137291ceb36a39440fa84305783b1d42c72f", + "0x90952e5becec45b2b73719c228429a2c364991cf1d5a9d6845ae5b38018c2626f4308daa322cab1c72e0f6c621bb2b35", + "0x8f5a97a801b6e9dcd66ccb80d337562c96f7914e7169e8ff0fda71534054c64bf2a9493bb830623d612cfe998789be65", + "0x84f3df8b9847dcf1d63ca470dc623154898f83c25a6983e9b78c6d2d90a97bf5e622445be835f32c1e55e6a0a562ea78", + "0x91d12095cd7a88e7f57f254f02fdb1a1ab18984871dead2f107404bcf8069fe68258c4e6f6ebd2477bddf738135400bb", + "0xb771a28bc04baef68604d4723791d3712f82b5e4fe316d7adc2fc01b935d8e644c06d59b83bcb542afc40ebafbee0683", + "0x872f6341476e387604a7e93ae6d6117e72d164e38ebc2b825bc6df4fcce815004d7516423c190c1575946b5de438c08d", + "0x90d6b4aa7d40a020cdcd04e8b016d041795961a8e532a0e1f4041252131089114a251791bf57794cadb7d636342f5d1c", + "0x899023ba6096a181448d927fed7a0fe858be4eac4082a42e30b3050ee065278d72fa9b9d5ce3bc1372d4cbd30a2f2976", + "0xa28f176571e1a9124f95973f414d5bdbf5794d41c3839d8b917100902ac4e2171eb940431236cec93928a60a77ede793", + "0x838dbe5bcd29c4e465d02350270fa0036cd46f8730b13d91e77afb7f5ed16525d0021d3b2ae173a76c378516a903e0cb", + "0x8e105d012dd3f5d20f0f1c4a7e7f09f0fdd74ce554c3032e48da8cce0a77260d7d47a454851387770f5c256fa29bcb88", + "0x8f4df0f9feeb7a487e1d138d13ea961459a6402fd8f8cabb226a92249a0d04ded5971f3242b9f90d08da5ff66da28af6", + "0xad1cfda4f2122a20935aa32fb17c536a3653a18617a65c6836700b5537122af5a8206befe9eaea781c1244c43778e7f1", + "0x832c6f01d6571964ea383292efc8c8fa11e61c0634a25fa180737cc7ab57bc77f25e614aac9a2a03d98f27b3c1c29de2", + "0x903f89cc13ec6685ac7728521898781fecb300e9094ef913d530bf875c18bcc3ceed7ed51e7b482d45619ab4b025c2e9", + "0xa03c474bb915aad94f171e8d96f46abb2a19c9470601f4c915512ec8b9e743c3938450a2a5b077b4618b9df8809e1dc1", + "0x83536c8456f306045a5f38ae4be2e350878fa7e164ea408d467f8c3bc4c2ee396bd5868008c089183868e4dfad7aa50b", + "0x88f26b4ea1b236cb326cd7ad7e2517ec8c4919598691474fe15d09cabcfc37a8d8b1b818f4d112432ee3a716b0f37871", + "0xa44324e3fe96e9c12b40ded4f0f3397c8c7ee8ff5e96441118d8a6bfad712d3ac990b2a6a23231a8f691491ac1fd480f", + "0xb0de4693b4b9f932191a21ee88629964878680152a82996c0019ffc39f8d9369bbe2fe5844b68d6d9589ace54af947e4", + "0x8e5d8ba948aea5fd26035351a960e87f0d23efddd8e13236cc8e4545a3dda2e9a85e6521efb8577e03772d3637d213d9", + "0x93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556", + "0x8731176363ad7658a2862426ee47a5dce9434216cef60e6045fa57c40bb3ce1e78dac4510ae40f1f31db5967022ced32", + "0xb10c9a96745722c85bdb1a693100104d560433d45b9ac4add54c7646a7310d8e9b3ca9abd1039d473ae768a18e489845", + "0xa2ac374dfbb464bf850b4a2caf15b112634a6428e8395f9c9243baefd2452b4b4c61b0cb2836d8eae2d57d4900bf407e", + "0xb69fe3ded0c4f5d44a09a0e0f398221b6d1bf5dbb8bc4e338b93c64f1a3cac1e4b5f73c2b8117158030ec03787f4b452", + "0x8852cdbaf7d0447a8c6f211b4830711b3b5c105c0f316e3a6a18dcfbb9be08bd6f4e5c8ae0c3692da08a2dfa532f9d5c", + "0x93bbf6d7432a7d98ade3f94b57bf9f4da9bc221a180a370b113066dd42601bb9e09edd79e2e6e04e00423399339eebda", + "0xa80941c391f1eeafc1451c59e4775d6a383946ff22997aeaadf806542ba451d3b0f0c6864eeba954174a296efe2c1550", + "0xa045fe2bb011c2a2f71a0181a8f457a3078470fb74c628eab8b59aef69ffd0d649723bf74d6885af3f028bc5a104fb39", + "0xb9d8c35911009c4c8cad64692139bf3fc16b78f5a19980790cb6a7aea650a25df4231a4437ae0c351676a7e42c16134f", + "0x94c79501ded0cfcbab99e1841abe4a00a0252b3870e20774c3da16c982d74c501916ec28304e71194845be6e3113c7ab", + "0x900a66418b082a24c6348d8644ddb1817df5b25cb33044a519ef47cc8e1f7f1e38d2465b7b96d32ed472d2d17f8414c6", + "0xb26f45d393b8b2fcb29bdbb16323dc7f4b81c09618519ab3a39f8ee5bd148d0d9f3c0b5dfab55b5ce14a1cb9206d777b", + "0xaa1a87735fc493a80a96a9a57ca40a6d9c32702bfcaa9869ce1a116ae65d69cefe2f3e79a12454b4590353e96f8912b4", + "0xa922b188d3d0b69b4e4ea2a2aa076566962844637da12c0832105d7b31dea4a309eee15d12b7a336be3ea36fcbd3e3b7", + "0x8f3841fcf4105131d8c4d9885e6e11a46c448226401cf99356c291fadb864da9fa9d30f3a73c327f23f9fd99a11d633e", + "0x9791d1183fae270e226379af6c497e7da803ea854bb20afa74b253239b744c15f670ee808f708ede873e78d79a626c9a", + "0xa4cad52e3369491ada61bf28ada9e85de4516d21c882e5f1cd845bea9c06e0b2887b0c5527fcff6fc28acd3c04f0a796", + "0xb9ac86a900899603452bd11a7892a9bfed8054970bfcbeaa8c9d1930db891169e38d6977f5258c25734f96c8462eee3b", + "0xa3a154c28e5580656a859f4efc2f5ebfa7eaa84ca40e3f134fa7865e8581586db74992dbfa4036aa252fba103773ddde", + "0x95cc2a0c1885a029e094f5d737e3ecf4d26b99036453a8773c77e360101f9f98676ee246f6f732a377a996702d55691f", + "0x842651bbe99720438d8d4b0218feb60481280c05beb17750e9ca0d8c0599a60f873b7fbdcc7d8835ba9a6d57b16eec03", + "0x81ee54699da98f5620307893dcea8f64670609fa20e5622265d66283adeac122d458b3308c5898e6c57c298db2c8b24f", + "0xb97868b0b2bc98032d68352a535a1b341b9ff3c7af4e3a7f3ebc82d3419daa1b5859d6aedc39994939623c7cd878bd9b", + "0xb60325cd5d36461d07ef253d826f37f9ee6474a760f2fff80f9873d01fd2b57711543cdc8d7afa1c350aa753c2e33dea", + "0x8c205326c11d25a46717b780c639d89714c7736c974ae71287e3f4b02e6605ac2d9b4928967b1684f12be040b7bf2dd3", + "0x95a392d82db51e26ade6c2ccd3396d7e40aff68fa570b5951466580d6e56dda51775dce5cf3a74a7f28c3cb2eb551c4d", + "0x8f2cc8071eb56dffb70bda6dd433b556221dc8bba21c53353c865f00e7d4d86c9e39f119ea9a8a12ef583e9a55d9a6b6", + "0x9449a71af9672aaf8856896d7e3d788b22991a7103f75b08c0abbcc2bfe60fda4ed8ce502cea4511ff0ea52a93e81222", + "0x857090ab9fdb7d59632d068f3cc8cf27e61f0d8322d30e6b38e780a1f05227199b4cd746aac1311c36c659ef20931f28", + "0x98a891f4973e7d9aaf9ac70854608d4f7493dffc7e0987d7be9dd6029f6ea5636d24ef3a83205615ca1ff403750058e1", + "0xa486e1365bbc278dd66a2a25d258dc82f46b911103cb16aab3945b9c95ae87b386313a12b566df5b22322ede0afe25ad", + "0xa9a1eb399ed95d396dccd8d1ac718043446f8b979ec62bdce51c617c97a312f01376ab7fb87d27034e5f5570797b3c33", + "0xb7abc3858d7a74bb446218d2f5a037e0fae11871ed9caf44b29b69c500c1fa1dcfad64c9cdccc9d80d5e584f06213deb", + "0x8cfb09fe2e202faa4cebad932b1d35f5ca204e1c2a0c740a57812ac9a6792130d1312aabd9e9d4c58ca168bfebd4c177", + "0xa90a305c2cd0f184787c6be596fa67f436afd1f9b93f30e875f817ac2aae8bdd2e6e656f6be809467e6b3ad84adb86b1", + "0x80a9ef993c2b009ae172cc8f7ec036f5734cf4f4dfa06a7db4d54725e7fbfae5e3bc6f22687bdbb6961939d6f0c87537", + "0x848ade1901931e72b955d7db1893f07003e1708ff5d93174bac5930b9a732640f0578839203e9b77eb27965c700032d3", + "0x93fdf4697609c5ae9c33b9ca2f5f1af44abeb2b98dc4fdf732cf7388de086f410730dc384d9b7a7f447bb009653c8381", + "0x89ce3fb805aea618b5715c0d22a9f46da696b6fa86794f56fdf1d44155a33d42daf1920bcbe36cbacf3cf4c92df9cbc7", + "0x829ce2c342cf82aa469c65f724f308f7a750bd1494adc264609cd790c8718b8b25b5cab5858cf4ee2f8f651d569eea67", + "0xaf2f0cee7bf413204be8b9df59b9e4991bc9009e0d6dbe6815181df0ec2ca93ab8f4f3135b1c14d8f53d74bff0bd6f27", + "0xb87998cecf7b88cde93d1779f10a521edd5574a2fbd240102978639ec57433ba08cdb53849038a329cebbe74657268d2", + "0xa64542a1261a6ed3d720c2c3a802303aad8c4c110c95d0f12e05c1065e66f42da494792b6bfc5b9272363f3b1d457f58", + "0x86a6fd042e4f282fadf07a4bfee03fc96a3aea49f7a00f52bf249a20f1ec892326855410e61f37fbb27d9305eb2fc713", + "0x967ea5bc403b6db269682f7fd0df90659350d7e1aa66bc4fab4c9dfcd75ed0bba4b52f1cebc5f34dc8ba810793727629", + "0xa52990f9f3b8616ce3cdc2c74cd195029e6a969753dcf2d1630438700e7d6ebde36538532b3525ac516f5f2ce9dd27a3", + "0xa64f7ff870bab4a8bf0d4ef6f5c744e9bf1021ed08b4c80903c7ad318e80ba1817c3180cc45cb5a1cae1170f0241655f", + "0xb00f706fa4de1f663f021e8ad3d155e84ce6084a409374b6e6cd0f924a0a0b51bebaaaf1d228c77233a73b0a5a0df0e9", + "0x8b882cc3bff3e42babdb96df95fb780faded84887a0a9bab896bef371cdcf169d909f5658649e93006aa3c6e1146d62e", + "0x9332663ef1d1dcf805c3d0e4ce7a07d9863fb1731172e766b3cde030bf81682cc011e26b773fb9c68e0477b4ae2cfb79", + "0xa8aa8151348dbd4ef40aaeb699b71b4c4bfd3218560c120d85036d14f678f6736f0ec68e80ce1459d3d35feccc575164", + "0xa16cd8b729768f51881c213434aa28301fa78fcb554ddd5f9012ee1e4eae7b5cb3dd88d269d53146dea92d10790faf0b", + "0x86844f0ef9d37142faf3b1e196e44fbe280a3ba4189aa05c356778cb9e3b388a2bff95eed305ada8769935c9974e4c57", + "0xae2eec6b328fccf3b47bcdac32901ac2744a51beb410b04c81dea34dee4912b619466a4f5e2780d87ecefaebbe77b46d", + "0x915df4c38d301c8a4eb2dc5b1ba0ffaad67cbb177e0a80095614e9c711f4ef24a4cef133f9d982a63d2a943ba6c8669d", + "0xae6a2a4dedfc2d1811711a8946991fede972fdf2a389b282471280737536ffc0ac3a6d885b1f8bda0366eb0b229b9979", + "0xa9b628c63d08b8aba6b1317f6e91c34b2382a6c85376e8ef2410a463c6796740ae936fc4e9e0737cb9455d1daa287bd8", + "0x848e30bf7edf2546670b390d5cf9ab71f98fcb6add3c0b582cb34996c26a446dee5d1bde4fdcde4fc80c10936e117b29", + "0x907d6096c7c8c087d1808dd995d5d2b9169b3768c3f433475b50c2e2bd4b082f4d543afd8b0b0ddffa9c66222a72d51d", + "0xa59970a2493b07339124d763ac9d793c60a03354539ecbcf6035bc43d1ea6e35718202ae6d7060b7d388f483d971573c", + "0xb9cfef2af9681b2318f119d8611ff6d9485a68d8044581b1959ab1840cbca576dbb53eec17863d2149966e9feb21122f", + "0xad47271806161f61d3afa45cdfe2babceef5e90031a21779f83dc8562e6076680525b4970b2f11fe9b2b23c382768323", + "0x8e425a99b71677b04fe044625d338811fbb8ee32368a424f6ab2381c52e86ee7a6cecedf777dc97181519d41c351bc22", + "0x86b55b54d7adefc12954a9252ee23ae83efe8b5b4b9a7dc307904413e5d69868c7087a818b2833f9b004213d629be8ad", + "0xa14fda6b93923dd11e564ae4457a66f397741527166e0b16a8eb91c6701c244fd1c4b63f9dd3515193ec88fa6c266b35", + "0xa9b17c36ae6cd85a0ed7f6cabc5b47dc8f80ced605db327c47826476dc1fb8f8669aa7a7dc679fbd4ee3d8e8b4bd6a6f", + "0x82a0829469c1458d959c821148f15dacae9ea94bf56c59a6ab2d4dd8b3d16d73e313b5a3912a6c1f131d73a8f06730c4", + "0xb22d56d549a53eaef549595924bdb621ff807aa4513feedf3fdcbf7ba8b6b9cfa4481c2f67fc642db397a6b794a8b63a", + "0x974c59c24392e2cb9294006cbe3c52163e255f3bd0c2b457bdc68a6338e6d5b6f87f716854492f8d880a6b896ccf757c", + "0xb70d247ba7cad97c50b57f526c2ba915786e926a94e8f8c3eebc2e1be6f4255411b9670e382060049c8f4184302c40b2", + "0xad80201fe75ef21c3ddbd98cf23591e0d7a3ba1036dfe77785c32f44755a212c31f0ceb0a0b6f5ee9b6dc81f358d30c3", + "0x8c656e841f9bb90b9a42d425251f3fdbc022a604d75f5845f479ed4be23e02aaf9e6e56cde351dd7449c50574818a199", + "0x8b88dd3fa209d3063b7c5b058f7249ee9900fbc2287d16da61a0704a0a1d71e45d9c96e1cda7fdf9654534ec44558b22", + "0x961da00cc8750bd84d253c08f011970ae1b1158ad6778e8ed943d547bceaf52d6d5a212a7de3bf2706688c4389b827d2", + "0xa5dd379922549a956033e3d51a986a4b1508e575042b8eaa1df007aa77cf0b8c2ab23212f9c075702788fa9c53696133", + "0xac8fcfde3a349d1e93fc8cf450814e842005c545c4844c0401bc80e6b96cdb77f29285a14455e167c191d4f312e866cd", + "0xac63d79c799783a8466617030c59dd5a8f92ee6c5204676fd8d881ce5f7f8663bdbeb0379e480ea9b6340ab0dc88e574", + "0x805874fde19ce359041ae2bd52a39e2841acabfd31f965792f2737d7137f36d4e4722ede8340d8c95afa6af278af8acb", + "0x8d2f323a228aa8ba7b7dc1399138f9e6b41df1a16a7069003ab8104b8b68506a45141bc5fe66acf430e23e13a545190b", + "0xa1610c721a2d9af882bb6b39bea97cff1527a3aea041d25934de080214ae77c959e79957164440686d15ab301e897d4d", + "0xaba16d29a47fc36f12b654fde513896723e2c700c4190f11b26aa4011da57737ad717daa02794aa3246e4ae5f0b0cc3a", + "0xa406db2f15fdd135f346cc4846623c47edd195e80ba8c7cb447332095314d565e4040694ca924696bb5ee7f8996ea0ba", + "0x8b30e2cd9b47d75ba57b83630e40f832249af6c058d4f490416562af451993eec46f3e1f90bc4d389e4c06abd1b32a46", + "0xaacf9eb7036e248e209adbfc3dd7ce386569ea9b312caa4b240726549db3c68c4f1c8cbf8ed5ea9ea60c7e57c9df3b8e", + "0xb20fcac63bf6f5ee638a42d7f89be847f348c085ddcbec3fa318f4323592d136c230495f188ef2022aa355cc2b0da6f9", + "0x811eff750456a79ec1b1249d76d7c1547065b839d8d4aaad860f6d4528eb5b669473dcceeeea676cddbc3980b68461b7", + "0xb52d14ae33f4ab422f953392ae76a19c618cc31afc96290bd3fe2fb44c954b5c92c4789f3f16e8793f2c0c1691ade444", + "0xa7826dafeeba0db5b66c4dfcf2b17fd7b40507a5a53ac2e42942633a2cb30b95ba1739a6e9f3b7a0e0f1ec729bf274e2", + "0x8acfd83ddf7c60dd7c8b20c706a3b972c65d336b8f9b3d907bdd8926ced271430479448100050b1ef17578a49c8fa616", + "0xaf0c69f65184bb06868029ad46f8465d75c36814c621ac20a5c0b06a900d59305584f5a6709683d9c0e4b6cd08d650a6", + "0xb6cc8588191e00680ee6c3339bd0f0a17ad8fd7f4be57d5d7075bede0ea593a19e67f3d7c1a20114894ee5bfcab71063", + "0xa82fd4f58635129dbb6cc3eb9391cf2d28400018b105fc41500fbbd12bd890b918f97d3d359c29dd3b4c4e34391dfab0", + "0x92fc544ed65b4a3625cf03c41ddff7c039bc22d22c0d59dcc00efd5438401f2606adb125a1d5de294cca216ec8ac35a3", + "0x906f67e4a32582b71f15940523c0c7ce370336935e2646bdaea16a06995256d25e99df57297e39d6c39535e180456407", + "0x97510337ea5bbd5977287339197db55c60533b2ec35c94d0a460a416ae9f60e85cee39be82abeeacd5813cf54df05862", + "0x87e6894643815c0ea48cb96c607266c5ee4f1f82ba5fe352fb77f9b6ed14bfc2b8e09e80a99ac9047dfcf62b2ae26795", + "0xb6fd55dd156622ad7d5d51b7dde75e47bd052d4e542dd6449e72411f68275775c846dde301e84613312be8c7bce58b07", + "0xb98461ac71f554b2f03a94e429b255af89eec917e208a8e60edf5fc43b65f1d17a20de3f31d2ce9f0cb573c25f2f4d98", + "0x96f0dea40ca61cefbee41c4e1fe9a7d81fbe1f49bb153d083ab70f5d0488a1f717fd28cedcf6aa18d07cce2c62801898", + "0x8d7c3ab310184f7dc34b6ce4684e4d29a31e77b09940448ea4daac730b7eb308063125d4dd229046cf11bfd521b771e0", + "0x96f0564898fe96687918bbf0a6adead99cf72e3a35ea3347e124af9d006221f8e82e5a9d2fe80094d5e8d48e610f415e", + "0xad50fcb92c2675a398cf07d4c40a579e44bf8d35f27cc330b57e54d5ea59f7d898af0f75dccfe3726e5471133d70f92b", + "0x828beed62020361689ae7481dd8f116902b522fb0c6c122678e7f949fdef70ead011e0e6bffd25678e388744e17cdb69", + "0x8349decac1ca16599eee2efc95bcaabf67631107da1d34a2f917884bd70dfec9b4b08ab7bc4379d6c73b19c0b6e54fb8", + "0xb2a6a2e50230c05613ace9e58bb2e98d94127f196f02d9dddc53c43fc68c184549ca12d713cb1b025d8260a41e947155", + "0x94ff52181aadae832aed52fc3b7794536e2a31a21fc8be3ea312ca5c695750d37f08002f286b33f4023dba1e3253ecfa", + "0xa21d56153c7e5972ee9a319501be4faff199fdf09bb821ea9ce64aa815289676c00f105e6f00311b3a5b627091b0d0fc", + "0xa27a60d219f1f0c971db73a7f563b371b5c9fc3ed1f72883b2eac8a0df6698400c9954f4ca17d7e94e44bd4f95532afb", + "0xa2fc56fae99b1f18ba5e4fe838402164ce82f8a7f3193d0bbd360c2bac07c46f9330c4c7681ffb47074c6f81ee6e7ac6", + "0xb748e530cd3afb96d879b83e89c9f1a444f54e55372ab1dcd46a0872f95ce8f49cf2363fc61be82259e04f555937ed16", + "0x8bf8993e81080c7cbba1e14a798504af1e4950b2f186ab3335b771d6acaee4ffe92131ae9c53d74379d957cb6344d9cd", + "0x96774d0ef730d22d7ab6d9fb7f90b9ead44285219d076584a901960542756700a2a1603cdf72be4708b267200f6c36a9", + "0xb47703c2ab17be1e823cc7bf3460db1d6760c0e33862c90ca058845b2ff234b0f9834ddba2efb2ee1770eb261e7d8ffd", + "0x84319e67c37a9581f8b09b5e4d4ae88d0a7fb4cbb6908971ab5be28070c3830f040b1de83ee663c573e0f2f6198640e4", + "0x96811875fa83133e0b3c0e0290f9e0e28bca6178b77fdf5350eb19344d453dbd0d71e55a0ef749025a5a2ca0ad251e81", + "0x81a423423e9438343879f2bfd7ee9f1c74ebebe7ce3cfffc8a11da6f040cc4145c3b527bd3cf63f9137e714dbcb474ef", + "0xb8c3535701ddbeec2db08e17a4fa99ba6752d32ece5331a0b8743676f421fcb14798afc7c783815484f14693d2f70db8", + "0x81aee980c876949bf40782835eec8817d535f6f3f7e00bf402ddd61101fdcd60173961ae90a1cf7c5d060339a18c959d", + "0x87e67b928d97b62c49dac321ce6cb680233f3a394d4c9a899ac2e8db8ccd8e00418e66cdfd68691aa3cb8559723b580c", + "0x8eac204208d99a2b738648df96353bbb1b1065e33ee4f6bba174b540bbbd37d205855e1f1e69a6b7ff043ca377651126", + "0x848e6e7a54ad64d18009300b93ea6f459ce855971dddb419b101f5ac4c159215626fadc20cc3b9ab1701d8f6dfaddd8b", + "0x88aa123d9e0cf309d46dddb6acf634b1ade3b090a2826d6e5e78669fa1220d6df9a6697d7778cd9b627db17eea846126", + "0x9200c2a629b9144d88a61151b661b6c4256cc5dadfd1e59a8ce17a013c2d8f7e754aabe61663c3b30f1bc47784c1f8cf", + "0xb6e1a2827c3bdda91715b0e1b1f10dd363cef337e7c80cac1f34165fc0dea7c8b69747e310563db5818390146ce3e231", + "0x92c333e694f89f0d306d54105b2a5dcc912dbe7654d9e733edab12e8537350815be472b063e56cfde5286df8922fdecb", + "0xa6fac04b6d86091158ebb286586ccfec2a95c9786e14d91a9c743f5f05546073e5e3cc717635a0c602cad8334e922346", + "0xa581b4af77feebc1fb897d49b5b507c6ad513d8f09b273328efbb24ef0d91eb740d01b4d398f2738125dacfe550330cd", + "0x81c4860cccf76a34f8a2bc3f464b7bfd3e909e975cce0d28979f457738a56e60a4af8e68a3992cf273b5946e8d7f76e2", + "0x8d1eaa09a3180d8af1cbaee673db5223363cc7229a69565f592fa38ba0f9d582cedf91e15dabd06ebbf2862fc0feba54", + "0x9832f49b0147f4552402e54593cfa51f99540bffada12759b71fcb86734be8e500eea2d8b3d036710bdf04c901432de9", + "0x8bdb0e8ec93b11e5718e8c13cb4f5de545d24829fd76161216340108098dfe5148ed25e3b57a89a516f09fa79043734d", + "0xab96f06c4b9b0b2c0571740b24fca758e6976315053a7ecb20119150a9fa416db2d3a2e0f8168b390bb063f0c1caf785", + "0xab777f5c52acd62ecf4d1f168b9cc8e1a9b45d4ec6a8ff52c583e867c2239aba98d7d3af977289b367edce03d9c2dfb1", + "0xa09d3ce5e748da84802436951acc3d3ea5d8ec1d6933505ed724d6b4b0d69973ab0930daec9c6606960f6e541e4a3ce2", + "0x8ef94f7be4d85d5ad3d779a5cf4d7b2fc3e65c52fb8e1c3c112509a4af77a0b5be994f251e5e40fabeeb1f7d5615c22b", + "0xa7406a5bf5708d9e10922d3c5c45c03ef891b8d0d74ec9f28328a72be4cdc05b4f2703fa99366426659dfca25d007535", + "0xb7f52709669bf92a2e070bfe740f422f0b7127392c5589c7f0af71bb5a8428697c762d3c0d74532899da24ea7d8695c2", + "0xb9dfb0c8df84104dbf9239ccefa4672ef95ddabb8801b74997935d1b81a78a6a5669a3c553767ec19a1281f6e570f4ff", + "0xae4d5c872156061ce9195ac640190d8d71dd406055ee43ffa6f9893eb24b870075b74c94d65bc1d5a07a6573282b5520", + "0xafe6bd3eb72266d333f1807164900dcfa02a7eb5b1744bb3c86b34b3ee91e3f05e38fa52a50dc64eeb4bdb1dd62874b8", + "0x948043cf1bc2ef3c01105f6a78dc06487f57548a3e6ef30e6ebc51c94b71e4bf3ff6d0058c72b6f3ecc37efd7c7fa8c0", + "0xa22fd17c2f7ffe552bb0f23fa135584e8d2d8d75e3f742d94d04aded2a79e22a00dfe7acbb57d44e1cdb962fb22ae170", + "0x8cd0f4e9e4fb4a37c02c1bde0f69359c43ab012eb662d346487be0c3758293f1ca560122b059b091fddce626383c3a8f", + "0x90499e45f5b9c81426f3d735a52a564cafbed72711d9279fdd88de8038e953bc48c57b58cba85c3b2e4ce56f1ddb0e11", + "0x8c30e4c034c02958384564cac4f85022ef36ab5697a3d2feaf6bf105049675bbf23d01b4b6814711d3d9271abff04cac", + "0x81f7999e7eeea30f3e1075e6780bbf054f2fb6f27628a2afa4d41872a385b4216dd5f549da7ce6cf39049b2251f27fb7", + "0xb36a7191f82fc39c283ffe53fc1f5a9a00b4c64eee7792a8443475da9a4d226cf257f226ea9d66e329af15d8f04984ec", + "0xaad4da528fdbb4db504f3041c747455baff5fcd459a2efd78f15bdf3aea0bdb808343e49df88fe7a7c8620009b7964a3", + "0x99ebd8c6dd5dd299517fb6381cfc2a7f443e6e04a351440260dd7c2aee3f1d8ef06eb6c18820b394366ecdfd2a3ce264", + "0x8873725b81871db72e4ec3643084b1cdce3cbf80b40b834b092767728605825c19b6847ad3dcf328438607e8f88b4410", + "0xb008ee2f895daa6abd35bd39b6f7901ae4611a11a3271194e19da1cdcc7f1e1ea008fe5c5440e50d2c273784541ad9c5", + "0x9036feafb4218d1f576ef89d0e99124e45dacaa6d816988e34d80f454d10e96809791d5b78f7fd65f569e90d4d7238c5", + "0x92073c1d11b168e4fa50988b0288638b4868e48bbc668c5a6dddf5499875d53be23a285acb5e4bad60114f6cf6c556e9", + "0x88c87dfcb8ba6cbfe7e1be081ccfadbd589301db2cb7c99f9ee5d7db90aa297ed1538d5a867678a763f2deede5fd219a", + "0xb42a562805c661a50f5dea63108002c0f27c0da113da6a9864c9feb5552225417c0356c4209e8e012d9bcc9d182c7611", + "0x8e6317d00a504e3b79cd47feb4c60f9df186467fe9ca0f35b55c0364db30528f5ff071109dabb2fc80bb9cd4949f0c24", + "0xb7b1ea6a88694f8d2f539e52a47466695e39e43a5eb9c6f23bca15305fe52939d8755cc3ac9d6725e60f82f994a3772f", + "0xa3cd55161befe795af93a38d33290fb642b8d80da8b786c6e6fb02d393ea308fbe87f486994039cbd7c7b390414594b6", + "0xb416d2d45b44ead3b1424e92c73c2cf510801897b05d1724ff31cbd741920cd858282fb5d6040fe1f0aa97a65bc49424", + "0x950ee01291754feace97c2e933e4681e7ddfbc4fcd079eb6ff830b0e481d929c93d0c7fb479c9939c28ca1945c40da09", + "0x869bd916aee8d86efe362a49010382674825d49195b413b4b4018e88ce43fe091b475d0b863ff0ba2259400f280c2b23", + "0x9782f38cd9c9d3385ec286ebbc7cba5b718d2e65a5890b0a5906b10a89dc8ed80d417d71d7c213bf52f2af1a1f513ea7", + "0x91cd33bc2628d096269b23faf47ee15e14cb7fdc6a8e3a98b55e1031ea0b68d10ba30d97e660f7e967d24436d40fad73", + "0x8becc978129cc96737034c577ae7225372dd855da8811ae4e46328e020c803833b5bdbc4a20a93270e2b8bd1a2feae52", + "0xa36b1d8076783a9522476ce17f799d78008967728ce920531fdaf88303321bcaf97ecaa08e0c01f77bc32e53c5f09525", + "0xb4720e744943f70467983aa34499e76de6d59aa6fadf86f6b787fdce32a2f5b535b55db38fe2da95825c51002cfe142d", + "0x91ad21fc502eda3945f6de874d1b6bf9a9a7711f4d61354f9e5634fc73f9c06ada848de15ab0a75811d3250be862827d", + "0x84f78e2ebf5fc077d78635f981712daf17e2475e14c2a96d187913006ad69e234746184a51a06ef510c9455b38acb0d7", + "0x960aa7906e9a2f11db64a26b5892ac45f20d2ccb5480f4888d89973beb6fa0dfdc06d68d241ff5ffc7f1b82b1aac242d", + "0xa99365dcd1a00c66c9db6924b97c920f5c723380e823b250db85c07631b320ec4e92e586f7319e67a522a0578f7b6d6c", + "0xa25d92d7f70cf6a88ff317cfec071e13774516da664f5fac0d4ecaa65b8bf4eb87a64a4d5ef2bd97dfae98d388dbf5cc", + "0xa7af47cd0041295798f9779020a44653007444e8b4ef0712982b06d0dcdd434ec4e1f7c5f7a049326602cb605c9105b7", + "0xaefe172eac5568369a05980931cc476bebd9dea573ba276d59b9d8c4420784299df5a910033b7e324a6c2dfc62e3ef05", + "0xb69bc9d22ffa645baa55e3e02522e9892bb2daa7fff7c15846f13517d0799766883ee09ae0869df4139150c5b843ca8a", + "0x95a10856140e493354fdd12722c7fdded21b6a2ffbc78aa2697104af8ad0c8e2206f44b0bfee077ef3949d46bbf7c16b", + "0x891f2fcd2c47cbea36b7fa715968540c233313f05333f09d29aba23c193f462ed490dd4d00969656e89c53155fdfe710", + "0xa6c33e18115e64e385c843dde34e8a228222795c7ca90bc2cc085705d609025f3351d9be61822c69035a49fb3e48f2d5", + "0xb87fb12f12c0533b005adad0487f03393ff682e13575e3cb57280c3873b2c38ba96a63c49eef7a442753d26b7005230b", + "0xb905c02ba451bfd411c135036d92c27af3b0b1c9c2f1309d6948544a264b125f39dd41afeff4666b12146c545adc168a", + "0x8b29c513f43a78951cf742231cf5457a6d9d55edf45df5481a0f299a418d94effef561b15d2c1a01d1b8067e7153fda9", + "0xb9941cccd51dc645920d2781c81a317e5a33cb7cf76427b60396735912cb6d2ca9292bb4d36b6392467d390d2c58d9f3", + "0xa8546b627c76b6ef5c93c6a98538d8593dbe21cb7673fd383d5401b0c935eea0bdeeefeb1af6ad41bad8464fb87bbc48", + "0xaa286b27de2812de63108a1aec29d171775b69538dc6198640ac1e96767c2b83a50391f49259195957d457b493b667c9", + "0xa932fb229f641e9abbd8eb2bd874015d97b6658ab6d29769fc23b7db9e41dd4f850382d4c1f08af8f156c5937d524473", + "0xa1412840fcc86e2aeec175526f2fb36e8b3b8d21a78412b7266daf81e51b3f68584ed8bd42a66a43afdd8c297b320520", + "0x89c78be9efb624c97ebca4fe04c7704fa52311d183ffd87737f76b7dadc187c12c982bd8e9ed7cd8beb48cdaafd2fd01", + "0xa3f5ddec412a5bec0ce15e3bcb41c6214c2b05d4e9135a0d33c8e50a78eaba71e0a5a6ea8b45854dec5c2ed300971fc2", + "0x9721f9cec7a68b7758e3887548790de49fa6a442d0396739efa20c2f50352a7f91d300867556d11a703866def2d5f7b5", + "0xa23764e140a87e5991573521af039630dd28128bf56eed2edbed130fd4278e090b60cf5a1dca9de2910603d44b9f6d45", + "0xa1a6494a994215e48ab55c70efa8ffdddce6e92403c38ae7e8dd2f8288cad460c6c7db526bbdf578e96ca04d9fe12797", + "0xb1705ea4cb7e074efe0405fc7b8ee2ec789af0426142f3ec81241cacd4f7edcd88e39435e4e4d8e7b1df64f3880d6613", + "0x85595d061d677116089a6064418b93eb44ff79e68d12bd9625078d3bbc440a60d0b02944eff6054433ee34710ae6fbb4", + "0x9978d5e30bedb7526734f9a1febd973a70bfa20890490e7cc6f2f9328feab1e24f991285dbc3711d892514e2d7d005ad", + "0xaf30243c66ea43b9f87a061f947f7bce745f09194f6e95f379c7582b9fead920e5d6957eaf05c12ae1282ada4670652f", + "0xa1930efb473f88001e47aa0b2b2a7566848cccf295792e4544096ecd14ee5d7927c173a8576b405bfa2eec551cd67eb5", + "0xb0446d1c590ee5a45f7e22d269c044f3848c97aec1d226b44bfd0e94d9729c28a38bccddc3a1006cc5fe4e3c24f001f2", + "0xb8a8380172df3d84b06176df916cf557966d4f2f716d3e9437e415d75b646810f79f2b2b71d857181b7fc944018883a3", + "0xa563afec25b7817bfa26e19dc9908bc00aa8fc3d19be7d6de23648701659009d10e3e4486c28e9c6b13d48231ae29ac5", + "0xa5a8e80579de886fb7d6408f542791876885947b27ad6fa99a8a26e381f052598d7b4e647b0115d4b5c64297e00ce28e", + "0x8f87afcc7ad33c51ac719bade3cd92da671a37a82c14446b0a2073f4a0a23085e2c8d31913ed2d0be928f053297de8f6", + "0xa43c455ce377e0bc434386c53c752880687e017b2f5ae7f8a15c044895b242dffde4c92fb8f8bb50b18470b17351b156", + "0x8368f8b12a5bceb1dba25adb3a2e9c7dc9b1a77a1f328e5a693f5aec195cd1e06b0fe9476b554c1c25dac6c4a5b640a3", + "0x919878b27f3671fc78396f11531c032f3e2bd132d04cc234fa4858676b15fb1db3051c0b1db9b4fc49038216f11321ce", + "0xb48cd67fb7f1242696c1f877da4bdf188eac676cd0e561fbac1a537f7b8229aff5a043922441d603a26aae56a15faee4", + "0xa3e0fdfd4d29ea996517a16f0370b54787fefe543c2fe73bfc6f9e560c1fd30dad8409859e2d7fa2d44316f24746c712", + "0x8bb156ade8faf149df7bea02c140c7e392a4742ae6d0394d880a849127943e6f26312033336d3b9fdc0092d71b5efe87", + "0x8845e5d5cc555ca3e0523244300f2c8d7e4d02aaebcb5bd749d791208856c209a6f84dd99fd55968c9f0ab5f82916707", + "0xa3e90bb5c97b07789c2f32dff1aec61d0a2220928202f5ad5355ae71f8249237799d6c8a22602e32e572cb12eabe0c17", + "0xb150bcc391884c996149dc3779ce71f15dda63a759ee9cc05871f5a8379dcb62b047098922c0f26c7bd04deb394c33f9", + "0x95cd4ad88d51f0f2efcfd0c2df802fe252bb9704d1afbf9c26a248df22d55da87bdfaf41d7bc6e5df38bd848f0b13f42", + "0xa05a49a31e91dff6a52ac8b9c2cfdd646a43f0d488253f9e3cfbce52f26667166bbb9b608fc358763a65cbf066cd6d05", + "0xa59c3c1227fdd7c2e81f5e11ef5c406da44662987bac33caed72314081e2eed66055d38137e01b2268e58ec85dd986c0", + "0xb7020ec3bd73a99861f0f1d88cf5a19abab1cbe14b7de77c9868398c84bb8e18dbbe9831838a96b6d6ca06e82451c67b", + "0x98d1ff2525e9718ee59a21d8900621636fcd873d9a564b8dceb4be80a194a0148daf1232742730b3341514b2e5a5436c", + "0x886d97b635975fc638c1b6afc493e5998ca139edba131b75b65cfe5a8e814f11bb678e0eeee5e6e5cd913ad3f2fefdfc", + "0x8fb9fd928d38d5d813b671c924edd56601dd7163b686c13f158645c2f869d9250f3859aa5463a39258c90fef0f41190a", + "0xaac35e1cd655c94dec3580bb3800bd9c2946c4a9856f7d725af15fbea6a2d8ca51c8ad2772abed60ee0e3fb9cb24046b", + "0xb8d71fa0fa05ac9e443c9b4929df9e7f09a919be679692682e614d24227e04894bfc14a5c73a62fb927fedff4a0e4aa7", + "0xa45a19f11fbbb531a704badbb813ed8088ab827c884ee4e4ebf363fa1132ff7cfa9d28be9c85b143e4f7cdbc94e7cf1a", + "0x82b54703a4f295f5471b255ab59dce00f0fe90c9fb6e06b9ee48b15c91d43f4e2ef4a96c3118aeb03b08767be58181bb", + "0x8283264c8e6d2a36558f0d145c18576b6600ff45ff99cc93eca54b6c6422993cf392668633e5df396b9331e873d457e5", + "0x8c549c03131ead601bc30eb6b9537b5d3beb7472f5bb1bcbbfd1e9f3704477f7840ab3ab7f7dc13bbbbcdff886a462d4", + "0xafbb0c520ac1b5486513587700ad53e314cb74bfbc12e0b5fbdcfdaac36d342e8b59856196a0d84a25cff6e6e1d17e76", + "0x89e4c22ffb51f2829061b3c7c1983c5c750cad158e3a825d46f7cf875677da5d63f653d8a297022b5db5845c9271b32b", + "0xafb27a86c4c2373088c96b9adf4433f2ebfc78ac5c526e9f0510670b6e4e5e0057c0a4f75b185e1a30331b9e805c1c15", + "0xa18e16b57445f88730fc5d3567bf5a176861dc14c7a08ed2996fe80eed27a0e7628501bcb78a1727c5e9ac55f29c12c4", + "0x93d61bf88b192d6825cf4e1120af1c17aa0f994d158b405e25437eaeefae049f7b721a206e7cc8a04fdc29d3c42580a1", + "0xa99f2995a2e3ed2fd1228d64166112038de2f516410aa439f4c507044e2017ea388604e2d0f7121256fadf7fbe7023d1", + "0x914fd91cffc23c32f1c6d0e98bf660925090d873367d543034654389916f65f552e445b0300b71b61b721a72e9a5983c", + "0xb42a578a7787b71f924e7def425d849c1c777156b1d4170a8ee7709a4a914e816935131afd9a0412c4cb952957b20828", + "0x82fb30590e84b9e45db1ec475a39971cf554dc01bcc7050bc89265740725c02e2be5a972168c5170c86ae83e5b0ad2c0", + "0xb14f8d8e1e93a84976289e0cf0dfa6f3a1809e98da16ee5c4932d0e1ed6bf8a07697fdd4dd86a3df84fb0003353cdcc0", + "0x85d7a2f4bda31aa2cb208b771fe03291a4ebdaf6f1dc944c27775af5caec412584c1f45bc741fca2a6a85acb3f26ad7d", + "0xaf02e56ce886ff2253bc0a68faad76f25ead84b2144e5364f3fb9b648f03a50ee9dc0b2c33ebacf7c61e9e43201ef9ef", + "0x87e025558c8a0b0abd06dfc350016847ea5ced7af2d135a5c9eec9324a4858c4b21510fb0992ec52a73447f24945058e", + "0x80fff0bafcd058118f5e7a4d4f1ae0912efeb281d2cbe4d34ba8945cc3dbe5d8baf47fb077343b90b8d895c90b297aca", + "0xb6edcf3a40e7b1c3c0148f47a263cd819e585a51ef31c2e35a29ce6f04c53e413f743034c0d998d9c00a08ba00166f31", + "0xabb87ed86098c0c70a76e557262a494ff51a30fb193f1c1a32f8e35eafa34a43fcc07aa93a3b7a077d9e35afa07b1a3d", + "0xa280214cd3bb0fb7ecd2d8bcf518cbd9078417f2b91d2533ec2717563f090fb84f2a5fcfdbbeb2a2a1f8a71cc5aa5941", + "0xa63083ca7238ea2b57d15a475963cf1d4f550d8cd76db290014a0461b90351f1f26a67d674c837b0b773b330c7c3d534", + "0xa8fa39064cb585ece5263e2f42f430206476bf261bd50f18d2b694889bd79d04d56410664cecad62690e5c5a20b3f6ff", + "0x85ba52ce9d700a5dcf6c5b00559acbe599d671ce5512467ff4b6179d7fad550567ce2a9c126a50964e3096458ea87920", + "0xb913501e1008f076e5eac6d883105174f88b248e1c9801e568fefaffa1558e4909364fc6d9512aa4d125cbd7cc895f05", + "0x8eb33b5266c8f2ed4725a6ad147a322e44c9264cf261c933cbbe230a43d47fca0f29ec39756b20561dabafadd5796494", + "0x850ebc8b661a04318c9db5a0515066e6454fa73865aa4908767a837857ecd717387f614acb614a88e075d4edc53a2f5a", + "0xa08d6b92d866270f29f4ce23a3f5d99b36b1e241a01271ede02817c8ec3f552a5c562db400766c07b104a331835c0c64", + "0x8131804c89bb3e74e9718bfc4afa547c1005ff676bd4db9604335032b203390cfa54478d45c6c78d1fe31a436ed4be9f", + "0x9106d94f23cc1eacec8316f16d6f0a1cc160967c886f51981fdb9f3f12ee1182407d2bb24e5b873de58cb1a3ee915a6b", + "0xa13806bfc3eae7a7000c9d9f1bd25e10218d4e67f59ae798b145b098bca3edad2b1040e3fc1e6310e612fb8818f459ac", + "0x8c69fbca502046cb5f6db99900a47b34117aef3f4b241690cdb3b84ca2a2fc7833e149361995dc41fa78892525bce746", + "0x852c473150c91912d58ecb05769222fa18312800c3f56605ad29eec9e2d8667b0b81c379048d3d29100ed2773bb1f3c5", + "0xb1767f6074426a00e01095dbb1795beb4e4050c6411792cbad6537bc444c3165d1058bafd1487451f9c5ddd209e0ae7e", + "0x80c600a5fe99354ce59ff0f84c760923dc8ff66a30bf47dc0a086181785ceb01f9b951c4e66df800ea6d705e8bc47055", + "0xb5cf19002fbc88a0764865b82afcb4d64a50196ea361e5c71dff7de084f4dcbbc34ec94a45cc9e0247bd51da565981aa", + "0x93e67a254ea8ce25e112d93cc927fadaa814152a2c4ec7d9a56eaa1ed47aec99b7e9916b02e64452cc724a6641729bbb", + "0xace70b32491bda18eee4a4d041c3bc9effae9340fe7e6c2f5ad975ee0874c17f1a7da7c96bd85fccff9312c518fac6e9", + "0xab4cfa02065017dd7f1aadc66f2c92f78f0f11b8597c03a5d69d82cb2eaf95a4476a836ac102908f137662472c8d914b", + "0xa40b8cd8deb8ae503d20364d64cab7c2801b7728a9646ed19c65edea6a842756a2f636283494299584ad57f4bb12cd0b", + "0x8594e11d5fc2396bcd9dbf5509ce4816dbb2b7305168021c426171fb444d111da5a152d6835ad8034542277011c26c0e", + "0x8024de98c26b4c994a66628dc304bb737f4b6859c86ded552c5abb81fd4c6c2e19d5a30beed398a694b9b2fdea1dd06a", + "0x8843f5872f33f54df8d0e06166c1857d733995f67bc54abb8dfa94ad92407cf0179bc91b0a50bbb56cdc2b350d950329", + "0xb8bab44c7dd53ef9edf497dcb228e2a41282c90f00ba052fc52d57e87b5c8ab132d227af1fcdff9a12713d1f980bcaae", + "0x982b4d7b29aff22d527fd82d2a52601d95549bfb000429bb20789ed45e5abf1f4b7416c7b7c4b79431eb3574b29be658", + "0x8eb1f571b6a1878e11e8c1c757e0bc084bab5e82e897ca9be9b7f4b47b91679a8190bf0fc8f799d9b487da5442415857", + "0xa6e74b588e5af935c8b243e888582ef7718f8714569dd4992920740227518305eb35fab674d21a5551cca44b3e511ef2", + "0xa30fc2f3a4cb4f50566e82307de73cd7bd8fe2c1184e9293c136a9b9e926a018d57c6e4f308c95b9eb8299e94d90a2a1", + "0xa50c5869ca5d2b40722c056a32f918d47e0b65ca9d7863ca7d2fb4a7b64fe523fe9365cf0573733ceaadebf20b48fff8", + "0x83bbdd32c04d17581418cf360749c7a169b55d54f2427390defd9f751f100897b2d800ce6636c5bbc046c47508d60c8c", + "0xa82904bdf614de5d8deaff688c8a5e7ac5b3431687acbcda8fa53960b7c417a39c8b2e462d7af91ce6d79260f412db8e", + "0xa4362e31ff4b05d278b033cf5eebea20de01714ae16d4115d04c1da4754269873afc8171a6f56c5104bfd7b0db93c3e7", + "0xb5b8daa63a3735581e74a021b684a1038cea77168fdb7fdf83c670c2cfabcfc3ab2fc7359069b5f9048188351aef26b5", + "0xb48d723894b7782d96ac8433c48faca1bdfa5238019c451a7f47d958097cce3ae599b876cf274269236b9d6ff8b6d7ca", + "0x98ffff6a61a3a6205c7820a91ca2e7176fab5dba02bc194c4d14942ac421cb254183c705506ab279e4f8db066f941c6c", + "0xae7db24731da2eaa6efc4f7fcba2ecc26940ddd68038dce43acf2cee15b72dc4ef42a7bfdd32946d1ed78786dd7696b3", + "0xa656db14f1de9a7eb84f6301b4acb2fbf78bfe867f48a270e416c974ab92821eb4df1cb881b2d600cfed0034ac784641", + "0xaa315f8ecba85a5535e9a49e558b15f39520fce5d4bf43131bfbf2e2c9dfccc829074f9083e8d49f405fb221d0bc4c3c", + "0x90bffba5d9ff40a62f6c8e9fc402d5b95f6077ed58d030c93e321b8081b77d6b8dac3f63a92a7ddc01585cf2c127d66c", + "0xabdd733a36e0e0f05a570d0504e73801bf9b5a25ff2c78786f8b805704997acb2e6069af342538c581144d53149fa6d3", + "0xb4a723bb19e8c18a01bd449b1bb3440ddb2017f10bb153da27deb7a6a60e9bb37619d6d5435fbb1ba617687838e01dd0", + "0x870016b4678bab3375516db0187a2108b2e840bae4d264b9f4f27dbbc7cc9cac1d7dc582d7a04d6fd1ed588238e5e513", + "0x80d33d2e20e8fc170aa3cb4f69fffb72aeafb3b5bb4ea0bc79ab55da14142ca19b2d8b617a6b24d537366e3b49cb67c3", + "0xa7ee76aec273aaae03b3b87015789289551969fb175c11557da3ab77e39ab49d24634726f92affae9f4d24003050d974", + "0x8415ea4ab69d779ebd42d0fe0c6aef531d6a465a5739e429b1fcf433ec45aa8296c527e965a20f0ec9f340c9273ea3cf", + "0x8c7662520794e8b4405d0b33b5cac839784bc86a5868766c06cbc1fa306dbe334978177417b31baf90ce7b0052a29c56", + "0x902b2abecc053a3dbdea9897ee21e74821f3a1b98b2d560a514a35799f4680322550fd3a728d4f6d64e1de98033c32b8", + "0xa05e84ed9ecab8d508d670c39f2db61ad6e08d2795ec32a3c9d0d3737ef3801618f4fc2a95f90ec2f068606131e076c5", + "0x8b9208ff4d5af0c2e3f53c9375da666773ac57197dfabb0d25b1c8d0588ba7f3c15ee9661bb001297f322ea2fbf6928b", + "0xa3c827741b34a03254d4451b5ab74a96f2b9f7fb069e2f5adaf54fd97cc7a4d516d378db5ca07da87d8566d6eef13726", + "0x8509d8a3f4a0ed378e0a1e28ea02f6bf1d7f6c819c6c2f5297c7df54c895b848f841653e32ba2a2c22c2ff739571acb8", + "0xa0ce988b7d3c40b4e496aa83a09e4b5472a2d98679622f32bea23e6d607bc7de1a5374fb162bce0549a67dad948519be", + "0xaa8a3dd12bd60e3d2e05f9c683cdcb8eab17fc59134815f8d197681b1bcf65108cba63ac5c58ee632b1e5ed6bba5d474", + "0x8b955f1d894b3aefd883fb4b65f14cd37fc2b9db77db79273f1700bef9973bf3fd123897ea2b7989f50003733f8f7f21", + "0xac79c00ddac47f5daf8d9418d798d8af89fc6f1682e7e451f71ea3a405b0d36af35388dd2a332af790bc83ca7b819328", + "0xa0d44dd2a4438b809522b130d0938c3fe7c5c46379365dbd1810a170a9aa5818e1c783470dd5d0b6d4ac7edbb7330910", + "0xa30b69e39ad43dd540a43c521f05b51b5f1b9c4eed54b8162374ae11eac25da4f5756e7b70ce9f3c92c2eeceee7431ed", + "0xac43220b762c299c7951222ea19761ab938bf38e4972deef58ed84f4f9c68c230647cf7506d7cbfc08562fcca55f0485", + "0xb28233b46a8fb424cfa386a845a3b5399d8489ceb83c8f3e05c22c934798d639c93718b7b68ab3ce24c5358339e41cbb", + "0xac30d50ee8ce59a10d4b37a3a35e62cdb2273e5e52232e202ca7d7b8d09d28958ee667fae41a7bb6cdc6fe8f6e6c9c85", + "0xb199842d9141ad169f35cc7ff782b274cbaa645fdb727761e0a89edbf0d781a15f8218b4bf4eead326f2903dd88a9cc1", + "0x85e018c7ddcad34bb8285a737c578bf741ccd547e68c734bdb3808380e12c5d4ef60fc896b497a87d443ff9abd063b38", + "0x8c856e6ba4a815bdb891e1276f93545b7072f6cb1a9aa6aa5cf240976f29f4dee01878638500a6bf1daf677b96b54343", + "0xb8a47555fa8710534150e1a3f13eab33666017be6b41005397afa647ea49708565f2b86b77ad4964d140d9ced6b4d585", + "0x8cd1f1db1b2f4c85a3f46211599caf512d5439e2d8e184663d7d50166fd3008f0e9253272f898d81007988435f715881", + "0xb1f34b14612c973a3eceb716dc102b82ab18afef9de7630172c2780776679a7706a4874e1df3eaadf541fb009731807f", + "0xb25464af9cff883b55be2ff8daf610052c02df9a5e147a2cf4df6ce63edcdee6dc535c533590084cc177da85c5dc0baa", + "0x91c3c4b658b42d8d3448ae1415d4541d02379a40dc51e36a59bd6e7b9ba3ea51533f480c7c6e8405250ee9b96a466c29", + "0x86dc027b95deb74c36a58a1333a03e63cb5ae22d3b29d114cfd2271badb05268c9d0c819a977f5e0c6014b00c1512e3a", + "0xae0e6ff58eb5fa35da5107ebeacf222ab8f52a22bb1e13504247c1dfa65320f40d97b0e6b201cb6613476687cb2f0681", + "0x8f13415d960b9d7a1d93ef28afc2223e926639b63bdefce0f85e945dfc81670a55df288893a0d8b3abe13c5708f82f91", + "0x956f67ca49ad27c1e3a68c1faad5e7baf0160c459094bf6b7baf36b112de935fdfd79fa4a9ea87ea8de0ac07272969f4", + "0x835e45e4a67df9fb51b645d37840b3a15c171d571a10b03a406dd69d3c2f22df3aa9c5cbe1e73f8d767ce01c4914ea9a", + "0x919b938e56d4b32e2667469d0bdccb95d9dda3341aa907683ee70a14bbbe623035014511c261f4f59b318b610ac90aa3", + "0x96b48182121ccd9d689bf1dfdc228175564cd68dc904a99c808a7f0053a6f636c9d953e12198bdf2ea49ea92772f2e18", + "0xac5e5a941d567fa38fdbcfa8cf7f85bb304e3401c52d88752bcd516d1fa9bac4572534ea2205e38423c1df065990790f", + "0xac0bd594fb85a8d4fc26d6df0fa81f11919401f1ecf9168b891ec7f061a2d9368af99f7fd8d9b43b2ce361e7b8482159", + "0x83d92c69ca540d298fe80d8162a1c7af3fa9b49dfb69e85c1d136a3ec39fe419c9fa78e0bb6d96878771fbd37fe92e40", + "0xb35443ae8aa66c763c2db9273f908552fe458e96696b90e41dd509c17a5c04ee178e3490d9c6ba2dc0b8f793c433c134", + "0x923b2d25aa45b2e580ffd94cbb37dc8110f340f0f011217ee1bd81afb0714c0b1d5fb4db86006cdd2457563276f59c59", + "0x96c9125d38fca1a61ac21257b696f8ac3dae78def50285e44d90ea293d591d1c58f703540a7e4e99e070afe4646bbe15", + "0xb57946b2332077fbcdcb406b811779aefd54473b5559a163cd65cb8310679b7e2028aa55c12a1401fdcfcac0e6fae29a", + "0x845daedc5cf972883835d7e13c937b63753c2200324a3b8082a6c4abb4be06c5f7c629d4abe4bfaf1d80a1f073eb6ce6", + "0x91a55dfd0efefcd03dc6dacc64ec93b8d296cb83c0ee72400a36f27246e7f2a60e73b7b70ba65819e9cfb73edb7bd297", + "0x8874606b93266455fe8fdd25df9f8d2994e927460af06f2e97dd4d2d90db1e6b06d441b72c2e76504d753badca87fb37", + "0x8ee99e6d231274ff9252c0f4e84549da173041299ad1230929c3e3d32399731c4f20a502b4a307642cac9306ccd49d3c", + "0x8836497714a525118e20849d6933bb8535fb6f72b96337d49e3133d936999c90a398a740f42e772353b5f1c63581df6d", + "0xa6916945e10628f7497a6cdc5e2de113d25f7ade3e41e74d3de48ccd4fce9f2fa9ab69645275002e6f49399b798c40af", + "0x9597706983107eb23883e0812e1a2c58af7f3499d50c6e29b455946cb9812fde1aa323d9ed30d1c0ffd455abe32303cd", + "0xa24ee89f7f515cc33bdbdb822e7d5c1877d337f3b2162303cfc2dae028011c3a267c5cb4194afa63a4856a6e1c213448", + "0x8cd25315e4318801c2776824ae6e7d543cb85ed3bc2498ba5752df2e8142b37653cf9e60104d674be3aeb0a66912e97a", + "0xb5085ecbe793180b40dbeb879f4c976eaaccaca3a5246807dced5890e0ed24d35f3f86955e2460e14fb44ff5081c07ba", + "0x960188cc0b4f908633a6840963a6fa2205fc42c511c6c309685234911c5304ef4c304e3ae9c9c69daa2fb6a73560c256", + "0xa32d0a70bf15d569b4cda5aebe3e41e03c28bf99cdd34ffa6c5d58a097f322772acca904b3a47addb6c7492a7126ebac", + "0x977f72d06ad72d4aa4765e0f1f9f4a3231d9f030501f320fe7714cc5d329d08112789fa918c60dd7fdb5837d56bb7fc6", + "0x99fa038bb0470d45852bb871620d8d88520adb701712fcb1f278fed2882722b9e729e6cdce44c82caafad95e37d0e6f7", + "0xb855e8f4fc7634ada07e83b6c719a1e37acb06394bc8c7dcab7747a8c54e5df3943915f021364bd019fdea103864e55f", + "0x88bc2cd7458532e98c596ef59ea2cf640d7cc31b4c33cef9ed065c078d1d4eb49677a67de8e6229cc17ea48bace8ee5a", + "0xaaa78a3feaa836d944d987d813f9b9741afb076e6aca1ffa42682ab06d46d66e0c07b8f40b9dbd63e75e81efa1ef7b08", + "0xb7b080420cc4d808723b98b2a5b7b59c81e624ab568ecdfdeb8bf3aa151a581b6f56e983ef1b6f909661e25db40b0c69", + "0xabee85c462ac9a2c58e54f06c91b3e5cd8c5f9ab5b5deb602b53763c54826ed6deb0d6db315a8d7ad88733407e8d35e2", + "0x994d075c1527407547590df53e9d72dd31f037c763848d1662eebd4cefec93a24328c986802efa80e038cb760a5300f5", + "0xab8777640116dfb6678e8c7d5b36d01265dfb16321abbfc277da71556a34bb3be04bc4ae90124ed9c55386d2bfb3bda0", + "0x967e3a828bc59409144463bcf883a3a276b5f24bf3cbfdd7a42343348cba91e00b46ac285835a9b91eef171202974204", + "0x875a9f0c4ffe5bb1d8da5e3c8e41d0397aa6248422a628bd60bfae536a651417d4e8a7d2fb98e13f2dad3680f7bd86d3", + "0xacaa330c3e8f95d46b1880126572b238dbb6d04484d2cd4f257ab9642d8c9fc7b212188b9c7ac9e0fd135c520d46b1bf", + "0xaceb762edbb0f0c43dfcdb01ea7a1ac5918ca3882b1e7ebc4373521742f1ed5250d8966b498c00b2b0f4d13212e6dd0b", + "0x81d072b4ad258b3646f52f399bced97c613b22e7ad76373453d80b1650c0ca87edb291a041f8253b649b6e5429bb4cff", + "0x980a47d27416ac39c7c3a0ebe50c492f8c776ea1de44d5159ac7d889b6d554357f0a77f0e5d9d0ff41aae4369eba1fc2", + "0x8b4dfd5ef5573db1476d5e43aacfb5941e45d6297794508f29c454fe50ea622e6f068b28b3debe8635cf6036007de2e3", + "0xa60831559d6305839515b68f8c3bc7abbd8212cc4083502e19dd682d56ca37c9780fc3ce4ec2eae81ab23b221452dc57", + "0x951f6b2c1848ced9e8a2339c65918e00d3d22d3e59a0a660b1eca667d18f8430d737884e9805865ef3ed0fe1638a22d9", + "0xb02e38fe790b492aa5e89257c4986c9033a8b67010fa2add9787de857d53759170fdd67715ca658220b4e14b0ca48124", + "0xa51007e4346060746e6b0e4797fc08ef17f04a34fe24f307f6b6817edbb8ce2b176f40771d4ae8a60d6152cbebe62653", + "0xa510005b05c0b305075b27b243c9d64bcdce85146b6ed0e75a3178b5ff9608213f08c8c9246f2ca6035a0c3e31619860", + "0xaaff4ef27a7a23be3419d22197e13676d6e3810ceb06a9e920d38125745dc68a930f1741c9c2d9d5c875968e30f34ab5", + "0x864522a9af9857de9814e61383bebad1ba9a881696925a0ea6bfc6eff520d42c506bbe5685a9946ed710e889765be4a0", + "0xb63258c080d13f3b7d5b9f3ca9929f8982a6960bdb1b0f8676f4dca823971601672f15e653917bf5d3746bb220504913", + "0xb51ce0cb10869121ae310c7159ee1f3e3a9f8ad498827f72c3d56864808c1f21fa2881788f19ece884d3f705cd7bd0c5", + "0x95d9cecfc018c6ed510e441cf84c712d9909c778c16734706c93222257f64dcd2a9f1bd0b400ca271e22c9c487014274", + "0x8beff4d7d0140b86380ff4842a9bda94c2d2be638e20ac68a4912cb47dbe01a261857536375208040c0554929ced1ddc", + "0x891ff49258749e2b57c1e9b8e04b12c77d79c3308b1fb615a081f2aacdfb4b39e32d53e069ed136fdbd43c53b87418fa", + "0x9625cad224e163d387738825982d1e40eeff35fe816d10d7541d15fdc4d3eee48009090f3faef4024b249205b0b28f72", + "0x8f3947433d9bd01aa335895484b540a9025a19481a1c40b4f72dd676bfcf332713714fd4010bde936eaf9470fd239ed0", + "0xa00ec2d67789a7054b53f0e858a8a232706ccc29a9f3e389df7455f1a51a2e75801fd78469a13dbc25d28399ae4c6182", + "0xa3f65884506d4a62b8775a0ea0e3d78f5f46bc07910a93cd604022154eabdf1d73591e304d61edc869e91462951975e1", + "0xa14eef4fd5dfac311713f0faa9a60415e3d30b95a4590cbf95f2033dffb4d16c02e7ceff3dcd42148a4e3bc49cce2dd4", + "0x8afa11c0eef3c540e1e3460bc759bb2b6ea90743623f88e62950c94e370fe4fd01c22b6729beba4dcd4d581198d9358f", + "0xafb05548a69f0845ffcc5f5dc63e3cdb93cd270f5655173b9a950394b0583663f2b7164ba6df8d60c2e775c1d9f120af", + "0x97f179e01a947a906e1cbeafa083960bc9f1bade45742a3afee488dfb6011c1c6e2db09a355d77f5228a42ccaa7bdf8e", + "0x8447fca4d35f74b3efcbd96774f41874ca376bf85b79b6e66c92fa3f14bdd6e743a051f12a7fbfd87f319d1c6a5ce217", + "0xa57ca39c23617cd2cf32ff93b02161bd7baf52c4effb4679d9d5166406e103bc8f3c6b5209e17c37dbb02deb8bc72ddd", + "0x9667c7300ff80f0140be002b0e36caab07aaee7cce72679197c64d355e20d96196acaf54e06e1382167d081fe6f739c1", + "0x828126bb0559ce748809b622677267ca896fa2ee76360fd2c02990e6477e06a667241379ca7e65d61a5b64b96d7867de", + "0x8b8835dea6ba8cf61c91f01a4b3d2f8150b687a4ee09b45f2e5fc8f80f208ae5d142d8e3a18153f0722b90214e60c5a7", + "0xa98e8ff02049b4da386e3ee93db23bbb13dfeb72f1cfde72587c7e6d962780b7671c63e8ac3fbaeb1a6605e8d79e2f29", + "0x87a4892a0026d7e39ef3af632172b88337cb03669dea564bcdb70653b52d744730ebb5d642e20cb627acc9dbb547a26b", + "0x877352a22fc8052878a57effc159dac4d75fe08c84d3d5324c0bab6d564cdf868f33ceee515eee747e5856b62cfa0cc7", + "0x8b801ba8e2ff019ee62f64b8cb8a5f601fc35423eb0f9494b401050103e1307dc584e4e4b21249cd2c686e32475e96c3", + "0xa9e7338d6d4d9bfec91b2af28a8ed13b09415f57a3a00e5e777c93d768fdb3f8e4456ae48a2c6626b264226e911a0e28", + "0x99c05fedf40ac4726ed585d7c1544c6e79619a0d3fb6bda75a08c7f3c0008e8d5e19ed4da48de3216135f34a15eba17c", + "0xa61cce8a1a8b13a4a650fdbec0eeea8297c352a8238fb7cac95a0df18ed16ee02a3daa2de108fa122aca733bd8ad7855", + "0xb97f37da9005b440b4cb05870dd881bf8491fe735844f2d5c8281818583b38e02286e653d9f2e7fa5e74c3c3eb616540", + "0xa72164a8554da8e103f692ac5ebb4aece55d5194302b9f74b6f2a05335b6e39beede0bf7bf8c5bfd4d324a784c5fb08c", + "0xb87e8221c5341cd9cc8bb99c10fe730bc105550f25ed4b96c0d45e6142193a1b2e72f1b3857373a659b8c09be17b3d91", + "0xa41fb1f327ef91dcb7ac0787918376584890dd9a9675c297c45796e32d6e5985b12f9b80be47fc3a8596c245f419d395", + "0x90dafa3592bdbb3465c92e2a54c2531822ba0459d45d3e7a7092fa6b823f55af28357cb51896d4ec2d66029c82f08e26", + "0xa0a9adc872ebc396557f484f1dd21954d4f4a21c4aa5eec543f5fa386fe590839735c01f236574f7ff95407cd12de103", + "0xb8c5c940d58be7538acf8672852b5da3af34f82405ef2ce8e4c923f1362f97fc50921568d0fd2fe846edfb0823e62979", + "0x85aaf06a8b2d0dac89dafd00c28533f35dbd074978c2aaa5bef75db44a7b12aeb222e724f395513b9a535809a275e30b", + "0x81f3cbe82fbc7028c26a6c1808c604c63ba023a30c9f78a4c581340008dbda5ec07497ee849a2183fcd9124f7936af32", + "0xa11ac738de75fd60f15a34209d3825d5e23385796a4c7fc5931822f3f380af977dd0f7b59fbd58eed7777a071e21b680", + "0x85a279c493de03db6fa6c3e3c1b1b29adc9a8c4effc12400ae1128da8421954fa8b75ad19e5388fe4543b76fb0812813", + "0x83a217b395d59ab20db6c4adb1e9713fc9267f5f31a6c936042fe051ce8b541f579442f3dcf0fa16b9e6de9fd3518191", + "0x83a0b86e7d4ed8f9ccdc6dfc8ff1484509a6378fa6f09ed908e6ab9d1073f03011dc497e14304e4e3d181b57de06a5ab", + "0xa63ad69c9d25704ce1cc8e74f67818e5ed985f8f851afa8412248b2df5f833f83b95b27180e9e7273833ed0d07113d3b", + "0x99b1bc2021e63b561fe44ddd0af81fcc8627a91bfeecbbc989b642bc859abc0c8d636399701aad7bbaf6a385d5f27d61", + "0xb53434adb66f4a807a6ad917c6e856321753e559b1add70824e5c1e88191bf6993fccb9b8b911fc0f473fb11743acacd", + "0x97ed3b9e6fb99bf5f945d4a41f198161294866aa23f2327818cdd55cb5dc4c1a8eff29dd8b8d04902d6cd43a71835c82", + "0xb1e808260e368a18d9d10bdea5d60223ba1713b948c782285a27a99ae50cc5fc2c53d407de07155ecc16fb8a36d744a0", + "0xa3eb4665f18f71833fec43802730e56b3ee5a357ea30a888ad482725b169d6f1f6ade6e208ee081b2e2633079b82ba7d", + "0xab8beb2c8353fc9f571c18fdd02bdb977fc883313469e1277b0372fbbb33b80dcff354ca41de436d98d2ed710faa467e", + "0xaa9071cfa971e4a335a91ad634c98f2be51544cb21f040f2471d01bb97e1df2277ae1646e1ea8f55b7ba9f5c8c599b39", + "0x80b7dbfdcaf40f0678012acc634eba44ea51181475180d9deb2050dc4f2de395289edd0223018c81057ec79b04b04c49", + "0x89623d7f6cb17aa877af14de842c2d4ab7fd576d61ddd7518b5878620a01ded40b6010de0da3cdf31d837eecf30e9847", + "0xa773bb024ae74dd24761f266d4fb27d6fd366a8634febe8235376b1ae9065c2fe12c769f1d0407867dfbe9f5272c352f", + "0x8455a561c3aaa6ba64c881a5e13921c592b3a02e968f4fb24a2243c36202795d0366d9cc1a24e916f84d6e158b7aeac7", + "0x81d8bfc4b283cf702a40b87a2b96b275bdbf0def17e67d04842598610b67ea08c804d400c3e69fa09ea001eaf345b276", + "0xb8f8f82cb11fea1c99467013d7e167ff03deb0c65a677fab76ded58826d1ba29aa7cf9fcd7763615735ea3ad38e28719", + "0x89a6a04baf9cccc1db55179e1650b1a195dd91fb0aebc197a25143f0f393524d2589975e3fbfc2547126f0bced7fd6f2", + "0xb81b2162df045390f04df07cbd0962e6b6ca94275a63edded58001a2f28b2ae2af2c7a6cba4ecd753869684e77e7e799", + "0xa3757f722776e50de45c62d9c4a2ee0f5655a512344c4cbec542d8045332806568dd626a719ef21a4eb06792ca70f204", + "0x8c5590df96ec22179a4e8786de41beb44f987a1dcc508eb341eecbc0b39236fdfad47f108f852e87179ccf4e10091e59", + "0x87502f026ed4e10167419130b88c3737635c5b9074c364e1dd247cef5ef0fc064b4ae99b187e33301e438bbd2fe7d032", + "0xaf925a2165e980ced620ff12289129fe17670a90ae0f4db9d4b39bd887ccb1f5d2514ac9ecf910f6390a8fc66bd5be17", + "0x857fca899828cf5c65d26e3e8a6e658542782fc72762b3b9c73514919f83259e0f849a9d4838b40dc905fe43024d0d23", + "0x87ffebdbfb69a9e1007ebac4ffcb4090ff13705967b73937063719aa97908986effcb7262fdadc1ae0f95c3690e3245d", + "0xa9ff6c347ac6f4c6ab993b748802e96982eaf489dc69032269568412fc9a79e7c2850dfc991b28211b3522ee4454344b", + "0xa65b3159df4ec48bebb67cb3663cd744027ad98d970d620e05bf6c48f230fa45bf17527fe726fdf705419bb7a1bb913e", + "0x84b97b1e6408b6791831997b03cd91f027e7660fd492a93d95daafe61f02427371c0e237c75706412f442991dfdff989", + "0xab761c26527439b209af0ae6afccd9340bbed5fbe098734c3145b76c5d2cd7115d9227b2eb523882b7317fbb09180498", + "0xa0479a8da06d7a69c0b0fee60df4e691c19c551f5e7da286dab430bfbcabf31726508e20d26ea48c53365a7f00a3ad34", + "0xa732dfc9baa0f4f40b5756d2e8d8937742999623477458e0bc81431a7b633eefc6f53b3b7939fe0a020018549c954054", + "0x901502436a1169ba51dc479a5abe7c8d84e0943b16bc3c6a627b49b92cd46263c0005bc324c67509edd693f28e612af1", + "0xb627aee83474e7f84d1bab9b7f6b605e33b26297ac6bbf52d110d38ba10749032bd551641e73a383a303882367af429b", + "0x95108866745760baef4a46ef56f82da6de7e81c58b10126ebd2ba2cd13d339f91303bf2fb4dd104a6956aa3b13739503", + "0x899ed2ade37236cec90056f3569bc50f984f2247792defafcceb49ad0ca5f6f8a2f06573705300e07f0de0c759289ff5", + "0xa9f5eee196d608efe4bcef9bf71c646d27feb615e21252cf839a44a49fd89da8d26a758419e0085a05b1d59600e2dc42", + "0xb36c6f68fed6e6c85f1f4a162485f24817f2843ec5cbee45a1ebfa367d44892e464949c6669f7972dc7167af08d55d25", + "0xaaaede243a9a1b6162afbc8f571a52671a5a4519b4062e3f26777664e245ba873ed13b0492c5dbf0258c788c397a0e9e", + "0x972b4fb39c31cbe127bf9a32a5cc10d621ebdd9411df5e5da3d457f03b2ab2cd1f6372d8284a4a9400f0b06ecdbfd38e", + "0x8f6ca1e110e959a4b1d9a5ce5f212893cec21db40d64d5ac4d524f352d72198f923416a850bf845bc5a22a79c0ea2619", + "0xa0f3c93b22134f66f04b2553a53b738644d1665ceb196b8494b315a4c28236fb492017e4a0de4224827c78e42f9908b7", + "0x807fb5ee74f6c8735b0b5ca07e28506214fe4047dbeb00045d7c24f7849e98706aea79771241224939cb749cf1366c7d", + "0x915eb1ff034224c0b645442cdb7d669303fdc00ca464f91aaf0b6fde0b220a3a74ff0cb043c26c9f3a5667b3fdaa9420", + "0x8fda6cef56ed33fefffa9e6ac8e6f76b1af379f89761945c63dd448801f7bb8ca970504a7105fac2f74f652ccff32327", + "0x87380cffdcffb1d0820fa36b63cc081e72187f86d487315177d4d04da4533eb19a0e2ff6115ceab528887819c44a5164", + "0x8cd89e03411a18e7f16f968b89fb500c36d47d229f6487b99e62403a980058db5925ce249206743333538adfad168330", + "0x974451b1df33522ce7056de9f03e10c70bf302c44b0741a59df3d6877d53d61a7394dcee1dd46e013d7cb9d73419c092", + "0x98c35ddf645940260c490f384a49496a7352bb8e3f686feed815b1d38f59ded17b1ad6e84a209e773ed08f7b8ff1e4c2", + "0x963f386cf944bb9b2ddebb97171b64253ea0a2894ac40049bdd86cda392292315f3a3d490ca5d9628c890cfb669f0acb", + "0x8d507712152babd6d142ee682638da8495a6f3838136088df9424ef50d5ec28d815a198c9a4963610b22e49b4cdf95e9", + "0x83d4bc6b0be87c8a4f1e9c53f257719de0c73d85b490a41f7420e777311640937320557ff2f1d9bafd1daaa54f932356", + "0x82f5381c965b7a0718441131c4d13999f4cdce637698989a17ed97c8ea2e5bdb5d07719c5f7be8688edb081b23ede0f4", + "0xa6ebecab0b72a49dfd01d69fa37a7f74d34fb1d4fef0aa10e3d6fceb9eccd671225c230af89f6eb514250e41a5f91f52", + "0x846d185bdad6e11e604df7f753b7a08a28b643674221f0e750ebdb6b86ec584a29c869e131bca868972a507e61403f6a", + "0x85a98332292acb744bd1c0fd6fdcf1f889a78a2c9624d79413ffa194cc8dfa7821a4b60cde8081d4b5f71f51168dd67f", + "0x8f7d97c3b4597880d73200d074eb813d95432306e82dafc70b580b8e08cb8098b70f2d07b4b3ac6a4d77e92d57035031", + "0x8185439c8751e595825d7053518cbe121f191846a38d4dbcb558c3f9d7a3104f3153401adaaaf27843bbe2edb504bfe3", + "0xb3c00d8ece1518fca6b1215a139b0a0e26d9cba1b3a424f7ee59f30ce800a5db967279ed60958dd1f3ee69cf4dd1b204", + "0xa2e6cb6978e883f9719c3c0d44cfe8de0cc6f644b98f98858433bea8bbe7b612c8aca5952fccce4f195f9d54f9722dc2", + "0x99663087e3d5000abbec0fbda4e7342ec38846cc6a1505191fb3f1a337cb369455b7f8531a6eb8b0f7b2c4baf83cbe2b", + "0xab0836c6377a4dbc7ca6a4d6cf021d4cd60013877314dd05f351706b128d4af6337711ed3443cb6ca976f40d74070a9a", + "0x87abfd5126152fd3bac3c56230579b489436755ea89e0566aa349490b36a5d7b85028e9fb0710907042bcde6a6f5d7e3", + "0x974ba1033f75f60e0cf7c718a57ae1da3721cf9d0fb925714c46f027632bdd84cd9e6de4cf4d00bc55465b1c5ebb7384", + "0xa607b49d73689ac64f25cec71221d30d53e781e1100d19a2114a21da6507a60166166369d860bd314acb226596525670", + "0xa7c2b0b915d7beba94954f2aa7dd08ec075813661e2a3ecca5d28a0733e59583247fed9528eb28aba55b972cdbaf06eb", + "0xb8b3123e44128cc8efbe3270f2f94e50ca214a4294c71c3b851f8cbb70cb67fe9536cf07d04bf7fe380e5e3a29dd3c15", + "0xa59a07e343b62ad6445a0859a32b58c21a593f9ddbfe52049650f59628c93715aa1f4e1f45b109321756d0eeec8a5429", + "0x94f51f8a4ed18a6030d0aaa8899056744bd0e9dc9ac68f62b00355cddab11da5da16798db75f0bfbce0e5bdfe750c0b6", + "0x97460a97ca1e1fa5ce243b81425edc0ec19b7448e93f0b55bc9785eedeeafe194a3c8b33a61a5c72990edf375f122777", + "0x8fa859a089bc17d698a7ee381f37ce9beadf4e5b44fce5f6f29762bc04f96faff5d58c48c73631290325f05e9a1ecf49", + "0xabdf38f3b20fc95eff31de5aa9ef1031abfa48f1305ee57e4d507594570401503476d3bcc493838fc24d6967a3082c7f", + "0xb8914bfb82815abb86da35c64d39ab838581bc0bf08967192697d9663877825f2b9d6fbdcf9b410463482b3731361aef", + "0xa8187f9d22b193a5f578999954d6ec9aa9b32338ccadb8a3e1ce5bad5ea361d69016e1cdfac44e9d6c54e49dd88561b9", + "0xaac262cb7cba7fd62c14daa7b39677cabc1ef0947dd06dd89cac8570006a200f90d5f0353e84f5ff03179e3bebe14231", + "0xa630ef5ece9733b8c46c0a2df14a0f37647a85e69c63148e79ffdcc145707053f9f9d305c3f1cf3c7915cb46d33abd07", + "0xb102c237cb2e254588b6d53350dfda6901bd99493a3fbddb4121d45e0b475cf2663a40d7b9a75325eda83e4ba1e68cb3", + "0x86a930dd1ddcc16d1dfa00aa292cb6c2607d42c367e470aa920964b7c17ab6232a7108d1c2c11fc40fb7496547d0bbf8", + "0xa832fdc4500683e72a96cce61e62ac9ee812c37fe03527ad4cf893915ca1962cee80e72d4f82b20c8fc0b764376635a1", + "0x88ad985f448dabb04f8808efd90f273f11f5e6d0468b5489a1a6a3d77de342992a73eb842d419034968d733f101ff683", + "0x98a8538145f0d86f7fbf9a81c9140f6095c5bdd8960b1c6f3a1716428cd9cca1bf8322e6d0af24e6169abcf7df2b0ff6", + "0x9048c6eba5e062519011e177e955a200b2c00b3a0b8615bdecdebc217559d41058d3315f6d05617be531ef0f6aef0e51", + "0x833bf225ab6fc68cdcacf1ec1b50f9d05f5410e6cdcd8d56a3081dc2be8a8d07b81534d1ec93a25c2e270313dfb99e3b", + "0xa84bcd24c3da5e537e64a811b93c91bfc84d7729b9ead7f79078989a6eb76717d620c1fad17466a0519208651e92f5ff", + "0xb7cdd0a3fbd79aed93e1b5a44ca44a94e7af5ed911e4492f332e3a5ed146c7286bde01b52276a2fcc02780d2109874dd", + "0x8a19a09854e627cb95750d83c20c67442b66b35896a476358f993ba9ac114d32c59c1b3d0b8787ee3224cf3888b56c64", + "0xa9abd5afb8659ee52ada8fa5d57e7dd355f0a7350276f6160bec5fbf70d5f99234dd179eb221c913e22a49ec6d267846", + "0x8c13c4274c0d30d184e73eaf812200094bbbd57293780bdadbceb262e34dee5b453991e7f37c7333a654fc71c69d6445", + "0xa4320d73296ff8176ce0127ca1921c450e2a9c06eff936681ebaffb5a0b05b17fded24e548454de89aca2dcf6d7a9de4", + "0xb2b8b3e15c1f645f07783e5628aba614e60157889db41d8161d977606788842b67f83f361eae91815dc0abd84e09abd5", + "0xad26c3aa35ddfddc15719b8bb6c264aaec7065e88ac29ba820eb61f220fef451609a7bb037f3722d022e6c86e4f1dc88", + "0xb8615bf43e13ae5d7b8dd903ce37190800cd490f441c09b22aa29d7a29ed2c0417b7a08ead417868f1de2589deaadd80", + "0x8d3425e1482cd1e76750a76239d33c06b3554c3c3c87c15cb7ab58b1cee86a4c5c4178b44e23f36928365a1b484bde02", + "0x806893a62e38c941a7dd6f249c83af16596f69877cc737d8f73f6b8cd93cbc01177a7a276b2b8c6b0e5f2ad864db5994", + "0x86618f17fa4b0d65496b661bbb5ba3bc3a87129d30a4b7d4f515b904f4206ca5253a41f49fd52095861e5e065ec54f21", + "0x9551915da1304051e55717f4c31db761dcdcf3a1366c89a4af800a9e99aca93a357bf928307f098e62b44a02cb689a46", + "0x8f79c4ec0ec1146cb2a523b52fe33def90d7b5652a0cb9c2d1c8808a32293e00aec6969f5b1538e3a94cd1efa3937f86", + "0xa0c03e329a707300081780f1e310671315b4c6a4cedcb29697aedfabb07a9d5df83f27b20e9c44cf6b16e39d9ded5b98", + "0x86a7cfa7c8e7ce2c01dd0baec2139e97e8e090ad4e7b5f51518f83d564765003c65968f85481bbb97cb18f005ccc7d9f", + "0xa33811770c6dfda3f7f74e6ad0107a187fe622d61b444bbd84fd7ef6e03302e693b093df76f6ab39bb4e02afd84a575a", + "0x85480f5c10d4162a8e6702b5e04f801874d572a62a130be94b0c02b58c3c59bdcd48cd05f0a1c2839f88f06b6e3cd337", + "0x8e181011564b17f7d787fe0e7f3c87f6b62da9083c54c74fd6c357a1f464c123c1d3d8ade3cf72475000b464b14e2be3", + "0x8ee178937294b8c991337e0621ab37e9ffa4ca2bdb3284065c5e9c08aad6785d50cf156270ff9daf9a9127289710f55b", + "0x8bd1e8e2d37379d4b172f1aec96f2e41a6e1393158d7a3dbd9a95c8dd4f8e0b05336a42efc11a732e5f22b47fc5c271d", + "0x8f3da353cd487c13136a85677de8cedf306faae0edec733cf4f0046f82fa4639db4745b0095ff33a9766aba50de0cbcf", + "0x8d187c1e97638df0e4792b78e8c23967dac43d98ea268ca4aabea4e0fa06cb93183fd92d4c9df74118d7cc27bf54415e", + "0xa4c992f08c2f8bac0b74b3702fb0c75c9838d2ce90b28812019553d47613c14d8ce514d15443159d700b218c5a312c49", + "0xa6fd1874034a34c3ea962a316c018d9493d2b3719bb0ec4edbc7c56b240802b2228ab49bee6f04c8a3e9f6f24a48c1c2", + "0xb2efed8e799f8a15999020900dc2c58ece5a3641c90811b86a5198e593d7318b9d53b167818ccdfbe7df2414c9c34011", + "0x995ff7de6181ddf95e3ead746089c6148da3508e4e7a2323c81785718b754d356789b902e7e78e2edc6b0cbd4ff22c78", + "0x944073d24750a9068cbd020b834afc72d2dde87efac04482b3287b40678ad07588519a4176b10f2172a2c463d063a5cd", + "0x99db4b1bb76475a6fd75289986ef40367960279524378cc917525fb6ba02a145a218c1e9caeb99332332ab486a125ac0", + "0x89fce4ecd420f8e477af4353b16faabb39e063f3f3c98fde2858b1f2d1ef6eed46f0975a7c08f233b97899bf60ccd60a", + "0x8c09a4f07a02b80654798bc63aada39fd638d3e3c4236ccd8a5ca280350c31e4a89e5f4c9aafb34116e71da18c1226b8", + "0x85325cfa7ded346cc51a2894257eab56e7488dbff504f10f99f4cd2b630d913003761a50f175ed167e8073f1b6b63fb0", + "0xb678b4fbec09a8cc794dcbca185f133578f29e354e99c05f6d07ac323be20aecb11f781d12898168e86f2e0f09aca15e", + "0xa249cfcbca4d9ba0a13b5f6aac72bf9b899adf582f9746bb2ad043742b28915607467eb794fca3704278f9136f7642be", + "0x9438e036c836a990c5e17af3d78367a75b23c37f807228362b4d13e3ddcb9e431348a7b552d09d11a2e9680704a4514f", + "0x925ab70450af28c21a488bfb5d38ac994f784cf249d7fd9ad251bb7fd897a23e23d2528308c03415074d43330dc37ef4", + "0xa290563904d5a8c0058fc8330120365bdd2ba1fdbaef7a14bc65d4961bb4217acfaed11ab82669e359531f8bf589b8db", + "0xa7e07a7801b871fc9b981a71e195a3b4ba6b6313bc132b04796a125157e78fe5c11a3a46cf731a255ac2d78a4ae78cd0", + "0xb26cd2501ee72718b0eebab6fb24d955a71f363f36e0f6dff0ab1d2d7836dab88474c0cef43a2cc32701fca7e82f7df3", + "0xa1dc3b6c968f3de00f11275092290afab65b2200afbcfa8ddc70e751fa19dbbc300445d6d479a81bda3880729007e496", + "0xa9bc213e28b630889476a095947d323b9ac6461dea726f2dc9084473ae8e196d66fb792a21905ad4ec52a6d757863e7d", + "0xb25d178df8c2df8051e7c888e9fa677fde5922e602a95e966db9e4a3d6b23ce043d7dc48a5b375c6b7c78e966893e8c3", + "0xa1c8d88d72303692eaa7adf68ea41de4febec40cc14ae551bb4012afd786d7b6444a3196b5d9d5040655a3366d96b7cd", + "0xb22bd44f9235a47118a9bbe2ba5a2ba9ec62476061be2e8e57806c1a17a02f9a51403e849e2e589520b759abd0117683", + "0xb8add766050c0d69fe81d8d9ea73e1ed05f0135d093ff01debd7247e42dbb86ad950aceb3b50b9af6cdc14ab443b238f", + "0xaf2cf95f30ef478f018cf81d70d47d742120b09193d8bb77f0d41a5d2e1a80bfb467793d9e2471b4e0ad0cb2c3b42271", + "0x8af5ef2107ad284e246bb56e20fef2a255954f72de791cbdfd3be09f825298d8466064f3c98a50496c7277af32b5c0bc", + "0x85dc19558572844c2849e729395a0c125096476388bd1b14fa7f54a7c38008fc93e578da3aac6a52ff1504d6ca82db05", + "0xae8c9b43c49572e2e166d704caf5b4b621a3b47827bb2a3bcd71cdc599bba90396fd9a405261b13e831bb5d44c0827d7", + "0xa7ba7efede25f02e88f6f4cbf70643e76784a03d97e0fbd5d9437c2485283ad7ca3abb638a5f826cd9f6193e5dec0b6c", + "0x94a9d122f2f06ef709fd8016fd4b712d88052245a65a301f5f177ce22992f74ad05552b1f1af4e70d1eac62cef309752", + "0x82d999b3e7cf563833b8bc028ff63a6b26eb357dfdb3fd5f10e33a1f80a9b2cfa7814d871b32a7ebfbaa09e753e37c02", + "0xaec6edcde234df502a3268dd2c26f4a36a2e0db730afa83173f9c78fcb2b2f75510a02b80194327b792811caefda2725", + "0x94c0bfa66c9f91d462e9194144fdd12d96f9bbe745737e73bab8130607ee6ea9d740e2cfcbbd00a195746edb6369ee61", + "0xab7573dab8c9d46d339e3f491cb2826cabe8b49f85f1ede78d845fc3995537d1b4ab85140b7d0238d9c24daf0e5e2a7e", + "0x87e8b16832843251fe952dadfd01d41890ed4bb4b8fa0254550d92c8cced44368225eca83a6c3ad47a7f81ff8a80c984", + "0x9189d2d9a7c64791b19c0773ad4f0564ce6bea94aa275a917f78ad987f150fdb3e5e26e7fef9982ac184897ecc04683f", + "0xb3661bf19e2da41415396ae4dd051a9272e8a2580b06f1a1118f57b901fa237616a9f8075af1129af4eabfefedbe2f1c", + "0xaf43c86661fb15daf5d910a4e06837225e100fb5680bd3e4b10f79a2144c6ec48b1f8d6e6b98e067d36609a5d038889a", + "0x82ac0c7acaa83ddc86c5b4249aae12f28155989c7c6b91e5137a4ce05113c6cbc16f6c44948b0efd8665362d3162f16a", + "0x8f268d1195ab465beeeb112cd7ffd5d5548559a8bc01261106d3555533fc1971081b25558d884d552df0db1cddda89d8", + "0x8ef7caa5521f3e037586ce8ac872a4182ee20c7921c0065ed9986c047e3dda08294da1165f385d008b40d500f07d895f", + "0x8c2f98f6880550573fad46075d3eba26634b5b025ce25a0b4d6e0193352c8a1f0661064027a70fe8190b522405f9f4e3", + "0xb7653f353564feb164f0f89ec7949da475b8dad4a4d396d252fc2a884f6932d027b7eb2dc4d280702c74569319ed701a", + "0xa026904f4066333befd9b87a8fad791d014096af60cdd668ef919c24dbe295ff31f7a790e1e721ba40cf5105abca67f4", + "0x988f982004ada07a22dd345f2412a228d7a96b9cae2c487de42e392afe1e35c2655f829ce07a14629148ce7079a1f142", + "0x9616add009067ed135295fb74d5b223b006b312bf14663e547a0d306694ff3a8a7bb9cfc466986707192a26c0bce599f", + "0xad4c425de9855f6968a17ee9ae5b15e0a5b596411388cf976df62ecc6c847a6e2ddb2cea792a5f6e9113c2445dba3e5c", + "0xb698ac9d86afa3dc69ff8375061f88e3b0cff92ff6dfe747cebaf142e813c011851e7a2830c10993b715e7fd594604a9", + "0xa386fa189847bb3b798efca917461e38ead61a08b101948def0f82cd258b945ed4d45b53774b400af500670149e601b7", + "0x905c95abda2c68a6559d8a39b6db081c68cef1e1b4be63498004e1b2f408409be9350b5b5d86a30fd443e2b3e445640a", + "0x9116dade969e7ce8954afcdd43e5cab64dc15f6c1b8da9d2d69de3f02ba79e6c4f6c7f54d6bf586d30256ae405cd1e41", + "0xa3084d173eacd08c9b5084a196719b57e47a0179826fda73466758235d7ecdb87cbcf097bd6b510517d163a85a7c7edd", + "0x85bb00415ad3c9be99ff9ba83672cc59fdd24356b661ab93713a3c8eab34e125d8867f628a3c3891b8dc056e69cd0e83", + "0x8d58541f9f39ed2ee4478acce5d58d124031338ec11b0d55551f00a5a9a6351faa903a5d7c132dc5e4bb026e9cbd18e4", + "0xa622adf72dc250e54f672e14e128c700166168dbe0474cecb340da175346e89917c400677b1bc1c11fcc4cc26591d9db", + "0xb3f865014754b688ca8372e8448114fff87bf3ca99856ab9168894d0c4679782c1ced703f5b74e851b370630f5e6ee86", + "0xa7e490b2c40c2446fcd91861c020da9742c326a81180e38110558bb5d9f2341f1c1885e79b364e6419023d1cbdc47380", + "0xb3748d472b1062e54572badbb8e87ac36534407f74932e7fc5b8392d008e8e89758f1671d1e4d30ab0fa40551b13bb5e", + "0x89898a5c5ec4313aabc607b0049fd1ebad0e0c074920cf503c9275b564d91916c2c446d3096491c950b7af3ac5e4b0ed", + "0x8eb8c83fef2c9dd30ea44e286e9599ec5c20aba983f702e5438afe2e5b921884327ad8d1566c72395587efac79ca7d56", + "0xb92479599e806516ce21fb0bd422a1d1d925335ebe2b4a0a7e044dd275f30985a72b97292477053ac5f00e081430da80", + "0xa34ae450a324fe8a3c25a4d653a654f9580ed56bbea213b8096987bbad0f5701d809a17076435e18017fea4d69f414bc", + "0x81381afe6433d62faf62ea488f39675e0091835892ecc238e02acf1662669c6d3962a71a3db652f6fe3bc5f42a0e5dc5", + "0xa430d475bf8580c59111103316fe1aa79c523ea12f1d47a976bbfae76894717c20220e31cf259f08e84a693da6688d70", + "0xb842814c359754ece614deb7d184d679d05d16f18a14b288a401cef5dad2cf0d5ee90bad487b80923fc5573779d4e4e8", + "0x971d9a2627ff2a6d0dcf2af3d895dfbafca28b1c09610c466e4e2bff2746f8369de7f40d65b70aed135fe1d72564aa88", + "0x8f4ce1c59e22b1ce7a0664caaa7e53735b154cfba8d2c5cc4159f2385843de82ab58ed901be876c6f7fce69cb4130950", + "0x86cc9dc321b6264297987000d344fa297ef45bcc2a4df04e458fe2d907ad304c0ea2318e32c3179af639a9a56f3263cf", + "0x8229e0876dfe8f665c3fb19b250bd89d40f039bbf1b331468b403655be7be2e104c2fd07b9983580c742d5462ca39a43", + "0x99299d73066e8eb128f698e56a9f8506dfe4bd014931e86b6b487d6195d2198c6c5bf15cccb40ccf1f8ddb57e9da44a2", + "0xa3a3be37ac554c574b393b2f33d0a32a116c1a7cfeaf88c54299a4da2267149a5ecca71f94e6c0ef6e2f472b802f5189", + "0xa91700d1a00387502cdba98c90f75fbc4066fefe7cc221c8f0e660994c936badd7d2695893fde2260c8c11d5bdcdd951", + "0x8e03cae725b7f9562c5c5ab6361644b976a68bada3d7ca508abca8dfc80a469975689af1fba1abcf21bc2a190dab397d", + "0xb01461ad23b2a8fa8a6d241e1675855d23bc977dbf4714add8c4b4b7469ccf2375cec20e80cedfe49361d1a30414ac5b", + "0xa2673bf9bc621e3892c3d7dd4f1a9497f369add8cbaa3472409f4f86bd21ac67cfac357604828adfee6ada1835365029", + "0xa042dff4bf0dfc33c178ba1b335e798e6308915128de91b12e5dbbab7c4ac8d60a01f6aea028c3a6d87b9b01e4e74c01", + "0x86339e8a75293e4b3ae66b5630d375736b6e6b6b05c5cda5e73fbf7b2f2bd34c18a1d6cefede08625ce3046e77905cb8", + "0xaf2ebe1b7d073d03e3d98bc61af83bf26f7a8c130fd607aa92b75db22d14d016481b8aa231e2c9757695f55b7224a27f", + "0xa00ee882c9685e978041fd74a2c465f06e2a42ffd3db659053519925be5b454d6f401e3c12c746e49d910e4c5c9c5e8c", + "0x978a781c0e4e264e0dad57e438f1097d447d891a1e2aa0d5928f79a9d5c3faae6f258bc94fdc530b7b2fa6a9932bb193", + "0xaa4b7ce2e0c2c9e9655bf21e3e5651c8503bce27483017b0bf476be743ba06db10228b3a4c721219c0779747f11ca282", + "0xb003d1c459dacbcf1a715551311e45d7dbca83a185a65748ac74d1800bbeaba37765d9f5a1a221805c571910b34ebca8", + "0x95b6e531b38648049f0d19de09b881baa1f7ea3b2130816b006ad5703901a05da57467d1a3d9d2e7c73fb3f2e409363c", + "0xa6cf9c06593432d8eba23a4f131bb7f72b9bd51ab6b4b772a749fe03ed72b5ced835a349c6d9920dba2a39669cb7c684", + "0xaa3d59f6e2e96fbb66195bc58c8704e139fa76cd15e4d61035470bd6e305db9f98bcbf61ac1b95e95b69ba330454c1b3", + "0xb57f97959c208361de6d7e86dff2b873068adb0f158066e646f42ae90e650079798f165b5cd713141cd3a2a90a961d9a", + "0xa76ee8ed9052f6a7a8c69774bb2597be182942f08115baba03bf8faaeaee526feba86120039fe8ca7b9354c3b6e0a8e6", + "0x95689d78c867724823f564627d22d25010f278674c6d2d0cdb10329169a47580818995d1d727ce46c38a1e47943ebb89", + "0xab676d2256c6288a88e044b3d9ffd43eb9d5aaee00e8fc60ac921395fb835044c71a26ca948e557fed770f52d711e057", + "0x96351c72785c32e5d004b6f4a1259fb8153d631f0c93fed172f18e8ba438fbc5585c1618deeabd0d6d0b82173c2e6170", + "0x93dd8d3db576418e22536eba45ab7f56967c6c97c64260d6cddf38fb19c88f2ec5cd0e0156f50e70855eee8a2b879ffd", + "0xad6ff16f40f6de3d7a737f8e6cebd8416920c4ff89dbdcd75eabab414af9a6087f83ceb9aff7680aa86bff98bd09c8cc", + "0x84de53b11671abc9c38710e19540c5c403817562aeb22a88404cdaff792c1180f717dbdfe8f54940c062c4d032897429", + "0x872231b9efa1cdd447b312099a5c164c560440a9441d904e70f5abfc3b2a0d16be9a01aca5e0a2599a61e19407587e3d", + "0x88f44ac27094a2aa14e9dc40b099ee6d68f97385950f303969d889ee93d4635e34dff9239103bdf66a4b7cbba3e7eb7a", + "0xa59afebadf0260e832f6f44468443562f53fbaf7bcb5e46e1462d3f328ac437ce56edbca617659ac9883f9e13261fad7", + "0xb1990e42743a88de4deeacfd55fafeab3bc380cb95de43ed623d021a4f2353530bcab9594389c1844b1c5ea6634c4555", + "0x85051e841149a10e83f56764e042182208591396d0ce78c762c4a413e6836906df67f38c69793e158d64fef111407ba3", + "0x9778172bbd9b1f2ec6bbdd61829d7b39a7df494a818e31c654bf7f6a30139899c4822c1bf418dd4f923243067759ce63", + "0x9355005b4878c87804fc966e7d24f3e4b02bed35b4a77369d01f25a3dcbff7621b08306b1ac85b76fe7b4a3eb5f839b1", + "0x8f9dc6a54fac052e236f8f0e1f571ac4b5308a43acbe4cc8183bce26262ddaf7994e41cf3034a4cbeca2c505a151e3b1", + "0x8cc59c17307111723fe313046a09e0e32ea0cce62c13814ab7c6408c142d6a0311d801be4af53fc9240523f12045f9ef", + "0x8e6057975ed40a1932e47dd3ac778f72ee2a868d8540271301b1aa6858de1a5450f596466494a3e0488be4fbeb41c840", + "0x812145efbd6559ae13325d56a15940ca4253b17e72a9728986b563bb5acc13ec86453796506ac1a8f12bd6f9e4a288c3", + "0x911da0a6d6489eb3dab2ec4a16e36127e8a291ae68a6c2c9de33e97f3a9b1f00da57a94e270a0de79ecc5ecb45d19e83", + "0xb72ea85973f4b2a7e6e71962b0502024e979a73c18a9111130e158541fa47bbaaf53940c8f846913a517dc69982ba9e1", + "0xa7a56ad1dbdc55f177a7ad1d0af78447dc2673291e34e8ab74b26e2e2e7d8c5fe5dc89e7ef60f04a9508847b5b3a8188", + "0xb52503f6e5411db5d1e70f5fb72ccd6463fa0f197b3e51ca79c7b5a8ab2e894f0030476ada72534fa4eb4e06c3880f90", + "0xb51c7957a3d18c4e38f6358f2237b3904618d58b1de5dec53387d25a63772e675a5b714ad35a38185409931157d4b529", + "0xb86b4266e719d29c043d7ec091547aa6f65bbf2d8d831d1515957c5c06513b72aa82113e9645ad38a7bc3f5383504fa6", + "0xb95b547357e6601667b0f5f61f261800a44c2879cf94e879def6a105b1ad2bbf1795c3b98a90d588388e81789bd02681", + "0xa58fd4c5ae4673fa350da6777e13313d5d37ed1dafeeb8f4f171549765b84c895875d9d3ae6a9741f3d51006ef81d962", + "0x9398dc348d078a604aadc154e6eef2c0be1a93bb93ba7fe8976edc2840a3a318941338cc4d5f743310e539d9b46613d2", + "0x902c9f0095014c4a2f0dccaaab543debba6f4cc82c345a10aaf4e72511725dbed7a34cd393a5f4e48a3e5142b7be84ed", + "0xa7c0447849bb44d04a0393a680f6cd390093484a79a147dd238f5d878030d1c26646d88211108e59fe08b58ad20c6fbd", + "0x80db045535d6e67a422519f5c89699e37098449d249698a7cc173a26ccd06f60238ae6cc7242eb780a340705c906790c", + "0x8e52b451a299f30124505de2e74d5341e1b5597bdd13301cc39b05536c96e4380e7f1b5c7ef076f5b3005a868657f17c", + "0x824499e89701036037571761e977654d2760b8ce21f184f2879fda55d3cda1e7a95306b8abacf1caa79d3cc075b9d27f", + "0x9049b956b77f8453d2070607610b79db795588c0cec12943a0f5fe76f358dea81e4f57a4692112afda0e2c05c142b26f", + "0x81911647d818a4b5f4990bfd4bc13bf7be7b0059afcf1b6839333e8569cdb0172fd2945410d88879349f677abaed5eb3", + "0xad4048f19b8194ed45b6317d9492b71a89a66928353072659f5ce6c816d8f21e69b9d1817d793effe49ca1874daa1096", + "0x8d22f7b2ddb31458661abd34b65819a374a1f68c01fc6c9887edeba8b80c65bceadb8f57a3eb686374004b836261ef67", + "0x92637280c259bc6842884db3d6e32602a62252811ae9b019b3c1df664e8809ffe86db88cfdeb8af9f46435c9ee790267", + "0xa2f416379e52e3f5edc21641ea73dc76c99f7e29ea75b487e18bd233856f4c0183429f378d2bfc6cd736d29d6cadfa49", + "0x882cb6b76dbdc188615dcf1a8439eba05ffca637dd25197508156e03c930b17b9fed2938506fdd7b77567cb488f96222", + "0xb68b621bb198a763fb0634eddb93ed4b5156e59b96c88ca2246fd1aea3e6b77ed651e112ac41b30cd361fadc011d385e", + "0xa3cb22f6b675a29b2d1f827cacd30df14d463c93c3502ef965166f20d046af7f9ab7b2586a9c64f4eae4fad2d808a164", + "0x8302d9ce4403f48ca217079762ce42cee8bc30168686bb8d3a945fbd5acd53b39f028dce757b825eb63af2d5ae41169d", + "0xb2eef1fbd1a176f1f4cd10f2988c7329abe4eb16c7405099fb92baa724ab397bc98734ef7d4b24c0f53dd90f57520d04", + "0xa1bbef0bd684a3f0364a66bde9b29326bac7aa3dde4caed67f14fb84fed3de45c55e406702f1495a3e2864d4ee975030", + "0x976acdb0efb73e3a3b65633197692dedc2adaed674291ae3df76b827fc866d214e9cac9ca46baefc4405ff13f953d936", + "0xb9fbf71cc7b6690f601f0b1c74a19b7d14254183a2daaafec7dc3830cba5ae173d854bbfebeca985d1d908abe5ef0cda", + "0x90591d7b483598c94e38969c4dbb92710a1a894bcf147807f1bcbd8aa3ac210b9f2be65519aa829f8e1ccdc83ad9b8cf", + "0xa30568577c91866b9c40f0719d46b7b3b2e0b4a95e56196ac80898a2d89cc67880e1229933f2cd28ee3286f8d03414d7", + "0x97589a88c3850556b359ec5e891f0937f922a751ac7c95949d3bbc7058c172c387611c0f4cb06351ef02e5178b3dd9e4", + "0x98e7bbe27a1711f4545df742f17e3233fbcc63659d7419e1ca633f104cb02a32c84f2fac23ca2b84145c2672f68077ab", + "0xa7ddb91636e4506d8b7e92aa9f4720491bb71a72dadc47c7f4410e15f93e43d07d2b371951a0e6a18d1bd087aa96a5c4", + "0xa7c006692227a06db40bceac3d5b1daae60b5692dd9b54772bedb5fea0bcc91cbcdb530cac31900ffc70c5b3ffadc969", + "0x8d3ec6032778420dfa8be52066ba0e623467df33e4e1901dbadd586c5d750f4ccde499b5197e26b9ea43931214060f69", + "0x8d9a8410518ea64f89df319bfd1fc97a0971cdb9ad9b11d1f8fe834042ea7f8dce4db56eeaf179ff8dda93b6db93e5ce", + "0xa3c533e9b3aa04df20b9ff635cb1154ce303e045278fcf3f10f609064a5445552a1f93989c52ce852fd0bbd6e2b6c22e", + "0x81934f3a7f8c1ae60ec6e4f212986bcc316118c760a74155d06ce0a8c00a9b9669ec4e143ca214e1b995e41271774fd9", + "0xab8e2d01a71192093ef8fafa7485e795567cc9db95a93fb7cc4cf63a391ef89af5e2bfad4b827fffe02b89271300407f", + "0x83064a1eaa937a84e392226f1a60b7cfad4efaa802f66de5df7498962f7b2649924f63cd9962d47906380b97b9fe80e1", + "0xb4f5e64a15c6672e4b55417ee5dc292dcf93d7ea99965a888b1cc4f5474a11e5b6520eacbcf066840b343f4ceeb6bf33", + "0xa63d278b842456ef15c278b37a6ea0f27c7b3ffffefca77c7a66d2ea06c33c4631eb242bbb064d730e70a8262a7b848a", + "0x83a41a83dbcdf0d22dc049de082296204e848c453c5ab1ba75aa4067984e053acf6f8b6909a2e1f0009ed051a828a73b", + "0x819485b036b7958508f15f3c19436da069cbe635b0318ebe8c014cf1ef9ab2df038c81161b7027475bcfa6fff8dd9faf", + "0xaa40e38172806e1e045e167f3d1677ef12d5dcdc89b43639a170f68054bd196c4fae34c675c1644d198907a03f76ba57", + "0x969bae484883a9ed1fbed53b26b3d4ee4b0e39a6c93ece5b3a49daa01444a1c25727dabe62518546f36b047b311b177c", + "0x80a9e73a65da99664988b238096a090d313a0ee8e4235bc102fa79bb337b51bb08c4507814eb5baec22103ec512eaab0", + "0x86604379aec5bddda6cbe3ef99c0ac3a3c285b0b1a15b50451c7242cd42ae6b6c8acb717dcca7917838432df93a28502", + "0xa23407ee02a495bed06aa7e15f94cfb05c83e6d6fba64456a9bbabfa76b2b68c5c47de00ba169e710681f6a29bb41a22", + "0x98cff5ecc73b366c6a01b34ac9066cb34f7eeaf4f38a5429bad2d07e84a237047e2a065c7e8a0a6581017dadb4695deb", + "0x8de9f68a938f441f3b7ab84bb1f473c5f9e5c9e139e42b7ccee1d254bd57d0e99c2ccda0f3198f1fc5737f6023dd204e", + "0xb0ce48d815c2768fb472a315cad86aa033d0e9ca506f146656e2941829e0acb735590b4fbc713c2d18d3676db0a954ac", + "0x82f485cdefd5642a6af58ac6817991c49fac9c10ace60f90b27f1788cc026c2fe8afc83cf499b3444118f9f0103598a8", + "0x82c24550ed512a0d53fc56f64cc36b553823ae8766d75d772dacf038c460f16f108f87a39ceef7c66389790f799dbab3", + "0x859ffcf1fe9166388316149b9acc35694c0ea534d43f09dae9b86f4aa00a23b27144dda6a352e74b9516e8c8d6fc809c", + "0xb8f7f353eec45da77fb27742405e5ad08d95ec0f5b6842025be9def3d9892f85eb5dd0921b41e6eff373618dba215bca", + "0x8ccca4436f9017e426229290f5cd05eac3f16571a4713141a7461acfe8ae99cd5a95bf5b6df129148693c533966145da", + "0xa2c67ecc19c0178b2994846fea4c34c327a5d786ac4b09d1d13549d5be5996d8a89021d63d65cb814923388f47cc3a03", + "0xaa0ff87d676b418ec08f5cbf577ac7e744d1d0e9ebd14615b550eb86931eafd2a36d4732cc5d6fab1713fd7ab2f6f7c0", + "0x8aef4730bb65e44efd6bb9441c0ae897363a2f3054867590a2c2ecf4f0224e578c7a67f10b40f8453d9f492ac15a9b2d", + "0x86a187e13d8fba5addcfdd5b0410cedd352016c930f913addd769ee09faa6be5ca3e4b1bdb417a965c643a99bd92be42", + "0xa0a4e9632a7a094b14b29b78cd9c894218cdf6783e61671e0203865dc2a835350f465fbaf86168f28af7c478ca17bc89", + "0xa8c7b02d8deff2cd657d8447689a9c5e2cd74ef57c1314ac4d69084ac24a7471954d9ff43fe0907d875dcb65fd0d3ce5", + "0x97ded38760aa7be6b6960b5b50e83b618fe413cbf2bcc1da64c05140bcc32f5e0e709cd05bf8007949953fac5716bad9", + "0xb0d293835a24d64c2ae48ce26e550b71a8c94a0883103757fb6b07e30747f1a871707d23389ba2b2065fa6bafe220095", + "0x8f9e291bf849feaa575592e28e3c8d4b7283f733d41827262367ea1c40f298c7bcc16505255a906b62bf15d9f1ba85fb", + "0x998f4e2d12708b4fd85a61597ca2eddd750f73c9e0c9b3cf0825d8f8e01f1628fd19797dcaed3b16dc50331fc6b8b821", + "0xb30d1f8c115d0e63bf48f595dd10908416774c78b3bbb3194192995154d80ea042d2e94d858de5f8aa0261b093c401fd", + "0xb5d9c75bb41f964cbff3f00e96d9f1480c91df8913f139f0d385d27a19f57a820f838eb728e46823cbff00e21c660996", + "0xa6edec90b5d25350e2f5f0518777634f9e661ec9d30674cf5b156c4801746d62517751d90074830ac0f4b09911c262f1", + "0x82f98da1264b6b75b8fbeb6a4d96d6a05b25c24db0d57ba3a38efe3a82d0d4e331b9fc4237d6494ccfe4727206457519", + "0xb89511843453cf4ecd24669572d6371b1e529c8e284300c43e0d5bb6b3aaf35aeb634b3cb5c0a2868f0d5e959c1d0772", + "0xa82bf065676583e5c1d3b81987aaae5542f522ba39538263a944bb33ea5b514c649344a96c0205a3b197a3f930fcda6c", + "0xa37b47ea527b7e06c460776aa662d9a49ff4149d3993f1a974b0dd165f7171770d189b0e2ea54fd5fccb6a14b116e68a", + "0xa1017677f97dda818274d47556d09d0e4ccacb23a252f82a6cfe78c630ad46fb9806307445a59fb61262182de3a2b29c", + "0xb01e9fcac239ba270e6877b79273ddd768bf8a51d2ed8a051b1c11e18eff3de5920e2fcbfbd26f06d381eddd3b1f1e1b", + "0x82fcd53d803b1c8e4ed76adc339b7f3a5962d37042b9683aabac7513ac68775d4a566a9460183926a6a95dbe7d551a1f", + "0xa763e78995d55cd21cdb7ef75d9642d6e1c72453945e346ab6690c20a4e1eeec61bb848ef830ae4b56182535e3c71d8f", + "0xb769f4db602251d4b0a1186782799bdcef66de33c110999a5775c50b349666ffd83d4c89714c4e376f2efe021a5cfdb2", + "0xa59cbd1b785efcfa6e83fc3b1d8cf638820bc0c119726b5368f3fba9dce8e3414204fb1f1a88f6c1ff52e87961252f97", + "0x95c8c458fd01aa23ecf120481a9c6332ebec2e8bb70a308d0576926a858457021c277958cf79017ddd86a56cacc2d7db", + "0x82eb41390800287ae56e77f2e87709de5b871c8bdb67c10a80fc65f3acb9f7c29e8fa43047436e8933f27449ea61d94d", + "0xb3ec25e3545eb83aed2a1f3558d1a31c7edde4be145ecc13b33802654b77dc049b4f0065069dd9047b051e52ab11dcdd", + "0xb78a0c715738f56f0dc459ab99e252e3b579b208142836b3c416b704ca1de640ca082f29ebbcee648c8c127df06f6b1e", + "0xa4083149432eaaf9520188ebf4607d09cf664acd1f471d4fb654476e77a9eaae2251424ffda78d09b6cb880df35c1219", + "0x8c52857d68d6e9672df3db2df2dbf46b516a21a0e8a18eec09a6ae13c1ef8f369d03233320dd1c2c0bbe00abfc1ea18b", + "0x8c856089488803066bff3f8d8e09afb9baf20cecc33c8823c1c0836c3d45498c3de37e87c016b705207f60d2b00f8609", + "0x831a3df39be959047b2aead06b4dcd3012d7b29417f642b83c9e8ce8de24a3dbbd29c6fdf55e2db3f7ea04636c94e403", + "0xaed84d009f66544addabe404bf6d65af7779ce140dc561ff0c86a4078557b96b2053b7b8a43432ffb18cd814f143b9da", + "0x93282e4d72b0aa85212a77b336007d8ba071eea17492da19860f1ad16c1ea8867ccc27ef5c37c74b052465cc11ea4f52", + "0xa7b78b8c8d057194e8d68767f1488363f77c77bddd56c3da2bc70b6354c7aa76247c86d51f7371aa38a4aa7f7e3c0bb7", + "0xb1c77283d01dcd1bde649b5b044eac26befc98ff57cbee379fb5b8e420134a88f2fc7f0bf04d15e1fbd45d29e7590fe6", + "0xa4aa8de70330a73b2c6458f20a1067eed4b3474829b36970a8df125d53bbdda4f4a2c60063b7cccb0c80fc155527652f", + "0x948a6c79ba1b8ad7e0bed2fae2f0481c4e41b4d9bbdd9b58164e28e9065700e83f210c8d5351d0212e0b0b68b345b3a5", + "0x86a48c31dcbbf7b082c92d28e1f613a2378a910677d7db3a349dc089e4a1e24b12eee8e8206777a3a8c64748840b7387", + "0x976adb1af21e0fc34148917cf43d933d7bfd3fd12ed6c37039dcd5a4520e3c6cf5868539ba5bf082326430deb8a4458d", + "0xb93e1a4476f2c51864bb4037e7145f0635eb2827ab91732b98d49b6c07f6ac443111aa1f1da76d1888665cb897c3834e", + "0x8afd46fb23bf869999fa19784b18a432a1f252d09506b8dbb756af900518d3f5f244989b3d7c823d9029218c655d3dc6", + "0x83f1e59e3abeed18cdc632921672673f1cb6e330326e11c4e600e13e0d5bc11bdc970ae12952e15103a706fe720bf4d6", + "0x90ce4cc660714b0b673d48010641c09c00fc92a2c596208f65c46073d7f349dd8e6e077ba7dcef9403084971c3295b76", + "0x8b09b0f431a7c796561ecf1549b85048564de428dac0474522e9558b6065fede231886bc108539c104ce88ebd9b5d1b0", + "0x85d6e742e2fb16a7b0ba0df64bc2c0dbff9549be691f46a6669bca05e89c884af16822b85faefefb604ec48c8705a309", + "0xa87989ee231e468a712c66513746fcf03c14f103aadca0eac28e9732487deb56d7532e407953ab87a4bf8961588ef7b0", + "0xb00da10efe1c29ee03c9d37d5918e391ae30e48304e294696b81b434f65cf8c8b95b9d1758c64c25e534d045ba28696f", + "0x91c0e1fb49afe46c7056400baa06dbb5f6e479db78ee37e2d76c1f4e88994357e257b83b78624c4ef6091a6c0eb8254d", + "0x883fb797c498297ccbf9411a3e727c3614af4eccde41619b773dc7f3259950835ee79453debf178e11dec4d3ada687a0", + "0xa14703347e44eb5059070b2759297fcfcfc60e6893c0373eea069388eba3950aa06f1c57cd2c30984a2d6f9e9c92c79e", + "0xafebc7585b304ceba9a769634adff35940e89cd32682c78002822aab25eec3edc29342b7f5a42a56a1fec67821172ad5", + "0xaea3ff3822d09dba1425084ca95fd359718d856f6c133c5fabe2b2eed8303b6e0ba0d8698b48b93136a673baac174fd9", + "0xaf2456a09aa777d9e67aa6c7c49a1845ea5cdda2e39f4c935c34a5f8280d69d4eec570446998cbbe31ede69a91e90b06", + "0x82cada19fed16b891ef3442bafd49e1f07c00c2f57b2492dd4ee36af2bd6fd877d6cb41188a4d6ce9ec8d48e8133d697", + "0x82a21034c832287f616619a37c122cee265cc34ae75e881fcaea4ea7f689f3c2bc8150bbf7dbcfd123522bfb7f7b1d68", + "0x86877217105f5d0ec3eeff0289fc2a70d505c9fdf7862e8159553ef60908fb1a27bdaf899381356a4ef4649072a9796c", + "0x82b196e49c6e861089a427c0b4671d464e9d15555ffb90954cd0d630d7ae02eb3d98ceb529d00719c2526cd96481355a", + "0xa29b41d0d43d26ce76d4358e0db2b77df11f56e389f3b084d8af70a636218bd3ac86b36a9fe46ec9058c26a490f887f7", + "0xa4311c4c20c4d7dd943765099c50f2fd423e203ccfe98ff00087d205467a7873762510cac5fdce7a308913ed07991ed7", + "0xb1f040fc5cc51550cb2c25cf1fd418ecdd961635a11f365515f0cb4ffb31da71f48128c233e9cc7c0cf3978d757ec84e", + "0xa9ebae46f86d3bd543c5f207ed0d1aed94b8375dc991161d7a271f01592912072e083e2daf30c146430894e37325a1b9", + "0x826418c8e17ad902b5fe88736323a47e0ca7a44bce4cbe27846ec8fe81de1e8942455dda6d30e192cdcc73e11df31256", + "0x85199db563427c5edcbac21f3d39fec2357be91fb571982ddcdc4646b446ad5ced84410de008cb47b3477ee0d532daf8", + "0xb7eed9cd400b2ca12bf1d9ae008214b8561fb09c8ad9ff959e626ffde00fee5ff2f5b6612e231f2a1a9b1646fcc575e3", + "0x8b40bf12501dcbac78f5a314941326bfcddf7907c83d8d887d0bb149207f85d80cd4dfbd7935439ea7b14ea39a3fded7", + "0x83e3041af302485399ba6cd5120e17af61043977083887e8d26b15feec4a6b11171ac5c06e6ad0971d4b58a81ff12af3", + "0x8f5b9a0eecc589dbf8c35a65d5e996a659277ef6ea509739c0cb7b3e2da9895e8c8012de662e5b23c5fa85d4a8f48904", + "0x835d71ed5e919d89d8e6455f234f3ff215462c4e3720c371ac8c75e83b19dfe3ae15a81547e4dc1138e5f5997f413cc9", + "0x8b7d2e4614716b1db18e9370176ea483e6abe8acdcc3dcdf5fb1f4d22ca55d652feebdccc171c6de38398d9f7bfdec7a", + "0x93eace72036fe57d019676a02acf3d224cf376f166658c1bf705db4f24295881d477d6fdd7916efcfceff8c7a063deda", + "0xb1ac460b3d516879a84bc886c54f020a9d799e7c49af3e4d7de5bf0d2793c852254c5d8fe5616147e6659512e5ccb012", + "0xacd0947a35cb167a48bcd9667620464b54ac0e78f9316b4aa92dcaab5422d7a732087e52e1c827faa847c6b2fe6e7766", + "0x94ac33d21c3d12ff762d32557860e911cd94d666609ddcc42161b9c16f28d24a526e8b10bb03137257a92cec25ae637d", + "0x832e02058b6b994eadd8702921486241f9a19e68ed1406dad545e000a491ae510f525ccf9d10a4bba91c68f2c53a0f58", + "0x9471035d14f78ff8f463b9901dd476b587bb07225c351161915c2e9c6114c3c78a501379ab6fb4eb03194c457cbd22bf", + "0xab64593e034c6241d357fcbc32d8ea5593445a5e7c24cac81ad12bd2ef01843d477a36dc1ba21dbe63b440750d72096a", + "0x9850f3b30045e927ad3ec4123a32ed2eb4c911f572b6abb79121873f91016f0d80268de8b12e2093a4904f6e6cab7642", + "0x987212c36b4722fe2e54fa30c52b1e54474439f9f35ca6ad33c5130cd305b8b54b532dd80ffd2c274105f20ce6d79f6e", + "0x8b4d0c6abcb239b5ed47bef63bc17efe558a27462c8208fa652b056e9eae9665787cd1aee34fbb55beb045c8bfdb882b", + "0xa9f3483c6fee2fe41312d89dd4355d5b2193ac413258993805c5cbbf0a59221f879386d3e7a28e73014f10e65dd503d9", + "0xa2225da3119b9b7c83d514b9f3aeb9a6d9e32d9cbf9309cbb971fd53c4b2c001d10d880a8ad8a7c281b21d85ceca0b7c", + "0xa050be52e54e676c151f7a54453bbb707232f849beab4f3bf504b4d620f59ed214409d7c2bd3000f3ff13184ccda1c35", + "0xadbccf681e15b3edb6455a68d292b0a1d0f5a4cb135613f5e6db9943f02181341d5755875db6ee474e19ace1c0634a28", + "0x8b6eff675632a6fad0111ec72aacc61c7387380eb87933fd1d098856387d418bd38e77d897e65d6fe35951d0627c550b", + "0xaabe2328ddf90989b15e409b91ef055cb02757d34987849ae6d60bef2c902bf8251ed21ab30acf39e500d1d511e90845", + "0x92ba4eb1f796bc3d8b03515f65c045b66e2734c2da3fc507fdd9d6b5d1e19ab3893726816a32141db7a31099ca817d96", + "0x8a98b3cf353138a1810beb60e946183803ef1d39ac4ea92f5a1e03060d35a4774a6e52b14ead54f6794d5f4022b8685c", + "0x909f8a5c13ec4a59b649ed3bee9f5d13b21d7f3e2636fd2bb3413c0646573fdf9243d63083356f12f5147545339fcd55", + "0x9359d914d1267633141328ed0790d81c695fea3ddd2d406c0df3d81d0c64931cf316fe4d92f4353c99ff63e2aefc4e34", + "0xb88302031681b54415fe8fbfa161c032ea345c6af63d2fb8ad97615103fd4d4281c5a9cae5b0794c4657b97571a81d3b", + "0x992c80192a519038082446b1fb947323005b275e25f2c14c33cc7269e0ec038581cc43705894f94bad62ae33a8b7f965", + "0xa78253e3e3eece124bef84a0a8807ce76573509f6861d0b6f70d0aa35a30a123a9da5e01e84969708c40b0669eb70aa6", + "0x8d5724de45270ca91c94792e8584e676547d7ac1ac816a6bb9982ee854eb5df071d20545cdfd3771cd40f90e5ba04c8e", + "0x825a6f586726c68d45f00ad0f5a4436523317939a47713f78fd4fe81cd74236fdac1b04ecd97c2d0267d6f4981d7beb1" + ], + "g2_monomial": [ + "0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", + "0xb5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2", + "0xb5337ba0ce5d37224290916e268e2060e5c14f3f9fc9e1ec3af5a958e7a0303122500ce18f1a4640bf66525bd10e763501fe986d86649d8d45143c08c3209db3411802c226e9fe9a55716ac4a0c14f9dcef9e70b2bb309553880dc5025eab3cc", + "0xb3c1dcdc1f62046c786f0b82242ef283e7ed8f5626f72542aa2c7a40f14d9094dd1ebdbd7457ffdcdac45fd7da7e16c51200b06d791e5e43e257e45efdf0bd5b06cd2333beca2a3a84354eb48662d83aef5ecf4e67658c851c10b13d8d87c874", + "0x954d91c7688983382609fca9e211e461f488a5971fd4e40d7e2892037268eacdfd495cfa0a7ed6eb0eb11ac3ae6f651716757e7526abe1e06c64649d80996fd3105c20c4c94bc2b22d97045356fe9d791f21ea6428ac48db6f9e68e30d875280", + "0x88a6b6bb26c51cf9812260795523973bb90ce80f6820b6c9048ab366f0fb96e48437a7f7cb62aedf64b11eb4dfefebb0147608793133d32003cb1f2dc47b13b5ff45f1bb1b2408ea45770a08dbfaec60961acb8119c47b139a13b8641e2c9487", + "0x85cd7be9728bd925d12f47fb04b32d9fad7cab88788b559f053e69ca18e463113ecc8bbb6dbfb024835f901b3a957d3108d6770fb26d4c8be0a9a619f6e3a4bf15cbfd48e61593490885f6cee30e4300c5f9cf5e1c08e60a2d5b023ee94fcad0", + "0x80477dba360f04399821a48ca388c0fa81102dd15687fea792ee8c1114e00d1bc4839ad37ac58900a118d863723acfbe08126ea883be87f50e4eabe3b5e72f5d9e041db8d9b186409fd4df4a7dde38c0e0a3b1ae29b098e5697e7f110b6b27e4", + "0xb7a6aec08715a9f8672a2b8c367e407be37e59514ac19dd4f0942a68007bba3923df22da48702c63c0d6b3efd3c2d04e0fe042d8b5a54d562f9f33afc4865dcbcc16e99029e25925580e87920c399e710d438ac1ce3a6dc9b0d76c064a01f6f7", + "0xac1b001edcea02c8258aeffbf9203114c1c874ad88dae1184fadd7d94cd09053649efd0ca413400e6e9b5fa4eac33261000af88b6bd0d2abf877a4f0355d2fb4d6007adb181695201c5432e50b850b51b3969f893bddf82126c5a71b042b7686", + "0x90043fda4de53fb364fab2c04be5296c215599105ecff0c12e4917c549257125775c29f2507124d15f56e30447f367db0596c33237242c02d83dfd058735f1e3c1ff99069af55773b6d51d32a68bf75763f59ec4ee7267932ae426522b8aaab6", + "0xa8660ce853e9dc08271bf882e29cd53397d63b739584dda5263da4c7cc1878d0cf6f3e403557885f557e184700575fee016ee8542dec22c97befe1d10f414d22e84560741cdb3e74c30dda9b42eeaaf53e27822de2ee06e24e912bf764a9a533", + "0x8fe3921a96d0d065e8aa8fce9aa42c8e1461ca0470688c137be89396dd05103606dab6cdd2a4591efd6addf72026c12e065da7be276dee27a7e30afa2bd81c18f1516e7f068f324d0bad9570b95f6bd02c727cd2343e26db0887c3e4e26dceda", + "0x8ae1ad97dcb9c192c9a3933541b40447d1dc4eebf380151440bbaae1e120cc5cdf1bcea55180b128d8e180e3af623815191d063cc0d7a47d55fb7687b9d87040bf7bc1a7546b07c61db5ccf1841372d7c2fe4a5431ffff829f3c2eb590b0b710", + "0x8c2fa96870a88150f7876c931e2d3cc2adeaaaf5c73ef5fa1cf9dfa0991ae4819f9321af7e916e5057d87338e630a2f21242c29d76963cf26035b548d2a63d8ad7bd6efefa01c1df502cbdfdfe0334fb21ceb9f686887440f713bf17a89b8081", + "0xb9aa98e2f02bb616e22ee5dd74c7d1049321ac9214d093a738159850a1dbcc7138cb8d26ce09d8296368fd5b291d74fa17ac7cc1b80840fdd4ee35e111501e3fa8485b508baecda7c1ab7bd703872b7d64a2a40b3210b6a70e8a6ffe0e5127e3", + "0x9292db67f8771cdc86854a3f614a73805bf3012b48f1541e704ea4015d2b6b9c9aaed36419769c87c49f9e3165f03edb159c23b3a49c4390951f78e1d9b0ad997129b17cdb57ea1a6638794c0cca7d239f229e589c5ae4f9fe6979f7f8cba1d7", + "0x91cd9e86550f230d128664f7312591fee6a84c34f5fc7aed557bcf986a409a6de722c4330453a305f06911d2728626e611acfdf81284f77f60a3a1595053a9479964fd713117e27c0222cc679674b03bc8001501aaf9b506196c56de29429b46", + "0xa9516b73f605cc31b89c68b7675dc451e6364595243d235339437f556cf22d745d4250c1376182273be2d99e02c10eee047410a43eff634d051aeb784e76cb3605d8e079b9eb6ad1957dfdf77e1cd32ce4a573c9dfcc207ca65af6eb187f6c3d", + "0xa9667271f7d191935cc8ad59ef3ec50229945faea85bfdfb0d582090f524436b348aaa0183b16a6231c00332fdac2826125b8c857a2ed9ec66821cfe02b3a2279be2412441bc2e369b255eb98614e4be8490799c4df22f18d47d24ec70bba5f7", + "0xa4371144d2aa44d70d3cb9789096d3aa411149a6f800cb46f506461ee8363c8724667974252f28aea61b6030c05930ac039c1ee64bb4bd56532a685cae182bf2ab935eee34718cffcb46cae214c77aaca11dbb1320faf23c47247db1da04d8dc", + "0x89a7eb441892260b7e81168c386899cd84ffc4a2c5cad2eae0d1ab9e8b5524662e6f660fe3f8bfe4c92f60b060811bc605b14c5631d16709266886d7885a5eb5930097127ec6fb2ebbaf2df65909cf48f253b3d5e22ae48d3e9a2fd2b01f447e", + "0x9648c42ca97665b5eccb49580d8532df05eb5a68db07f391a2340769b55119eaf4c52fe4f650c09250fa78a76c3a1e271799b8333cc2628e3d4b4a6a3e03da1f771ecf6516dd63236574a7864ff07e319a6f11f153406280d63af9e2b5713283", + "0x9663bf6dd446ea7a90658ee458578d4196dc0b175ef7fcfa75f44d41670850774c2e46c5a6be132a2c072a3c0180a24f0305d1acac49d2d79878e5cda80c57feda3d01a6af12e78b5874e2a4b3717f11c97503b41a4474e2e95b179113726199", + "0xb212aeb4814e0915b432711b317923ed2b09e076aaf558c3ae8ef83f9e15a83f9ea3f47805b2750ab9e8106cb4dc6ad003522c84b03dc02829978a097899c773f6fb31f7fe6b8f2d836d96580f216fec20158f1590c3e0d7850622e15194db05", + "0x925f005059bf07e9ceccbe66c711b048e236ade775720d0fe479aebe6e23e8af281225ad18e62458dc1b03b42ad4ca290d4aa176260604a7aad0d9791337006fbdebe23746f8060d42876f45e4c83c3643931392fde1cd13ff8bddf8111ef974", + "0x9553edb22b4330c568e156a59ef03b26f5c326424f830fe3e8c0b602f08c124730ffc40bc745bec1a22417adb22a1a960243a10565c2be3066bfdb841d1cd14c624cd06e0008f4beb83f972ce6182a303bee3fcbcabc6cfe48ec5ae4b7941bfc", + "0x935f5a404f0a78bdcce709899eda0631169b366a669e9b58eacbbd86d7b5016d044b8dfc59ce7ed8de743ae16c2343b50e2f925e88ba6319e33c3fc76b314043abad7813677b4615c8a97eb83cc79de4fedf6ccbcfa4d4cbf759a5a84e4d9742", + "0xa5b014ab936eb4be113204490e8b61cd38d71da0dec7215125bcd131bf3ab22d0a32ce645bca93e7b3637cf0c2db3d6601a0ddd330dc46f9fae82abe864ffc12d656c88eb50c20782e5bb6f75d18760666f43943abb644b881639083e122f557", + "0x935b7298ae52862fa22bf03bfc1795b34c70b181679ae27de08a9f5b4b884f824ef1b276b7600efa0d2f1d79e4a470d51692fd565c5cf8343dd80e5d3336968fc21c09ba9348590f6206d4424eb229e767547daefa98bc3aa9f421158dee3f2a", + "0x9830f92446e708a8f6b091cc3c38b653505414f8b6507504010a96ffda3bcf763d5331eb749301e2a1437f00e2415efb01b799ad4c03f4b02de077569626255ac1165f96ea408915d4cf7955047620da573e5c439671d1fa5c833fb11de7afe6", + "0x840dcc44f673fff3e387af2bb41e89640f2a70bcd2b92544876daa92143f67c7512faf5f90a04b7191de01f3e2b1bde00622a20dc62ca23bbbfaa6ad220613deff43908382642d4d6a86999f662efd64b1df448b68c847cfa87630a3ffd2ec76", + "0x92950c895ed54f7f876b2fda17ecc9c41b7accfbdd42c210cc5b475e0737a7279f558148531b5c916e310604a1de25a80940c94fe5389ae5d6a5e9c371be67bceea1877f5401725a6595bcf77ece60905151b6dfcb68b75ed2e708c73632f4fd", + "0x8010246bf8e94c25fd029b346b5fbadb404ef6f44a58fd9dd75acf62433d8cc6db66974f139a76e0c26dddc1f329a88214dbb63276516cf325c7869e855d07e0852d622c332ac55609ba1ec9258c45746a2aeb1af0800141ee011da80af175d4", + "0xb0f1bad257ebd187bdc3f37b23f33c6a5d6a8e1f2de586080d6ada19087b0e2bf23b79c1b6da1ee82271323f5bdf3e1b018586b54a5b92ab6a1a16bb3315190a3584a05e6c37d5ca1e05d702b9869e27f513472bcdd00f4d0502a107773097da", + "0x9636d24f1ede773ce919f309448dd7ce023f424afd6b4b69cb98c2a988d849a283646dc3e469879daa1b1edae91ae41f009887518e7eb5578f88469321117303cd3ac2d7aee4d9cb5f82ab9ae3458e796dfe7c24284b05815acfcaa270ff22e2", + "0xb373feb5d7012fd60578d7d00834c5c81df2a23d42794fed91aa9535a4771fde0341c4da882261785e0caca40bf83405143085e7f17e55b64f6c5c809680c20b050409bf3702c574769127c854d27388b144b05624a0e24a1cbcc4d08467005b", + "0xb15680648949ce69f82526e9b67d9b55ce5c537dc6ab7f3089091a9a19a6b90df7656794f6edc87fb387d21573ffc847062623685931c2790a508cbc8c6b231dd2c34f4d37d4706237b1407673605a604bcf6a50cc0b1a2db20485e22b02c17e", + "0x8817e46672d40c8f748081567b038a3165f87994788ec77ee8daea8587f5540df3422f9e120e94339be67f186f50952504cb44f61e30a5241f1827e501b2de53c4c64473bcc79ab887dd277f282fbfe47997a930dd140ac08b03efac88d81075", + "0xa6e4ef6c1d1098f95aae119905f87eb49b909d17f9c41bcfe51127aa25fee20782ea884a7fdf7d5e9c245b5a5b32230b07e0dbf7c6743bf52ee20e2acc0b269422bd6cf3c07115df4aa85b11b2c16630a07c974492d9cdd0ec325a3fabd95044", + "0x8634aa7c3d00e7f17150009698ce440d8e1b0f13042b624a722ace68ead870c3d2212fbee549a2c190e384d7d6ac37ce14ab962c299ea1218ef1b1489c98906c91323b94c587f1d205a6edd5e9d05b42d591c26494a6f6a029a2aadb5f8b6f67", + "0x821a58092900bdb73decf48e13e7a5012a3f88b06288a97b855ef51306406e7d867d613d9ec738ebacfa6db344b677d21509d93f3b55c2ebf3a2f2a6356f875150554c6fff52e62e3e46f7859be971bf7dd9d5b3e1d799749c8a97c2e04325df", + "0x8dba356577a3a388f782e90edb1a7f3619759f4de314ad5d95c7cc6e197211446819c4955f99c5fc67f79450d2934e3c09adefc91b724887e005c5190362245eec48ce117d0a94d6fa6db12eda4ba8dde608fbbd0051f54dcf3bb057adfb2493", + "0xa32a690dc95c23ed9fb46443d9b7d4c2e27053a7fcc216d2b0020a8cf279729c46114d2cda5772fd60a97016a07d6c5a0a7eb085a18307d34194596f5b541cdf01b2ceb31d62d6b55515acfd2b9eec92b27d082fbc4dc59fc63b551eccdb8468", + "0xa040f7f4be67eaf0a1d658a3175d65df21a7dbde99bfa893469b9b43b9d150fc2e333148b1cb88cfd0447d88fa1a501d126987e9fdccb2852ecf1ba907c2ca3d6f97b055e354a9789854a64ecc8c2e928382cf09dda9abde42bbdf92280cdd96", + "0x864baff97fa60164f91f334e0c9be00a152a416556b462f96d7c43b59fe1ebaff42f0471d0bf264976f8aa6431176eb905bd875024cf4f76c13a70bede51dc3e47e10b9d5652d30d2663b3af3f08d5d11b9709a0321aba371d2ef13174dcfcaf", + "0x95a46f32c994133ecc22db49bad2c36a281d6b574c83cfee6680b8c8100466ca034b815cfaedfbf54f4e75188e661df901abd089524e1e0eb0bf48d48caa9dd97482d2e8c1253e7e8ac250a32fd066d5b5cb08a8641bdd64ecfa48289dca83a3", + "0xa2cce2be4d12144138cb91066e0cd0542c80b478bf467867ebef9ddaf3bd64e918294043500bf5a9f45ee089a8d6ace917108d9ce9e4f41e7e860cbce19ac52e791db3b6dde1c4b0367377b581f999f340e1d6814d724edc94cb07f9c4730774", + "0xb145f203eee1ac0a1a1731113ffa7a8b0b694ef2312dabc4d431660f5e0645ef5838e3e624cfe1228cfa248d48b5760501f93e6ab13d3159fc241427116c4b90359599a4cb0a86d0bb9190aa7fabff482c812db966fd2ce0a1b48cb8ac8b3bca", + "0xadabe5d215c608696e03861cbd5f7401869c756b3a5aadc55f41745ad9478145d44393fec8bb6dfc4ad9236dc62b9ada0f7ca57fe2bae1b71565dbf9536d33a68b8e2090b233422313cc96afc7f1f7e0907dc7787806671541d6de8ce47c4cd0", + "0xae7845fa6b06db53201c1080e01e629781817f421f28956589c6df3091ec33754f8a4bd4647a6bb1c141ac22731e3c1014865d13f3ed538dcb0f7b7576435133d9d03be655f8fbb4c9f7d83e06d1210aedd45128c2b0c9bab45a9ddde1c862a5", + "0x9159eaa826a24adfa7adf6e8d2832120ebb6eccbeb3d0459ffdc338548813a2d239d22b26451fda98cc0c204d8e1ac69150b5498e0be3045300e789bcb4e210d5cd431da4bdd915a21f407ea296c20c96608ded0b70d07188e96e6c1a7b9b86b", + "0xa9fc6281e2d54b46458ef564ffaed6944bff71e389d0acc11fa35d3fcd8e10c1066e0dde5b9b6516f691bb478e81c6b20865281104dcb640e29dc116daae2e884f1fe6730d639dbe0e19a532be4fb337bf52ae8408446deb393d224eee7cfa50", + "0x84291a42f991bfb36358eedead3699d9176a38f6f63757742fdbb7f631f2c70178b1aedef4912fed7b6cf27e88ddc7eb0e2a6aa4b999f3eb4b662b93f386c8d78e9ac9929e21f4c5e63b12991fcde93aa64a735b75b535e730ff8dd2abb16e04", + "0xa1b7fcacae181495d91765dfddf26581e8e39421579c9cbd0dd27a40ea4c54af3444a36bf85a11dda2114246eaddbdd619397424bb1eb41b5a15004b902a590ede5742cd850cf312555be24d2df8becf48f5afba5a8cd087cb7be0a521728386", + "0x92feaaf540dbd84719a4889a87cdd125b7e995a6782911931fef26da9afcfbe6f86aaf5328fe1f77631491ce6239c5470f44c7791506c6ef1626803a5794e76d2be0af92f7052c29ac6264b7b9b51f267ad820afc6f881460521428496c6a5f1", + "0xa525c925bfae1b89320a5054acc1fa11820f73d0cf28d273092b305467b2831fab53b6daf75fb926f332782d50e2522a19edcd85be5eb72f1497193c952d8cd0bcc5d43b39363b206eae4cb1e61668bde28a3fb2fc1e0d3d113f6dfadb799717", + "0x98752bb6f5a44213f40eda6aa4ff124057c1b13b6529ab42fe575b9afa66e59b9c0ed563fb20dff62130c436c3e905ee17dd8433ba02c445b1d67182ab6504a90bbe12c26a754bbf734665c622f76c62fe2e11dd43ce04fd2b91a8463679058b", + "0xa9aa9a84729f7c44219ff9e00e651e50ddea3735ef2a73fdf8ed8cd271961d8ed7af5cd724b713a89a097a3fe65a3c0202f69458a8b4c157c62a85668b12fc0d3957774bc9b35f86c184dd03bfefd5c325da717d74192cc9751c2073fe9d170e", + "0xb221c1fd335a4362eff504cd95145f122bf93ea02ae162a3fb39c75583fc13a932d26050e164da97cff3e91f9a7f6ff80302c19dd1916f24acf6b93b62f36e9665a8785413b0c7d930c7f1668549910f849bca319b00e59dd01e5dec8d2edacc", + "0xa71e2b1e0b16d754b848f05eda90f67bedab37709550171551050c94efba0bfc282f72aeaaa1f0330041461f5e6aa4d11537237e955e1609a469d38ed17f5c2a35a1752f546db89bfeff9eab78ec944266f1cb94c1db3334ab48df716ce408ef", + "0xb990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c463e3f1eead58b20b09efcefa566fbfdfe1c6e48d32367936142d0a734143e5e63cdf86be7457723535b787a9cfcfa32fe1d61ad5a2617220", + "0x8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db", + "0xa92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c", + "0x92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10" + ] +} \ No newline at end of file diff --git a/pylint.ini b/pylint.ini new file mode 100644 index 0000000000..a8242683a3 --- /dev/null +++ b/pylint.ini @@ -0,0 +1,3 @@ +[MESSAGES CONTROL] +disable = all +enable = unused-argument \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..7c829bab77 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +[build-system] +requires = [ + "marko==2.1.4", + "ruamel.yaml==0.18.14", + "setuptools==80.9.0", + "wheel==0.45.1", +] + +[project] +name = "eth2spec" +dynamic = ["version"] +authors = [{ name = "ethereum" }] +description = "Ethereum consensus layer specifications package" +readme = { file = "README.md", content-type = "text/markdown" } +requires-python = ">=3.10,<4.0" +dependencies = [ + "curdleproofs==0.1.2", + "eth-typing==5.2.1", + "eth-utils==5.3.0", + "frozendict==2.4.6", + "lru-dict==1.3.0", + "marko==2.1.4", + "milagro_bls_binding==1.9.0", + "py_arkworks_bls12381==0.3.8", + "py_ecc==8.0.0", + "pycryptodome==3.23.0", + "remerkleable==0.1.28", + "ruamel.yaml==0.18.14", + "setuptools==80.9.0", + "trie==3.1.0", +] + +[project.optional-dependencies] +test = [ + "deepdiff==8.5.0", + "pytest-cov==6.2.1", + "pytest-xdist==3.7.0", + "pytest==8.4.1", +] +lint = [ + "codespell==2.4.1", + "mdformat-gfm-alerts==2.0.0", + "mdformat-gfm==0.4.1", + "mdformat-ruff==0.1.3", + "mdformat-toc==0.3.0", + "mdformat==0.7.22", + "mypy==1.16.1", + "ruff==0.12.0", +] +generator = [ + "filelock==3.18.0", + "minizinc==0.10.0", + "pathos==0.3.4", + "pytest==8.4.1", + "python-snappy==0.7.3", + "rich==14.0.0", + "tqdm==4.67.1", +] +docs = [ + "mdx-truly-sane-lists==1.3", + "mkdocs-awesome-pages-plugin==2.10.1", + "mkdocs-material==9.6.14", + "mkdocs==1.6.1", +] + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +select = [ + "F", # https://docs.astral.sh/ruff/rules/#pyflakes-f + "I", # https://docs.astral.sh/ruff/rules/#isort-i + "PL", # https://docs.astral.sh/ruff/rules/#pylint-pl + "UP", # https://docs.astral.sh/ruff/rules/#pyupgrade-up +] +ignore = [ + "PLR0911", # https://docs.astral.sh/ruff/rules/too-many-return-statements/ + "PLR0912", # https://docs.astral.sh/ruff/rules/too-many-branches/ + "PLR0913", # https://docs.astral.sh/ruff/rules/too-many-arguments/ + "PLR0915", # https://docs.astral.sh/ruff/rules/too-many-statements/ + "PLR1714", # https://docs.astral.sh/ruff/rules/repeated-equality-comparison/ + "PLR2004", # https://docs.astral.sh/ruff/rules/magic-value-comparison/ + "PLW0128", # https://docs.astral.sh/ruff/rules/redeclared-assigned-name/ + "PLW0603", # https://docs.astral.sh/ruff/rules/global-statement/ + "PLW2901", # https://docs.astral.sh/ruff/rules/redefined-loop-name/ +] + +[tool.ruff.lint.isort] +combine-as-imports = true +known-first-party = ["eth2spec"] +order-by-type = false diff --git a/tests/core/pyspec/eth2spec/test/custody_game/__init__.py b/pysetup/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/custody_game/__init__.py rename to pysetup/__init__.py diff --git a/pysetup/constants.py b/pysetup/constants.py new file mode 100644 index 0000000000..05ec3fd1d5 --- /dev/null +++ b/pysetup/constants.py @@ -0,0 +1,33 @@ +# Definitions in context.py +PHASE0 = "phase0" +ALTAIR = "altair" +BELLATRIX = "bellatrix" +CAPELLA = "capella" +DENEB = "deneb" +ELECTRA = "electra" +FULU = "fulu" +EIP6800 = "eip6800" +EIP7441 = "eip7441" +EIP7732 = "eip7732" +EIP7805 = "eip7805" + + +# The helper functions that are used when defining constants +CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = """ +def ceillog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"ceillog2 accepts only positive values, x={x}") + return uint64((x - 1).bit_length()) + + +def floorlog2(x: int) -> uint64: + if x < 1: + raise ValueError(f"floorlog2 accepts only positive values, x={x}") + return uint64(x.bit_length() - 1) +""" + + +OPTIMIZED_BLS_AGGREGATE_PUBKEYS = """ +def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: + return bls.AggregatePKs(pubkeys) +""" diff --git a/pysetup/helpers.py b/pysetup/helpers.py new file mode 100644 index 0000000000..084baca5b1 --- /dev/null +++ b/pysetup/helpers.py @@ -0,0 +1,396 @@ +import re +import textwrap +from functools import reduce +from typing import TypeVar + +from .constants import CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS +from .md_doc_paths import PREVIOUS_FORK_OF +from .spec_builders import spec_builders +from .typing import ( + ProtocolDefinition, + SpecObject, + VariableDefinition, +) + + +def collect_prev_forks(fork: str) -> list[str]: + forks = [fork] + while True: + fork = PREVIOUS_FORK_OF[fork] + if fork is None: + return forks + forks.append(fork) + + +def requires_mypy_type_ignore(value: str) -> bool: + return value.startswith("ByteVector") or ( + value.startswith("Vector") and any(k in value for k in ["ceillog2", "floorlog2"]) + ) + + +def make_function_abstract(protocol_def: ProtocolDefinition, key: str): + function = protocol_def.functions[key].split('"""') + protocol_def.functions[key] = function[0] + "..." + + +def objects_to_spec( + preset_name: str, spec_object: SpecObject, fork: str, ordered_class_objects: dict[str, str] +) -> str: + """ + Given all the objects that constitute a spec, combine them into a single pyfile. + """ + + def gen_new_type_definitions(custom_types: dict[str, str]) -> str: + return "\n\n".join( + [ + ( + f"class {key}({value}):\n pass\n" + if not requires_mypy_type_ignore(value) + else f"class {key}({value}): # type: ignore\n pass\n" + ) + for key, value in custom_types.items() + ] + ) + + new_type_definitions = gen_new_type_definitions(spec_object.custom_types) + preset_dep_new_type_definitions = gen_new_type_definitions(spec_object.preset_dep_custom_types) + + # Collect builders with the reversed previous forks + # e.g. `[bellatrix, altair, phase0]` -> `[phase0, altair, bellatrix]` + builders = [spec_builders[fork] for fork in collect_prev_forks(fork)[::-1]] + + def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str: + abstract_functions = ["verify_and_notify_new_payload"] + for key in protocol_def.functions.keys(): + if key in abstract_functions: + make_function_abstract(protocol_def, key) + + protocol = f"class {protocol_name}(Protocol):" + for fn_source in protocol_def.functions.values(): + fn_source = fn_source.replace("self: " + protocol_name, "self") + protocol += "\n\n" + textwrap.indent(fn_source, " ") + return protocol + + protocols_spec = "\n\n\n".join(format_protocol(k, v) for k, v in spec_object.protocols.items()) + for k in list(spec_object.functions): + if k in [ + "ceillog2", + "floorlog2", + "compute_merkle_proof", + ]: + del spec_object.functions[k] + + functions = reduce( + lambda fns, builder: builder.implement_optimizations(fns), builders, spec_object.functions + ) + functions_spec = "\n\n\n".join(functions.values()) + ordered_class_objects_spec = "\n\n\n".join(ordered_class_objects.values()) + + # Access global dict of config vars for runtime configurables + # Ignore variable between quotes and doubles quotes + for name in spec_object.config_vars.keys(): + functions_spec = re.sub(rf"(? str: + if isinstance(vardef, list): + # A special case for list of records. + indent = " " * 4 + lines = [f"{name}=("] + for d in vardef: + line = indent * 2 + "frozendict({\n" + for k, v in d.items(): + line += indent * 3 + f'"{k}": {v},\n' + line += indent * 2 + "})," + lines.append(line) + lines.append(indent + "),") + return "\n".join(lines) + elif vardef.type_name is None: + out = f"{name}={vardef.value}," + else: + out = f"{name}={vardef.type_name}({vardef.value})," + if vardef.comment is not None: + out += f" # {vardef.comment}" + return out + + def format_config_var_param(value): + if isinstance(value, list): + # A special case for list of records. + return "tuple[frozendict[str, Any], ...]" + elif isinstance(value, VariableDefinition): + return value.type_name if value.type_name is not None else "int" + + config_spec = "class Configuration(NamedTuple):\n" + config_spec += " PRESET_BASE: str\n" + config_spec += "\n".join( + f" {k}: {format_config_var_param(v)}" for k, v in spec_object.config_vars.items() + ) + config_spec += "\n\n\nconfig = Configuration(\n" + config_spec += f' PRESET_BASE="{preset_name}",\n' + config_spec += "\n".join( + " " + format_config_var(k, v) for k, v in spec_object.config_vars.items() + ) + config_spec += "\n)\n" + + def format_constant(name: str, vardef: VariableDefinition) -> str: + if vardef.type_name is None: + if vardef.type_hint is None: + out = f"{name} = {vardef.value}" + else: + out = f"{name}: {vardef.type_hint} = {vardef.value}" + else: + out = f"{name} = {vardef.type_name}({vardef.value})" + if vardef.comment is not None: + out += f" # {vardef.comment}" + return out + + # Merge all constant objects + hardcoded_ssz_dep_constants = reduce( + lambda obj, builder: {**obj, **builder.hardcoded_ssz_dep_constants()}, builders, {} + ) + hardcoded_func_dep_presets = reduce( + lambda obj, builder: {**obj, **builder.hardcoded_func_dep_presets(spec_object)}, + builders, + {}, + ) + # Concatenate all strings + imports = reduce( + lambda txt, builder: (txt + "\n\n" + builder.imports(preset_name)).strip("\n"), builders, "" + ) + classes = reduce( + lambda txt, builder: (txt + "\n\n" + builder.classes()).strip("\n"), builders, "" + ) + preparations = reduce( + lambda txt, builder: (txt + "\n\n" + builder.preparations()).strip("\n"), builders, "" + ) + sundry_functions = reduce( + lambda txt, builder: (txt + "\n\n" + builder.sundry_functions()).strip("\n"), builders, "" + ) + # Keep engine from the most recent fork + execution_engine_cls = reduce( + lambda txt, builder: builder.execution_engine_cls() or txt, builders, "" + ) + + # Remove deprecated constants + deprecate_constants = reduce( + lambda obj, builder: obj.union(builder.deprecate_constants()), builders, set() + ) + # constant_vars = {k: v for k, v in spec_object.constant_vars.items() if k not in deprecate_constants} + filtered_ssz_dep_constants = { + k: v for k, v in hardcoded_ssz_dep_constants.items() if k not in deprecate_constants + } + # Remove deprecated presets + deprecate_presets = reduce( + lambda obj, builder: obj.union(builder.deprecate_presets()), builders, set() + ) + # preset_vars = {k: v for k, v in spec_object.constant_vars.items() if k not in deprecate_constants} + filtered_hardcoded_func_dep_presets = { + k: v for k, v in hardcoded_func_dep_presets.items() if k not in deprecate_presets + } + + constant_vars_spec = "# Constant vars\n" + "\n".join( + format_constant(k, v) for k, v in spec_object.constant_vars.items() + ) + preset_dep_constant_vars_spec = "# Preset computed constants\n" + "\n".join( + format_constant(k, v) for k, v in spec_object.preset_dep_constant_vars.items() + ) + preset_vars_spec = "# Preset vars\n" + "\n".join( + format_constant(k, v) for k, v in spec_object.preset_vars.items() + ) + ssz_dep_constants = "\n".join( + map(lambda x: f"{x} = {hardcoded_ssz_dep_constants[x]}", hardcoded_ssz_dep_constants) + ) + ssz_dep_constants_verification = "\n".join( + map( + lambda x: f"assert {x} == {spec_object.ssz_dep_constants[x]}", + filtered_ssz_dep_constants, + ) + ) + func_dep_presets_verification = "\n".join( + map( + lambda x: f"assert {x} == {spec_object.func_dep_presets[x]} # noqa: E501", + filtered_hardcoded_func_dep_presets, + ) + ) + spec_strs = [ + imports, + preparations, + f"fork = '{fork}'\n", + # The helper functions that some SSZ containers require. Need to be defined before `custom_type_dep_constants` + CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS, + # The constants that some SSZ containers require. Need to be defined before `constants_spec` + ssz_dep_constants, + new_type_definitions, + constant_vars_spec, + # The presets that some SSZ types require. Need to be defined before `preset_dep_new_type_definitions` + preset_vars_spec, + preset_dep_constant_vars_spec, + preset_dep_new_type_definitions, + config_spec, + # Custom classes which are not required to be SSZ containers. + classes, + ordered_class_objects_spec, + protocols_spec, + functions_spec, + sundry_functions, + execution_engine_cls, + # Since some constants are hardcoded in setup.py, the following assertions verify that the hardcoded constants are + # as same as the spec definition. + ssz_dep_constants_verification, + func_dep_presets_verification, + ] + return "\n\n\n".join([str.strip("\n") for str in spec_strs if str]) + "\n" + + +def combine_protocols( + old_protocols: dict[str, ProtocolDefinition], new_protocols: dict[str, ProtocolDefinition] +) -> dict[str, ProtocolDefinition]: + for key, value in new_protocols.items(): + if key not in old_protocols: + old_protocols[key] = value + else: + functions = combine_dicts(old_protocols[key].functions, value.functions) + old_protocols[key] = ProtocolDefinition(functions=functions) + return old_protocols + + +T = TypeVar("T") + + +def combine_dicts(old_dict: dict[str, T], new_dict: dict[str, T]) -> dict[str, T]: + return {**old_dict, **new_dict} + + +ignored_dependencies = [ + "bit", + "Bitlist", + "Bitvector", + "BLSPubkey", + "BLSSignature", + "boolean", + "byte", + "ByteList", + "bytes", + "Bytes1", + "Bytes20", + "Bytes31", + "Bytes32", + "Bytes4", + "Bytes48", + "Bytes8", + "Bytes96", + "ByteVector", + "ceillog2", + "Container", + "dict", + "Dict", + "field", + "floorlog2", + "List", + "Optional", + "Sequence", + "Set", + "Tuple", + "uint128", + "uint16", + "uint256", + "uint32", + "uint64", + "uint8", + "Vector", +] + + +def dependency_order_class_objects(objects: dict[str, str], custom_types: dict[str, str]) -> None: + """ + Determines which SSZ Object is dependent on which other and orders them appropriately + """ + items = list(objects.items()) + for key, value in items: + dependencies = [] + for line in value.split("\n"): + if not re.match(r"\s+\w+: .+", line): + continue # skip whitespace etc. + line = line[line.index(":") + 1 :] # strip of field name + if "#" in line: + line = line[: line.index("#")] # strip of comment + dependencies.extend( + re.findall(r"(\w+)", line) + ) # catch all legible words, potential dependencies + dependencies = filter( + lambda x: "_" not in x and x.upper() != x, dependencies + ) # filter out constants + dependencies = filter(lambda x: x not in ignored_dependencies, dependencies) + dependencies = filter(lambda x: x not in custom_types, dependencies) + for dep in dependencies: + key_list = list(objects.keys()) + for item in [dep, key] + key_list[key_list.index(dep) + 1 :]: + objects[item] = objects.pop(item) + + +def combine_ssz_objects(old_objects: dict[str, str], new_objects: dict[str, str]) -> dict[str, str]: + """ + Takes in old spec and new spec ssz objects, combines them, + and returns the newer versions of the objects in dependency order. + """ + for key, value in new_objects.items(): + old_objects[key] = value + return old_objects + + +def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: + """ + Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function. + """ + protocols = combine_protocols(spec0.protocols, spec1.protocols) + functions = combine_dicts(spec0.functions, spec1.functions) + custom_types = combine_dicts(spec0.custom_types, spec1.custom_types) + preset_dep_custom_types = combine_dicts( + spec0.preset_dep_custom_types, spec1.preset_dep_custom_types + ) + constant_vars = combine_dicts(spec0.constant_vars, spec1.constant_vars) + preset_dep_constant_vars = combine_dicts( + spec0.preset_dep_constant_vars, spec1.preset_dep_constant_vars + ) + preset_vars = combine_dicts(spec0.preset_vars, spec1.preset_vars) + config_vars = combine_dicts(spec0.config_vars, spec1.config_vars) + ssz_dep_constants = combine_dicts(spec0.ssz_dep_constants, spec1.ssz_dep_constants) + func_dep_presets = combine_dicts(spec0.func_dep_presets, spec1.func_dep_presets) + ssz_objects = combine_ssz_objects(spec0.ssz_objects, spec1.ssz_objects) + dataclasses = combine_dicts(spec0.dataclasses, spec1.dataclasses) + return SpecObject( + functions=functions, + protocols=protocols, + custom_types=custom_types, + preset_dep_custom_types=preset_dep_custom_types, + constant_vars=constant_vars, + preset_dep_constant_vars=preset_dep_constant_vars, + preset_vars=preset_vars, + config_vars=config_vars, + ssz_dep_constants=ssz_dep_constants, + func_dep_presets=func_dep_presets, + ssz_objects=ssz_objects, + dataclasses=dataclasses, + ) + + +def parse_config_vars(conf: dict[str, str]) -> dict[str, str | list[dict[str, str]]]: + """ + Parses a dict of basic str/int/list types into a dict for insertion into the spec code. + """ + out: dict[str, str | list[dict[str, str]]] = dict() + for k, v in conf.items(): + if isinstance(v, list): + # A special case for list of records + out[k] = v + elif isinstance(v, str) and ( + v.startswith("0x") or k == "PRESET_BASE" or k == "CONFIG_NAME" + ): + # Represent byte data with string, to avoid misinterpretation as big-endian int. + # Everything except PRESET_BASE and CONFIG_NAME is either byte data or an integer. + out[k] = f"'{v}'" + else: + out[k] = str(int(v)) + return out diff --git a/pysetup/md_doc_paths.py b/pysetup/md_doc_paths.py new file mode 100644 index 0000000000..ca7e5c586d --- /dev/null +++ b/pysetup/md_doc_paths.py @@ -0,0 +1,94 @@ +import os + +from .constants import ( + ALTAIR, + BELLATRIX, + CAPELLA, + DENEB, + EIP6800, + EIP7441, + EIP7732, + EIP7805, + ELECTRA, + FULU, + PHASE0, +) + +PREVIOUS_FORK_OF = { + PHASE0: None, + ALTAIR: PHASE0, + BELLATRIX: ALTAIR, + CAPELLA: BELLATRIX, + DENEB: CAPELLA, + ELECTRA: DENEB, + FULU: ELECTRA, + EIP6800: DENEB, + EIP7441: CAPELLA, + EIP7732: ELECTRA, + EIP7805: ELECTRA, +} + +ALL_FORKS = list(PREVIOUS_FORK_OF.keys()) + +IGNORE_SPEC_FILES = ["specs/phase0/deposit-contract.md"] + +EXTRA_SPEC_FILES = {BELLATRIX: "sync/optimistic.md"} + +DEFAULT_ORDER = ( + "beacon-chain", + "polynomial-commitments", +) + + +def is_post_fork(a, b) -> bool: + """ + Returns true if fork a is after b, or if a == b + """ + if a == b: + return True + + prev_fork = PREVIOUS_FORK_OF[a] + if prev_fork == b: + return True + elif prev_fork is None: + return False + else: + return is_post_fork(prev_fork, b) + + +def get_fork_directory(fork): + dir1 = f"specs/{fork}" + if os.path.exists(dir1): + return dir1 + dir2 = f"specs/_features/{fork}" + if os.path.exists(dir2): + return dir2 + raise FileNotFoundError(f"No directory found for fork: {fork}") + + +def sort_key(s): + for index, key in enumerate(DEFAULT_ORDER): + if key in s: + return (index, s) + return (len(DEFAULT_ORDER), s) + + +def get_md_doc_paths(spec_fork: str) -> str: + md_doc_paths = "" + + for fork in ALL_FORKS: + if is_post_fork(spec_fork, fork): + # Append all files in fork directory recursively + for root, _, files in os.walk(get_fork_directory(fork)): + filepaths = [] + for filename in files: + filepath = os.path.join(root, filename) + filepaths.append(filepath) + for filepath in sorted(filepaths, key=sort_key): + if filepath.endswith(".md") and filepath not in IGNORE_SPEC_FILES: + md_doc_paths += filepath + "\n" + # Append extra files if any + if fork in EXTRA_SPEC_FILES: + md_doc_paths += EXTRA_SPEC_FILES[fork] + "\n" + + return md_doc_paths diff --git a/pysetup/md_to_spec.py b/pysetup/md_to_spec.py new file mode 100644 index 0000000000..fb0392b152 --- /dev/null +++ b/pysetup/md_to_spec.py @@ -0,0 +1,645 @@ +import ast +import json +import re +import string +from collections.abc import Iterator, Mapping +from functools import cache +from pathlib import Path +from typing import cast + +from marko.block import BlankLine, Document, FencedCode, Heading, HTMLBlock +from marko.element import Element +from marko.ext.gfm import gfm +from marko.ext.gfm.elements import Table, TableCell, TableRow +from marko.inline import CodeSpan + +from .typing import ProtocolDefinition, SpecObject, VariableDefinition + + +class MarkdownToSpec: + def __init__( + self, + file_name: Path, + preset: dict[str, str], + config: dict[str, str | list[dict[str, str]]], + preset_name: str, + ): + """ + Initializes the MarkdownToSpec instance. + """ + self.preset = preset + self.config = config + self.preset_name = preset_name + + self.document_iterator: Iterator[Element] = self._parse_document(file_name) + self.all_custom_types: dict[str, str] = {} + self.current_heading_name: str | None = None + + # Use a single dict to hold all SpecObject fields + self.spec: dict[str, dict] = { + "config_vars": {}, + "constant_vars": {}, + "custom_types": {}, + "dataclasses": {}, + "func_dep_presets": {}, + "functions": {}, + "preset_dep_constant_vars": {}, + "preset_dep_custom_types": {}, + "preset_vars": {}, + "protocols": {}, + "ssz_dep_constants": {}, + "ssz_objects": {}, + } + + def run(self) -> SpecObject: + """ + Parses the markdown spec file and returns the SpecObject. + """ + while (child := self._get_next_element()) is not None: + self._process_child(child) + self._finalize_types() + return self._build_spec_object() + + def _get_next_element(self) -> Element | None: + """ + Returns the next non-blank element in the document. + """ + try: + while isinstance(result := next(self.document_iterator), BlankLine): + pass + return result + except StopIteration: + return None + + def _skip_element(self) -> None: + """ + Skips the current element in the document. + """ + self._get_next_element() + + def _parse_document(self, file_name: Path) -> Iterator[Element]: + """ + Parses the markdown file into document elements. + """ + with open(file_name) as source_file: + document = parse_markdown(source_file.read()) + return iter(document.children) + + def _process_child(self, child: Element) -> None: + """Processes a child Markdown element by dispatching to the appropriate handler based on its type.""" + + # Skip blank lines + if isinstance(child, BlankLine): + return + + # Dispatch to the correct handler + match child: + case Heading(): + self._process_heading(child) + case FencedCode(): + self._process_code_block(child) + case Table(): + self._process_table(child) + case HTMLBlock(): + self._process_html_block(child) + + def _process_heading(self, heading: Heading) -> None: + """ + Extracts the section name from the heading and updates current_heading_name for context. + """ + self.current_heading_name = _get_name_from_heading(heading) + + def _process_code_block(self, code_block: FencedCode) -> None: + """ + Processes a FencedCode block, ignoring non-Python code. + - Extracts source code and determines if it is a function, dataclass, or class. + """ + if code_block.lang != "python": + return + + source = _get_source_from_code_block(code_block) + module = ast.parse(source) + + # AST element for each top level definition of the module + for element in module.body: + # Extract source preserving decorators and comments + lines = source.split("\n") + + # Determine start line - include decorators if present + if hasattr(element, "decorator_list") and element.decorator_list: + start_line = element.decorator_list[0].lineno - 1 + else: + start_line = element.lineno - 1 + + # Extract the source + end_line = element.end_lineno + element_source = "\n".join(lines[start_line:end_line]) + + clean_source = "\n".join(line.rstrip() for line in element_source.splitlines()) + + if isinstance(element, ast.FunctionDef): + self._process_code_def(clean_source, element) + elif isinstance(element, ast.ClassDef) and _has_decorator(element, "dataclass"): + self._add_dataclass(clean_source, element) + elif isinstance(element, ast.ClassDef): + self._process_code_class(clean_source, element) + else: + raise Exception("unrecognized python code element: " + source) + + def _process_code_def(self, source: str, fn: ast.FunctionDef) -> None: + """ + Processes a function definition and stores it in the spec. + """ + self_type_name = _get_self_type_from_source(fn) + + if self_type_name is None: + self.spec["functions"][fn.name] = source + else: + self._add_protocol_function(self_type_name, fn.name, source) + + def _add_protocol_function( + self, protocol_name: str, function_name: str, function_def: str + ) -> None: + """ + Adds a function definition to the protocol functions dictionary. + """ + if protocol_name not in self.spec["protocols"]: + self.spec["protocols"][protocol_name] = ProtocolDefinition(functions={}) + self.spec["protocols"][protocol_name].functions[function_name] = function_def + + def _add_dataclass(self, source: str, cls: ast.ClassDef) -> None: + self.spec["dataclasses"][cls.name] = source + + def _process_code_class(self, source: str, cls: ast.ClassDef) -> None: + """ + Processes a class definition and updates the spec. + """ + class_name, parent_class = _get_class_info_from_ast(cls) + + # check consistency with spec + if class_name != self.current_heading_name: + raise Exception(f"class_name {class_name} != current_name {self.current_heading_name}") + + if parent_class: + assert parent_class == "Container" + self.spec["ssz_objects"][class_name] = source + + def _process_table(self, table: Table) -> None: + """ + Processes a table and updates the spec with its data. + """ + for row in cast(list[TableRow], table.children): + if len(row.children) < 2: + continue + + name, value, description = self._get_table_row_fields(row) + + # Skip types that have been defined elsewhere + if description is not None and description.startswith(""): + continue + + # If it is not a constant, check if it is a custom type + if not _is_constant_id(name): + # Check for short type declarations + if value.startswith( + ("uint", "Bytes", "ByteList", "Union", "Vector", "List", "ByteVector") + ): + self.all_custom_types[name] = value + continue + + # It is a constant name and a generalized index + if value.startswith("get_generalized_index"): + self.spec["ssz_dep_constants"][name] = value + continue + + # It is a constant and not a generalized index, and a function-dependent preset + if description is not None and description.startswith(""): + self.spec["func_dep_presets"][name] = value + + # It is a constant and not a generalized index, and not a function-dependent preset + value_def = _parse_value(name, value) + # It is a preset + if name in self.preset: + if self.preset_name == "mainnet": + check_yaml_matches_spec(name, self.preset, value_def) + + self.spec["preset_vars"][name] = VariableDefinition( + value_def.type_name, self.preset[name], value_def.comment, None + ) + + # It is a config variable + elif name in self.config: + if self.preset_name == "mainnet": + check_yaml_matches_spec(name, self.config, value_def) + + config_value = self.config[name] + if isinstance(config_value, str): + self.spec["config_vars"][name] = VariableDefinition( + value_def.type_name, config_value, value_def.comment, None + ) + else: + raise ValueError(f"Variable {name} should be a string in the config file.") + + # It is a constant variable or a preset_dep_constant_vars + else: + if name in ("ENDIANNESS", "KZG_ENDIANNESS"): + # Deal with mypy Literal typing check + value_def = _parse_value(name, value, type_hint="Final") + if any(k in value for k in self.preset) or any( + k in value for k in self.spec["preset_dep_constant_vars"] + ): + self.spec["preset_dep_constant_vars"][name] = value_def + else: + self.spec["constant_vars"][name] = value_def + + @staticmethod + def _get_table_row_fields(row: TableRow) -> tuple[str, str, str | None]: + """ + Extracts the name, value, and description fields from a table row element. + """ + cells = cast(list[TableCell], row.children) + name_cell = cells[0] + name = name_cell.children[0].children + + value_cell = cells[1] + value = value_cell.children[0].children + + if isinstance(name, list): + name = name[0].children + if isinstance(value, list): + value = value[0].children + + description = None + if len(cells) >= 3: + description_cell = cells[2] + if len(description_cell.children) > 0: + description = description_cell.children[0].children + if isinstance(description, list): + description = description[0].children + + return name, value, description + + def _process_list_of_records_table(self, table: Table, list_of_records_name: str) -> None: + """ + Handles tables marked as 'list-of-records'. + Updates config_vars with the processed list. + + Example of input: + | Name | Calories | Description | + | ------ | ------------- | ------------- | + | Apple | `uint64(96)` | 5.3oz serving | + | Orange | `uint64(75)` | 5.6oz serving | + | Banana | `uint64(111)` | 4.4oz serving | + + The method _process_html_block calls this method when it encounters a comment + of the form ``. + """ + list_of_records_spec = self._extract_list_of_records_spec(table) + + # Make a type map from the spec definition + type_map = self._make_list_of_records_type_map(list_of_records_spec) + + # Apply the types to the file config + list_of_records_config_file = self._extract_typed_records_config( + list_of_records_name, type_map + ) + + # For mainnet, check that the spec config & file config are the same + # For minimal, we expect this to be different; just use the file config + if self.preset_name == "mainnet": + assert list_of_records_spec == list_of_records_config_file, ( + f"list of records mismatch: {list_of_records_spec} vs {list_of_records_config_file}" + ) + + # Set the config variable + self.spec["config_vars"][list_of_records_name] = list_of_records_config_file + + @staticmethod + def _make_list_of_records_type_map(list_of_records: list[dict[str, str]]) -> dict[str, str]: + """ + Given a list of records (each a dict of field name to value), extract a mapping + from field name to type name, based on values of the form 'TypeName(...)'. + """ + type_map: dict[str, str] = {} + pattern = re.compile(r"^(\w+)\(.*\)$") + for entry in list_of_records: + for k, v in entry.items(): + m = pattern.match(v) + if m: + type_map[k] = m.group(1) + return type_map + + @staticmethod + def _extract_list_of_records_spec(table: Table) -> list[dict[str, str]]: + """ + Extracts the list of records from a table element. + Returns a list of dicts, each representing a row with field names as keys. + """ + + # Save the table header, used for field names (skip last item: description) + header_row = cast(TableRow, table.children[0]) + list_of_records_spec_header = [ + re.sub(r"\s+", "_", value.children[0].children.upper()) + for value in header_row.children[:-1] + ] + + # Process the remaining rows + list_of_records_spec: list[dict[str, str]] = [ + { + list_of_records_spec_header[j]: value.children[0].children + for j, value in enumerate(row.children[:-1]) + } + for row in table.children[1:] + ] + + return list_of_records_spec + + def _extract_typed_records_config( + self, list_of_records_name: str, type_map: dict[str, str] + ) -> list[dict[str, str]]: + """ + Applies type constructors to config entries based on the type map. + Returns a new list of dicts with types applied. + """ + list_of_records_config_file: list[dict[str, str]] = [] + entries = self.config[list_of_records_name] + if not isinstance(entries, list): + raise ValueError(f"Expected a dict for {list_of_records_name} in config file") + + for entry in entries: + new_entry = {} + for k, v in entry.items(): + ctor = type_map.get(k) + if ctor: + new_entry[k] = f"{ctor}({v})" + else: + new_entry[k] = v + list_of_records_config_file.append(new_entry) + return list_of_records_config_file + + def _process_html_block(self, html: HTMLBlock) -> None: + """ + Handles HTML comments for skip logic and list-of-records detection. + Sets flags or state variables for the next iteration. + """ + body = html.body.strip() + + # This comment marks that we should skip the next element + if body == "": + self._skip_element() + + # Handle list-of-records tables + # This comment marks that the next table is a list-of-records + # e.g. + match = re.match(r"", body) + if match: + table_element = self._get_next_element() + if not isinstance(table_element, Table): + raise Exception( + f"expected table after list-of-records comment, got {type(table_element)}" + ) + self._process_list_of_records_table(table_element, match.group(1).upper()) + + def _finalize_types(self) -> None: + """ + Processes all_custom_types into custom_types and preset_dep_custom_types. + Calls helper functions to update KZG and CURDLEPROOFS setups if needed. + """ + # Update KZG trusted setup if needed + if any("KZG_SETUP" in name for name in self.spec["constant_vars"]): + _update_constant_vars_with_kzg_setups( + self.spec["constant_vars"], self.spec["preset_dep_constant_vars"], self.preset_name + ) + + # Update CURDLEPROOFS CRS if needed + if any("CURDLEPROOFS_CRS" in name for name in self.spec["constant_vars"]): + _update_constant_vars_with_curdleproofs_crs( + self.spec["constant_vars"], self.preset_name + ) + + # Split all_custom_types into custom_types and preset_dep_custom_types + self.spec["custom_types"] = {} + self.spec["preset_dep_custom_types"] = {} + for name, value in self.all_custom_types.items(): + if any(k in value for k in self.preset) or any( + k in value for k in self.spec["preset_dep_constant_vars"] + ): + self.spec["preset_dep_custom_types"][name] = value + else: + self.spec["custom_types"][name] = value + + def _build_spec_object(self) -> SpecObject: + """ + Returns the SpecObject using all collected data. + """ + return SpecObject( + config_vars=self.spec["config_vars"], + constant_vars=self.spec["constant_vars"], + custom_types=self.spec["custom_types"], + dataclasses=self.spec["dataclasses"], + func_dep_presets=self.spec["func_dep_presets"], + functions=self.spec["functions"], + preset_dep_constant_vars=self.spec["preset_dep_constant_vars"], + preset_dep_custom_types=self.spec["preset_dep_custom_types"], + preset_vars=self.spec["preset_vars"], + protocols=self.spec["protocols"], + ssz_dep_constants=self.spec["ssz_dep_constants"], + ssz_objects=self.spec["ssz_objects"], + ) + + +@cache +def _get_name_from_heading(heading: Heading) -> str | None: + last_child = heading.children[-1] + if isinstance(last_child, CodeSpan): + return last_child.children + return None + + +@cache +def _get_source_from_code_block(block: FencedCode) -> str: + return block.children[0].children.strip() + + +@cache +def _get_self_type_from_source(fn: ast.FunctionDef) -> str | None: + args = fn.args.args + if len(args) == 0: + return None + if args[0].arg != "self": + return None + if args[0].annotation is None: + return None + return args[0].annotation.id + + +@cache +def _get_class_info_from_ast(cls: ast.ClassDef) -> tuple[str, str | None]: + base = cls.bases[0] + if isinstance(base, ast.Name): + parent_class = base.id + elif isinstance(base, ast.Subscript): + parent_class = base.value.id + else: + # NOTE: SSZ definition derives from earlier phase... + # e.g. `phase0.SignedBeaconBlock` + # TODO: check for consistency with other phases + parent_class = None + return cls.name, parent_class + + +@cache +def _is_constant_id(name: str) -> bool: + """ + Checks if the given name follows the convention for constant identifiers. + """ + if name[0] not in string.ascii_uppercase + "_": + return False + return all(map(lambda c: c in string.ascii_uppercase + "_" + string.digits, name[1:])) + + +@cache +def _load_kzg_trusted_setups(preset_name: str) -> tuple[list[str], list[str], list[str]]: + trusted_setups_file_path = ( + str(Path(__file__).parent.parent) + + "/presets/" + + preset_name + + "/trusted_setups/trusted_setup_4096.json" + ) + + with open(trusted_setups_file_path) as f: + json_data = json.load(f) + trusted_setup_G1_monomial = json_data["g1_monomial"] + trusted_setup_G1_lagrange = json_data["g1_lagrange"] + trusted_setup_G2_monomial = json_data["g2_monomial"] + + return trusted_setup_G1_monomial, trusted_setup_G1_lagrange, trusted_setup_G2_monomial + + +@cache +def _load_curdleproofs_crs(preset_name: str) -> dict[str, list[str]]: + """ + NOTE: File generated from https://github.com/asn-d6/curdleproofs/blob/8e8bf6d4191fb6a844002f75666fb7009716319b/tests/crs.rs#L53-L67 + """ + file_path = ( + str(Path(__file__).parent.parent) + + "/presets/" + + preset_name + + "/trusted_setups/curdleproofs_crs.json" + ) + + with open(file_path) as f: + json_data = json.load(f) + + return json_data + + +ALL_KZG_SETUPS = { + "minimal": _load_kzg_trusted_setups("minimal"), + "mainnet": _load_kzg_trusted_setups("mainnet"), +} + +ALL_CURDLEPROOFS_CRS = { + "minimal": _load_curdleproofs_crs("minimal"), + "mainnet": _load_curdleproofs_crs("mainnet"), +} + + +@cache +def _parse_value(name: str, typed_value: str, type_hint: str | None = None) -> VariableDefinition: + comment = None + if name in ("ROOT_OF_UNITY_EXTENDED", "ROOTS_OF_UNITY_EXTENDED", "ROOTS_OF_UNITY_REDUCED"): + comment = "noqa: E501" + + typed_value = typed_value.strip() + if "(" not in typed_value: + return VariableDefinition( + type_name=None, value=typed_value, comment=comment, type_hint=type_hint + ) + i = typed_value.index("(") + type_name = typed_value[:i] + + return VariableDefinition( + type_name=type_name, value=typed_value[i + 1 : -1], comment=comment, type_hint=type_hint + ) + + +def _update_constant_vars_with_kzg_setups( + constant_vars: dict[str, VariableDefinition], + preset_dep_constant_vars: dict[str, VariableDefinition], + preset_name: str, +) -> None: + comment = "noqa: E501" + kzg_setups = ALL_KZG_SETUPS[preset_name] + preset_dep_constant_vars["KZG_SETUP_G1_MONOMIAL"] = VariableDefinition( + preset_dep_constant_vars["KZG_SETUP_G1_MONOMIAL"].value, str(kzg_setups[0]), comment, None + ) + preset_dep_constant_vars["KZG_SETUP_G1_LAGRANGE"] = VariableDefinition( + preset_dep_constant_vars["KZG_SETUP_G1_LAGRANGE"].value, str(kzg_setups[1]), comment, None + ) + constant_vars["KZG_SETUP_G2_MONOMIAL"] = VariableDefinition( + constant_vars["KZG_SETUP_G2_MONOMIAL"].value, str(kzg_setups[2]), comment, None + ) + + +def _update_constant_vars_with_curdleproofs_crs( + constant_vars: dict[str, VariableDefinition], preset_name: str +) -> None: + comment = "noqa: E501" + constant_vars["CURDLEPROOFS_CRS"] = VariableDefinition( + None, + "curdleproofs.CurdleproofsCrs.from_json(json.dumps(" + + str(ALL_CURDLEPROOFS_CRS[str(preset_name)]).replace("0x", "") + + "))", + comment, + None, + ) + + +@cache +def parse_markdown(content: str) -> Document: + return gfm.parse(content) + + +def check_yaml_matches_spec( + var_name: str, yaml: Mapping[str, str | list[dict[str, str]]], value_def: VariableDefinition +) -> None: + """ + This function performs a sanity check for presets & configs. To a certain degree, it ensures + that the values in the specifications match those in the yaml files. + """ + if var_name == "TERMINAL_BLOCK_HASH": + # This is just Hash32() in the specs, that's fine + return + + # We use a var in the definition of a new var, replace usages + # Reverse sort so that overridden values come first + updated_value = value_def.value + for var in sorted(yaml.keys(), reverse=True): + if var in updated_value: + value = yaml[var] + if isinstance(value, str): + updated_value = updated_value.replace(var, value) + + else: + raise ValueError(f"Variable {var} should be a string in the yaml file.") + try: + assert yaml[var_name] == repr(eval(updated_value)), ( + f"mismatch for {var_name}: {yaml[var_name]} vs {eval(updated_value)}" + ) + except NameError: + # Okay it's probably something more serious, let's ignore + pass + + +def _has_decorator(decorateable: ast.ClassDef | ast.FunctionDef, name: str) -> bool: + return any(_is_decorator(d, name) for d in decorateable.decorator_list) + + +def _is_decorator(decorator: ast.expr, name: str) -> bool: + return ( + (isinstance(decorator, ast.Name) and decorator.id == name) + or (isinstance(decorator, ast.Attribute) and decorator.attr == name) + or (isinstance(decorator, ast.Call) and decorator.func.id == name) + or (isinstance(decorator, ast.Subscript) and decorator.value.id == name) + ) diff --git a/pysetup/spec_builders/__init__.py b/pysetup/spec_builders/__init__.py new file mode 100644 index 0000000000..ba207753b5 --- /dev/null +++ b/pysetup/spec_builders/__init__.py @@ -0,0 +1,28 @@ +from .altair import AltairSpecBuilder +from .bellatrix import BellatrixSpecBuilder +from .capella import CapellaSpecBuilder +from .deneb import DenebSpecBuilder +from .eip6800 import EIP6800SpecBuilder +from .eip7441 import EIP7441SpecBuilder +from .eip7732 import EIP7732SpecBuilder +from .eip7805 import EIP7805SpecBuilder +from .electra import ElectraSpecBuilder +from .fulu import FuluSpecBuilder +from .phase0 import Phase0SpecBuilder + +spec_builders = { + builder.fork: builder + for builder in ( + Phase0SpecBuilder, + AltairSpecBuilder, + BellatrixSpecBuilder, + CapellaSpecBuilder, + DenebSpecBuilder, + ElectraSpecBuilder, + FuluSpecBuilder, + EIP6800SpecBuilder, + EIP7441SpecBuilder, + EIP7732SpecBuilder, + EIP7805SpecBuilder, + ) +} diff --git a/pysetup/spec_builders/altair.py b/pysetup/spec_builders/altair.py new file mode 100644 index 0000000000..4d88d2956f --- /dev/null +++ b/pysetup/spec_builders/altair.py @@ -0,0 +1,51 @@ +from ..constants import ALTAIR, OPTIMIZED_BLS_AGGREGATE_PUBKEYS +from .base import BaseSpecBuilder + + +class AltairSpecBuilder(BaseSpecBuilder): + fork: str = ALTAIR + + @classmethod + def imports(cls, preset_name: str) -> str: + return f""" +from typing import NewType, Union as PyUnion + +from eth2spec.phase0 import {preset_name} as phase0 +from eth2spec.test.helpers.merkle import build_proof +from eth2spec.utils.ssz.ssz_typing import Path +""" + + @classmethod + def preparations(cls): + return """ +SSZVariableName = str +GeneralizedIndex = int +""" + + @classmethod + def sundry_functions(cls) -> str: + return """ +def get_generalized_index(ssz_class: Any, *path: PyUnion[int, SSZVariableName]) -> GeneralizedIndex: + ssz_path = Path(ssz_class) + for item in path: + ssz_path = ssz_path / item + return GeneralizedIndex(ssz_path.gindex()) + + +def compute_merkle_proof(object: SSZObject, + index: GeneralizedIndex) -> list[Bytes32]: + return build_proof(object.get_backing(), index)""" + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> dict[str, str]: + return { + "FINALIZED_ROOT_GINDEX": "GeneralizedIndex(105)", + "CURRENT_SYNC_COMMITTEE_GINDEX": "GeneralizedIndex(54)", + "NEXT_SYNC_COMMITTEE_GINDEX": "GeneralizedIndex(55)", + } + + @classmethod + def implement_optimizations(cls, functions: dict[str, str]) -> dict[str, str]: + if "eth_aggregate_pubkeys" in functions: + functions["eth_aggregate_pubkeys"] = OPTIMIZED_BLS_AGGREGATE_PUBKEYS.strip() + return functions diff --git a/pysetup/spec_builders/base.py b/pysetup/spec_builders/base.py new file mode 100644 index 0000000000..6b52355421 --- /dev/null +++ b/pysetup/spec_builders/base.py @@ -0,0 +1,63 @@ +from abc import ABC, abstractmethod + + +class BaseSpecBuilder(ABC): + @property + @abstractmethod + def fork(self) -> str: + raise NotImplementedError() + + @classmethod + def imports(cls, preset_name: str) -> str: + """ + Import objects from other libraries. + """ + return "" + + @classmethod + def classes(cls) -> str: + """ + Define special classes. + """ + return "" + + @classmethod + def preparations(cls) -> str: + """ + Define special types/constants for building pyspec or call functions. + """ + return "" + + @classmethod + def sundry_functions(cls) -> str: + """ + The functions that are (1) defined abstractly in specs or (2) adjusted for getting better performance. + """ + return "" + + @classmethod + def execution_engine_cls(cls) -> str: + return "" + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> dict[str, str]: + """ + The constants that are required for SSZ objects. + """ + return {} + + @classmethod + def hardcoded_func_dep_presets(cls, spec_object) -> dict[str, str]: + return {} + + @classmethod + def implement_optimizations(cls, functions: dict[str, str]) -> dict[str, str]: + return functions + + @classmethod + def deprecate_constants(cls) -> set[str]: + return set() + + @classmethod + def deprecate_presets(cls) -> set[str]: + return set() diff --git a/pysetup/spec_builders/bellatrix.py b/pysetup/spec_builders/bellatrix.py new file mode 100644 index 0000000000..43bb928c8d --- /dev/null +++ b/pysetup/spec_builders/bellatrix.py @@ -0,0 +1,65 @@ +from ..constants import BELLATRIX +from .base import BaseSpecBuilder + + +class BellatrixSpecBuilder(BaseSpecBuilder): + fork: str = BELLATRIX + + @classmethod + def imports(cls, preset_name: str): + return f""" +from typing import Protocol +from eth2spec.altair import {preset_name} as altair +from eth2spec.utils.ssz.ssz_typing import Bytes8, Bytes20, ByteList, ByteVector +""" + + @classmethod + def sundry_functions(cls) -> str: + return """ +ExecutionState = Any + + +def get_pow_block(hash: Bytes32) -> Optional[PowBlock]: + return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0)) + + +def get_execution_state(_execution_state_root: Bytes32) -> ExecutionState: + pass + + +def get_pow_chain_head() -> PowBlock: + pass + + +def validator_is_connected(validator_index: ValidatorIndex) -> bool: + # pylint: disable=unused-argument + return True""" + + @classmethod + def execution_engine_cls(cls) -> str: + return """ +class NoopExecutionEngine(ExecutionEngine): + + def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + # pylint: disable=unused-argument + raise NotImplementedError("no default block production") + + def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + return True + + def verify_and_notify_new_payload(self: ExecutionEngine, + new_payload_request: NewPayloadRequest) -> bool: + return True + + +EXECUTION_ENGINE = NoopExecutionEngine()""" diff --git a/pysetup/spec_builders/capella.py b/pysetup/spec_builders/capella.py new file mode 100644 index 0000000000..27c9290cd4 --- /dev/null +++ b/pysetup/spec_builders/capella.py @@ -0,0 +1,18 @@ +from ..constants import CAPELLA +from .base import BaseSpecBuilder + + +class CapellaSpecBuilder(BaseSpecBuilder): + fork: str = CAPELLA + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.bellatrix import {preset_name} as bellatrix +""" + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> dict[str, str]: + return { + "EXECUTION_PAYLOAD_GINDEX": "GeneralizedIndex(25)", + } diff --git a/pysetup/spec_builders/deneb.py b/pysetup/spec_builders/deneb.py new file mode 100644 index 0000000000..49713dc19a --- /dev/null +++ b/pysetup/spec_builders/deneb.py @@ -0,0 +1,87 @@ +from ..constants import DENEB +from .base import BaseSpecBuilder + + +class DenebSpecBuilder(BaseSpecBuilder): + fork: str = DENEB + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.capella import {preset_name} as capella +""" + + @classmethod + def classes(cls): + return """ +class BLSFieldElement(bls.Scalar): + pass + + +class Polynomial(list): + def __init__(self, evals: Optional[Sequence[BLSFieldElement]] = None): + if evals is None: + evals = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_BLOB + if len(evals) != FIELD_ELEMENTS_PER_BLOB: + raise ValueError("expected FIELD_ELEMENTS_PER_BLOB evals") + super().__init__(evals) +""" + + @classmethod + def preparations(cls): + return """ +T = TypeVar('T') # For generic function +TPoint = TypeVar('TPoint') # For generic function. G1 or G2 point. +""" + + @classmethod + def sundry_functions(cls) -> str: + return """ +def retrieve_blobs_and_proofs(beacon_block_root: Root) -> Tuple[Sequence[Blob], Sequence[KZGProof]]: + # pylint: disable=unused-argument + return [], [] +""" + + @classmethod + def execution_engine_cls(cls) -> str: + return """ +class NoopExecutionEngine(ExecutionEngine): + + def notify_new_payload(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + # pylint: disable=unused-argument + raise NotImplementedError("no default block production") + + def is_valid_block_hash(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root) -> bool: + return True + + def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: + return True + + def verify_and_notify_new_payload(self: ExecutionEngine, + new_payload_request: NewPayloadRequest) -> bool: + return True + + +EXECUTION_ENGINE = NoopExecutionEngine()""" + + @classmethod + def hardcoded_func_dep_presets(cls, spec_object) -> dict[str, str]: + return { + "KZG_COMMITMENT_INCLUSION_PROOF_DEPTH": spec_object.preset_vars[ + "KZG_COMMITMENT_INCLUSION_PROOF_DEPTH" + ].value, + } diff --git a/pysetup/spec_builders/eip6800.py b/pysetup/spec_builders/eip6800.py new file mode 100644 index 0000000000..b90cbbd835 --- /dev/null +++ b/pysetup/spec_builders/eip6800.py @@ -0,0 +1,13 @@ +from ..constants import EIP6800 +from .base import BaseSpecBuilder + + +class EIP6800SpecBuilder(BaseSpecBuilder): + fork: str = EIP6800 + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.deneb import {preset_name} as deneb +from eth2spec.utils.ssz.ssz_typing import Bytes31 +""" diff --git a/pysetup/spec_builders/eip7441.py b/pysetup/spec_builders/eip7441.py new file mode 100644 index 0000000000..9d9cfdb074 --- /dev/null +++ b/pysetup/spec_builders/eip7441.py @@ -0,0 +1,21 @@ +from ..constants import EIP7441 +from .base import BaseSpecBuilder + + +class EIP7441SpecBuilder(BaseSpecBuilder): + fork: str = EIP7441 + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.capella import {preset_name} as capella +import curdleproofs +import json +""" + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> dict[str, str]: + constants = { + "EXECUTION_PAYLOAD_GINDEX": "GeneralizedIndex(41)", + } + return {**super().hardcoded_ssz_dep_constants(), **constants} diff --git a/pysetup/spec_builders/eip7732.py b/pysetup/spec_builders/eip7732.py new file mode 100644 index 0000000000..d3d7f6d384 --- /dev/null +++ b/pysetup/spec_builders/eip7732.py @@ -0,0 +1,37 @@ +from ..constants import EIP7732 +from .base import BaseSpecBuilder + + +class EIP7732SpecBuilder(BaseSpecBuilder): + fork: str = EIP7732 + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.electra import {preset_name} as electra +""" + + @classmethod + def sundry_functions(cls) -> str: + return """ +def concat_generalized_indices(*indices: GeneralizedIndex) -> GeneralizedIndex: + o = GeneralizedIndex(1) + for i in indices: + o = GeneralizedIndex(o * bit_floor(i) + (i - bit_floor(i))) + return o""" + + @classmethod + def deprecate_constants(cls) -> set[str]: + return set( + [ + "EXECUTION_PAYLOAD_GINDEX", + ] + ) + + @classmethod + def deprecate_presets(cls) -> set[str]: + return set( + [ + "KZG_COMMITMENT_INCLUSION_PROOF_DEPTH", + ] + ) diff --git a/pysetup/spec_builders/eip7805.py b/pysetup/spec_builders/eip7805.py new file mode 100644 index 0000000000..6feaa3c5e8 --- /dev/null +++ b/pysetup/spec_builders/eip7805.py @@ -0,0 +1,52 @@ +from ..constants import EIP7805 +from .base import BaseSpecBuilder + + +class EIP7805SpecBuilder(BaseSpecBuilder): + fork: str = EIP7805 + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.electra import {preset_name} as electra +""" + + @classmethod + def execution_engine_cls(cls) -> str: + return """ +class NoopExecutionEngine(ExecutionEngine): + + def notify_new_payload(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes], + inclusion_list_transactions: Sequence[Transaction]) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + # pylint: disable=unused-argument + raise NotImplementedError("no default block production") + + def is_valid_block_hash(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes], + inclusion_list_transactions: Sequence[Transaction]) -> bool: + return True + + def is_valid_versioned_hashes(self: ExecutionEngine, new_payload_request: NewPayloadRequest) -> bool: + return True + + def verify_and_notify_new_payload(self: ExecutionEngine, + new_payload_request: NewPayloadRequest) -> bool: + return True + + +EXECUTION_ENGINE = NoopExecutionEngine()""" diff --git a/pysetup/spec_builders/electra.py b/pysetup/spec_builders/electra.py new file mode 100644 index 0000000000..6e8dd52f17 --- /dev/null +++ b/pysetup/spec_builders/electra.py @@ -0,0 +1,59 @@ +from ..constants import ELECTRA +from .base import BaseSpecBuilder + + +class ElectraSpecBuilder(BaseSpecBuilder): + fork: str = ELECTRA + + @classmethod + def imports(cls, preset_name: str): + return f""" +from eth2spec.deneb import {preset_name} as deneb +from eth2spec.utils.ssz.ssz_impl import ssz_serialize, ssz_deserialize +""" + + @classmethod + def hardcoded_ssz_dep_constants(cls) -> dict[str, str]: + return { + "FINALIZED_ROOT_GINDEX_ELECTRA": "GeneralizedIndex(169)", + "CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA": "GeneralizedIndex(86)", + "NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA": "GeneralizedIndex(87)", + } + + @classmethod + def execution_engine_cls(cls) -> str: + return """ +class NoopExecutionEngine(ExecutionEngine): + + def notify_new_payload(self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes]) -> bool: + return True + + def notify_forkchoice_updated(self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes]) -> Optional[PayloadId]: + pass + + def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + # pylint: disable=unused-argument + raise NotImplementedError("no default block production") + + def is_valid_block_hash(self: ExecutionEngine, + execution_payload: ExecutionPayload, + 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: + return True + + def verify_and_notify_new_payload(self: ExecutionEngine, + new_payload_request: NewPayloadRequest) -> bool: + return True + + +EXECUTION_ENGINE = NoopExecutionEngine()""" diff --git a/pysetup/spec_builders/fulu.py b/pysetup/spec_builders/fulu.py new file mode 100644 index 0000000000..641affde0f --- /dev/null +++ b/pysetup/spec_builders/fulu.py @@ -0,0 +1,57 @@ +from ..constants import FULU +from .base import BaseSpecBuilder + + +class FuluSpecBuilder(BaseSpecBuilder): + fork: str = FULU + + @classmethod + def imports(cls, preset_name: str): + return f""" +from frozendict import frozendict +from eth2spec.electra import {preset_name} as electra +""" + + @classmethod + def classes(cls): + return """ +class PolynomialCoeff(list): + def __init__(self, coeffs: Sequence[BLSFieldElement]): + if len(coeffs) > FIELD_ELEMENTS_PER_EXT_BLOB: + raise ValueError("expected <= FIELD_ELEMENTS_PER_EXT_BLOB coeffs") + super().__init__(coeffs) + + +class Coset(list): + def __init__(self, coeffs: Optional[Sequence[BLSFieldElement]] = None): + if coeffs is None: + coeffs = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_CELL + if len(coeffs) != FIELD_ELEMENTS_PER_CELL: + raise ValueError("expected FIELD_ELEMENTS_PER_CELL coeffs") + super().__init__(coeffs) + + +class CosetEvals(list): + def __init__(self, evals: Optional[Sequence[BLSFieldElement]] = None): + if evals is None: + evals = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_CELL + if len(evals) != FIELD_ELEMENTS_PER_CELL: + raise ValueError("expected FIELD_ELEMENTS_PER_CELL coeffs") + super().__init__(evals) +""" + + @classmethod + def sundry_functions(cls) -> str: + return """ +def retrieve_column_sidecars(beacon_block_root: Root) -> Sequence[DataColumnSidecar]: + # pylint: disable=unused-argument + return [] +""" + + @classmethod + def hardcoded_func_dep_presets(cls, spec_object) -> dict[str, str]: + return { + "KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH": spec_object.preset_vars[ + "KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH" + ].value, + } diff --git a/pysetup/spec_builders/phase0.py b/pysetup/spec_builders/phase0.py new file mode 100644 index 0000000000..1561907db4 --- /dev/null +++ b/pysetup/spec_builders/phase0.py @@ -0,0 +1,104 @@ +from ..constants import PHASE0 +from .base import BaseSpecBuilder + + +class Phase0SpecBuilder(BaseSpecBuilder): + fork: str = PHASE0 + + @classmethod + def imports(cls, preset_name: str) -> str: + return """from lru import LRU +from dataclasses import ( + dataclass, + field, +) +from typing import ( + Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple, Final +) + +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes +from eth2spec.utils.ssz.ssz_typing import ( + View, boolean, Container, List, Vector, uint8, uint32, uint64, uint256, + Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) +from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 +from eth2spec.utils import bls +from eth2spec.utils.hash_function import hash +""" + + @classmethod + def preparations(cls) -> str: + return """ +SSZObject = TypeVar('SSZObject', bound=View) +""" + + @classmethod + def sundry_functions(cls) -> str: + return ''' +def get_eth1_data(block: Eth1Block) -> Eth1Data: + """ + A stub function return mocking Eth1Data. + """ + return Eth1Data( + deposit_root=block.deposit_root, + deposit_count=block.deposit_count, + block_hash=hash_tree_root(block)) + + +def cache_this(key_fn, value_fn, lru_size): # type: ignore + cache_dict = LRU(size=lru_size) + + def wrapper(*args, **kw): # type: ignore + key = key_fn(*args, **kw) + if key not in cache_dict: + cache_dict[key] = value_fn(*args, **kw) + return cache_dict[key] + return wrapper + + +_compute_shuffled_index = compute_shuffled_index +compute_shuffled_index = cache_this( + lambda index, index_count, seed: (index, index_count, seed), + _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) + +_get_total_active_balance = get_total_active_balance +get_total_active_balance = cache_this( + lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), + _get_total_active_balance, lru_size=10) + +_get_base_reward = get_base_reward +get_base_reward = cache_this( + lambda state, index: (state.validators.hash_tree_root(), state.slot, index), + _get_base_reward, lru_size=2048) + +_get_committee_count_per_slot = get_committee_count_per_slot +get_committee_count_per_slot = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) + +_get_active_validator_indices = get_active_validator_indices +get_active_validator_indices = cache_this( + lambda state, epoch: (state.validators.hash_tree_root(), epoch), + _get_active_validator_indices, lru_size=3) + +_get_beacon_committee = get_beacon_committee +get_beacon_committee = cache_this( + lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), + _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) + +_get_matching_target_attestations = get_matching_target_attestations +get_matching_target_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_target_attestations, lru_size=10) + +_get_matching_head_attestations = get_matching_head_attestations +get_matching_head_attestations = cache_this( + lambda state, epoch: (state.hash_tree_root(), epoch), + _get_matching_head_attestations, lru_size=10) + +_get_attesting_indices = get_attesting_indices +get_attesting_indices = cache_this( + lambda state, attestation: ( + state.randao_mixes.hash_tree_root(), + state.validators.hash_tree_root(), attestation.hash_tree_root() + ), + _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)''' diff --git a/pysetup/typing.py b/pysetup/typing.py new file mode 100644 index 0000000000..4c7df5712c --- /dev/null +++ b/pysetup/typing.py @@ -0,0 +1,35 @@ +from pathlib import Path +from typing import NamedTuple + + +class ProtocolDefinition(NamedTuple): + # just function definitions currently. May expand with configuration vars in future. + functions: dict[str, str] + + +class VariableDefinition(NamedTuple): + type_name: str | None + value: str + comment: str | None # e.g. "noqa: E501" + type_hint: str | None # e.g., "Final" + + +class SpecObject(NamedTuple): + functions: dict[str, str] + protocols: dict[str, ProtocolDefinition] + custom_types: dict[str, str] + preset_dep_custom_types: dict[str, str] # the types that depend on presets + constant_vars: dict[str, VariableDefinition] + preset_dep_constant_vars: dict[str, VariableDefinition] + preset_vars: dict[str, VariableDefinition] + config_vars: dict[str, VariableDefinition] + ssz_dep_constants: dict[str, str] # the constants that depend on ssz_objects + func_dep_presets: dict[str, str] # the constants that depend on functions + ssz_objects: dict[str, str] + dataclasses: dict[str, str] + + +class BuildTarget(NamedTuple): + name: str + preset_paths: list[Path] + config_path: Path diff --git a/scripts/gen_kzg_trusted_setups.py b/scripts/gen_kzg_trusted_setups.py new file mode 100644 index 0000000000..98cb9b972e --- /dev/null +++ b/scripts/gen_kzg_trusted_setups.py @@ -0,0 +1,43 @@ +import os +from pathlib import Path + +from eth2spec.utils.kzg import ( + dump_kzg_trusted_setup_files, +) + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument( + "--secret", + dest="secret", + type=int, + required=True, + help='the secret of trusted setup', + ) + parser.add_argument( + "--g1-length", + dest="g1_length", + type=int, + required=True, + help='the length of G1 trusted setup', + ) + parser.add_argument( + "--g2-length", + dest="g2_length", + type=int, + required=True, + help='the length of G2 trusted setup', + ) + parser.add_argument( + "-o", + "--output-dir", + dest="output_dir", + required=True, + help='the output directory', + ) + args = parser.parse_args() + + dump_kzg_trusted_setup_files(args.secret, args.g1_length, args.g2_length, args.output_dir) diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 0000000000..98eca5969d --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1 @@ +../[generator] diff --git a/setup.py b/setup.py index 6e55cfddeb..76a16e2612 100644 --- a/setup.py +++ b/setup.py @@ -1,770 +1,67 @@ -from setuptools import setup, find_packages, Command -from setuptools.command.build_py import build_py +import copy +import logging +import os +import string +import warnings +from collections import OrderedDict +from collections.abc import Sequence from distutils import dir_util from distutils.util import convert_path +from functools import cache from pathlib import Path -import os -import re -import string -import textwrap -from typing import Dict, NamedTuple, List, Sequence, Optional, TypeVar -from abc import ABC, abstractmethod -import ast - - -# NOTE: have to programmatically include third-party dependencies in `setup.py`. -RUAMEL_YAML_VERSION = "ruamel.yaml==0.16.5" -try: - import ruamel.yaml -except ImportError: - import pip - pip.main(["install", RUAMEL_YAML_VERSION]) +from typing import cast from ruamel.yaml import YAML +from setuptools import Command, find_packages, setup +from setuptools.command.build_py import build_py -MARKO_VERSION = "marko==1.0.2" -try: - import marko -except ImportError: - import pip - pip.main(["install", MARKO_VERSION]) - -from marko.block import Heading, FencedCode, LinkRefDef, BlankLine -from marko.inline import CodeSpan -from marko.ext.gfm import gfm -from marko.ext.gfm.elements import Table, Paragraph - - -# Definitions in context.py -PHASE0 = 'phase0' -ALTAIR = 'altair' -MERGE = 'merge' - -# The helper functions that are used when defining constants -CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS = ''' -def ceillog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"ceillog2 accepts only positive values, x={x}") - return uint64((x - 1).bit_length()) - - -def floorlog2(x: int) -> uint64: - if x < 1: - raise ValueError(f"floorlog2 accepts only positive values, x={x}") - return uint64(x.bit_length() - 1) -''' - - -OPTIMIZED_BLS_AGGREGATE_PUBKEYS = ''' -def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: - return bls.AggregatePKs(pubkeys) -''' - - -class ProtocolDefinition(NamedTuple): - # just function definitions currently. May expand with configuration vars in future. - functions: Dict[str, str] - - -class VariableDefinition(NamedTuple): - type_name: Optional[str] - value: str - comment: Optional[str] # e.g. "noqa: E501" - - -class SpecObject(NamedTuple): - functions: Dict[str, str] - protocols: Dict[str, ProtocolDefinition] - custom_types: Dict[str, str] - constant_vars: Dict[str, VariableDefinition] - preset_vars: Dict[str, VariableDefinition] - config_vars: Dict[str, VariableDefinition] - ssz_dep_constants: Dict[str, str] # the constants that depend on ssz_objects - ssz_objects: Dict[str, str] - dataclasses: Dict[str, str] - - -def _get_name_from_heading(heading: Heading) -> Optional[str]: - last_child = heading.children[-1] - if isinstance(last_child, CodeSpan): - return last_child.children - return None - - -def _get_source_from_code_block(block: FencedCode) -> str: - return block.children[0].children.strip() - - -def _get_function_name_from_source(source: str) -> str: - fn = ast.parse(source).body[0] - return fn.name - - -def _get_self_type_from_source(source: str) -> Optional[str]: - fn = ast.parse(source).body[0] - args = fn.args.args - if len(args) == 0: - return None - if args[0].arg != 'self': - return None - if args[0].annotation is None: - return None - return args[0].annotation.id - - -def _get_class_info_from_source(source: str) -> (str, Optional[str]): - class_def = ast.parse(source).body[0] - base = class_def.bases[0] - if isinstance(base, ast.Name): - parent_class = base.id - else: - # NOTE: SSZ definition derives from earlier phase... - # e.g. `phase0.SignedBeaconBlock` - # TODO: check for consistency with other phases - parent_class = None - return class_def.name, parent_class - - -def _is_constant_id(name: str) -> bool: - if name[0] not in string.ascii_uppercase + '_': - return False - return all(map(lambda c: c in string.ascii_uppercase + '_' + string.digits, name[1:])) - - -ETH2_SPEC_COMMENT_PREFIX = "eth2spec:" - - -def _get_eth2_spec_comment(child: LinkRefDef) -> Optional[str]: - _, _, title = child._parse_info - if not (title[0] == "(" and title[len(title)-1] == ")"): - return None - title = title[1:len(title)-1] - if not title.startswith(ETH2_SPEC_COMMENT_PREFIX): - return None - return title[len(ETH2_SPEC_COMMENT_PREFIX):].strip() - - -def _parse_value(name: str, typed_value: str) -> VariableDefinition: - comment = None - if name == "BLS12_381_Q": - comment = "noqa: E501" - - typed_value = typed_value.strip() - if '(' not in typed_value: - return VariableDefinition(type_name=None, value=typed_value, comment=comment) - i = typed_value.index('(') - type_name = typed_value[:i] - - return VariableDefinition(type_name=type_name, value=typed_value[i+1:-1], comment=comment) - - -def get_spec(file_name: Path, preset: Dict[str, str], config: Dict[str, str]) -> SpecObject: - functions: Dict[str, str] = {} - protocols: Dict[str, ProtocolDefinition] = {} - constant_vars: Dict[str, VariableDefinition] = {} - preset_vars: Dict[str, VariableDefinition] = {} - config_vars: Dict[str, VariableDefinition] = {} - ssz_dep_constants: Dict[str, str] = {} - ssz_objects: Dict[str, str] = {} - dataclasses: Dict[str, str] = {} - custom_types: Dict[str, str] = {} - - with open(file_name) as source_file: - document = gfm.parse(source_file.read()) - - current_name = None - should_skip = False - for child in document.children: - if isinstance(child, BlankLine): - continue - if should_skip: - should_skip = False - continue - if isinstance(child, Heading): - current_name = _get_name_from_heading(child) - elif isinstance(child, FencedCode): - if child.lang != "python": - continue - source = _get_source_from_code_block(child) - if source.startswith("def"): - current_name = _get_function_name_from_source(source) - self_type_name = _get_self_type_from_source(source) - function_def = "\n".join(line.rstrip() for line in source.splitlines()) - if self_type_name is None: - functions[current_name] = function_def - else: - if self_type_name not in protocols: - protocols[self_type_name] = ProtocolDefinition(functions={}) - protocols[self_type_name].functions[current_name] = function_def - elif source.startswith("@dataclass"): - dataclasses[current_name] = "\n".join(line.rstrip() for line in source.splitlines()) - elif source.startswith("class"): - class_name, parent_class = _get_class_info_from_source(source) - # check consistency with spec - assert class_name == current_name - if parent_class: - assert parent_class == "Container" - # NOTE: trim whitespace from spec - ssz_objects[current_name] = "\n".join(line.rstrip() for line in source.splitlines()) - else: - raise Exception("unrecognized python code element") - elif isinstance(child, Table): - for row in child.children: - cells = row.children - if len(cells) >= 2: - name_cell = cells[0] - name = name_cell.children[0].children - - value_cell = cells[1] - value = value_cell.children[0].children - if isinstance(value, list): - # marko parses `**X**` as a list containing a X - value = value[0].children - - if not _is_constant_id(name): - # Check for short type declarations - if value.startswith(("uint", "Bytes", "ByteList", "Union")): - custom_types[name] = value - continue - - if value.startswith("get_generalized_index"): - ssz_dep_constants[name] = value - continue - - value_def = _parse_value(name, value) - if name in preset: - preset_vars[name] = VariableDefinition(value_def.type_name, preset[name], value_def.comment) - elif name in config: - config_vars[name] = VariableDefinition(value_def.type_name, config[name], value_def.comment) - else: - constant_vars[name] = value_def - - elif isinstance(child, LinkRefDef): - comment = _get_eth2_spec_comment(child) - if comment == "skip": - should_skip = True - - return SpecObject( - functions=functions, - protocols=protocols, - custom_types=custom_types, - constant_vars=constant_vars, - preset_vars=preset_vars, - config_vars=config_vars, - ssz_dep_constants=ssz_dep_constants, - ssz_objects=ssz_objects, - dataclasses=dataclasses, - ) - - -class SpecBuilder(ABC): - @property - @abstractmethod - def fork(self) -> str: - raise NotImplementedError() - - @classmethod - @abstractmethod - def imports(cls, preset_name: str) -> str: - """ - Import objects from other libraries. - """ - raise NotImplementedError() - - @classmethod - @abstractmethod - def preparations(cls) -> str: - """ - Define special types/constants for building pyspec or call functions. - """ - raise NotImplementedError() - - @classmethod - @abstractmethod - def sundry_functions(cls) -> str: - """ - The functions that are (1) defined abstractly in specs or (2) adjusted for getting better performance. - """ - raise NotImplementedError() - - @classmethod - @abstractmethod - def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: - """ - The constants that are required for SSZ objects. - """ - raise NotImplementedError() - - @classmethod - @abstractmethod - def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]: # TODO - """ - The constants that are required for custom types. - """ - raise NotImplementedError() - - @classmethod - @abstractmethod - def implement_optimizations(cls, functions: Dict[str, str]) -> Dict[str, str]: - raise NotImplementedError() - - @classmethod - @abstractmethod - def build_spec(cls, preset_name: str, - source_files: List[Path], preset_files: Sequence[Path], config_file: Path) -> str: - raise NotImplementedError() - - -# -# Phase0SpecBuilder -# -class Phase0SpecBuilder(SpecBuilder): - fork: str = PHASE0 - - @classmethod - def imports(cls, preset_name: str) -> str: - return '''from lru import LRU -from dataclasses import ( - dataclass, - field, +from pysetup.constants import ( + PHASE0, ) -from typing import ( - Any, Callable, Dict, Set, Sequence, Tuple, Optional, TypeVar, NamedTuple +from pysetup.helpers import ( + combine_spec_objects, + dependency_order_class_objects, + objects_to_spec, + parse_config_vars, +) +from pysetup.md_doc_paths import get_md_doc_paths +from pysetup.md_to_spec import MarkdownToSpec +from pysetup.spec_builders import spec_builders +from pysetup.typing import ( + BuildTarget, + SpecObject, ) -from eth2spec.utils.ssz.ssz_impl import hash_tree_root, copy, uint_to_bytes -from eth2spec.utils.ssz.ssz_typing import ( - View, boolean, Container, List, Vector, uint8, uint32, uint64, - Bytes1, Bytes4, Bytes32, Bytes48, Bytes96, Bitlist) -from eth2spec.utils.ssz.ssz_typing import Bitvector # noqa: F401 -from eth2spec.utils import bls -from eth2spec.utils.hash_function import hash -''' - - @classmethod - def preparations(cls) -> str: - return ''' -SSZObject = TypeVar('SSZObject', bound=View) -''' - - @classmethod - def sundry_functions(cls) -> str: - return ''' -def get_eth1_data(block: Eth1Block) -> Eth1Data: - """ - A stub function return mocking Eth1Data. - """ - return Eth1Data( - deposit_root=block.deposit_root, - deposit_count=block.deposit_count, - block_hash=hash_tree_root(block)) - - -def cache_this(key_fn, value_fn, lru_size): # type: ignore - cache_dict = LRU(size=lru_size) - - def wrapper(*args, **kw): # type: ignore - key = key_fn(*args, **kw) - nonlocal cache_dict - if key not in cache_dict: - cache_dict[key] = value_fn(*args, **kw) - return cache_dict[key] - return wrapper - - -_compute_shuffled_index = compute_shuffled_index -compute_shuffled_index = cache_this( - lambda index, index_count, seed: (index, index_count, seed), - _compute_shuffled_index, lru_size=SLOTS_PER_EPOCH * 3) - -_get_total_active_balance = get_total_active_balance -get_total_active_balance = cache_this( - lambda state: (state.validators.hash_tree_root(), compute_epoch_at_slot(state.slot)), - _get_total_active_balance, lru_size=10) - -_get_base_reward = get_base_reward -get_base_reward = cache_this( - lambda state, index: (state.validators.hash_tree_root(), state.slot, index), - _get_base_reward, lru_size=2048) - -_get_committee_count_per_slot = get_committee_count_per_slot -get_committee_count_per_slot = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_committee_count_per_slot, lru_size=SLOTS_PER_EPOCH * 3) - -_get_active_validator_indices = get_active_validator_indices -get_active_validator_indices = cache_this( - lambda state, epoch: (state.validators.hash_tree_root(), epoch), - _get_active_validator_indices, lru_size=3) - -_get_beacon_committee = get_beacon_committee -get_beacon_committee = cache_this( - lambda state, slot, index: (state.validators.hash_tree_root(), state.randao_mixes.hash_tree_root(), slot, index), - _get_beacon_committee, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3) - -_get_matching_target_attestations = get_matching_target_attestations -get_matching_target_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_target_attestations, lru_size=10) - -_get_matching_head_attestations = get_matching_head_attestations -get_matching_head_attestations = cache_this( - lambda state, epoch: (state.hash_tree_root(), epoch), - _get_matching_head_attestations, lru_size=10) - -_get_attesting_indices = get_attesting_indices -get_attesting_indices = cache_this( - lambda state, data, bits: ( - state.randao_mixes.hash_tree_root(), - state.validators.hash_tree_root(), data.hash_tree_root(), bits.hash_tree_root() - ), - _get_attesting_indices, lru_size=SLOTS_PER_EPOCH * MAX_COMMITTEES_PER_SLOT * 3)''' - - @classmethod - def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: - return {} - - @classmethod - def hardcoded_custom_type_dep_constants(cls) -> Dict[str, str]: - return {} - - @classmethod - def implement_optimizations(cls, functions: Dict[str, str]) -> Dict[str, str]: - return functions - - @classmethod - def build_spec(cls, preset_name: str, - source_files: Sequence[Path], preset_files: Sequence[Path], config_file: Path) -> str: - return _build_spec(preset_name, cls.fork, source_files, preset_files, config_file) - - -# -# AltairSpecBuilder -# -class AltairSpecBuilder(Phase0SpecBuilder): - fork: str = ALTAIR - - @classmethod - def imports(cls, preset_name: str) -> str: - return super().imports(preset_name) + '\n' + f''' -from typing import NewType, Union as PyUnion - -from eth2spec.phase0 import {preset_name} as phase0 -from eth2spec.utils.ssz.ssz_typing import Path -''' - - @classmethod - def preparations(cls): - return super().preparations() + '\n' + ''' -SSZVariableName = str -GeneralizedIndex = NewType('GeneralizedIndex', int) -''' - - @classmethod - def sundry_functions(cls) -> str: - return super().sundry_functions() + '\n\n' + ''' -def get_generalized_index(ssz_class: Any, *path: Sequence[PyUnion[int, SSZVariableName]]) -> GeneralizedIndex: - ssz_path = Path(ssz_class) - for item in path: - ssz_path = ssz_path / item - return GeneralizedIndex(ssz_path.gindex())''' - - - @classmethod - def hardcoded_ssz_dep_constants(cls) -> Dict[str, str]: - constants = { - 'FINALIZED_ROOT_INDEX': 'GeneralizedIndex(105)', - 'NEXT_SYNC_COMMITTEE_INDEX': 'GeneralizedIndex(55)', - } - return {**super().hardcoded_ssz_dep_constants(), **constants} - - @classmethod - def implement_optimizations(cls, functions: Dict[str, str]) -> Dict[str, str]: - if "eth_aggregate_pubkeys" in functions: - functions["eth_aggregate_pubkeys"] = OPTIMIZED_BLS_AGGREGATE_PUBKEYS.strip() - return super().implement_optimizations(functions) - -# -# MergeSpecBuilder -# -class MergeSpecBuilder(AltairSpecBuilder): - fork: str = MERGE - - @classmethod - def imports(cls, preset_name: str): - return super().imports(preset_name) + f''' -from typing import Protocol -from eth2spec.altair import {preset_name} as altair -from eth2spec.utils.ssz.ssz_typing import Bytes20, ByteList, ByteVector, uint256, Union -''' - - @classmethod - def preparations(cls): - return super().preparations() - - @classmethod - def sundry_functions(cls) -> str: - return super().sundry_functions() + '\n\n' + """ -ExecutionState = Any - - -def get_pow_block(hash: Bytes32) -> PowBlock: - return PowBlock(block_hash=hash, parent_hash=Bytes32(), total_difficulty=uint256(0), difficulty=uint256(0)) - - -def get_execution_state(execution_state_root: Bytes32) -> ExecutionState: - pass - - -def get_pow_chain_head() -> PowBlock: - pass - - -class NoopExecutionEngine(ExecutionEngine): - - def on_payload(self, execution_payload: ExecutionPayload) -> bool: - return True - - def set_head(self, block_hash: Hash32) -> bool: - return True - - def finalize_block(self, block_hash: Hash32) -> bool: - return True - - def assemble_block(self, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload: - raise NotImplementedError("no default block production") - - -EXECUTION_ENGINE = NoopExecutionEngine()""" - - - @classmethod - def hardcoded_custom_type_dep_constants(cls) -> str: - constants = { - 'MAX_BYTES_PER_OPAQUE_TRANSACTION': 'uint64(2**20)', - } - return {**super().hardcoded_custom_type_dep_constants(), **constants} +# Ignore '1.5.0-alpha.*' to '1.5.0a*' messages. +warnings.filterwarnings("ignore", message="Normalizing .* to .*") -spec_builders = { - builder.fork: builder - for builder in (Phase0SpecBuilder, AltairSpecBuilder, MergeSpecBuilder) -} +# Ignore 'running' and 'creating' messages +class PyspecFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + return not record.getMessage().startswith(("running ", "creating ")) -def is_spec_defined_type(value: str) -> bool: - return value.startswith('ByteList') or value.startswith('Union') +logging.getLogger().addFilter(PyspecFilter()) -def objects_to_spec(preset_name: str, - spec_object: SpecObject, - builder: SpecBuilder, - ordered_class_objects: Dict[str, str]) -> str: - """ - Given all the objects that constitute a spec, combine them into a single pyfile. - """ - new_type_definitions = ( - '\n\n'.join( - [ - f"class {key}({value}):\n pass\n" - for key, value in spec_object.custom_types.items() - if not is_spec_defined_type(value) - ] - ) - + ('\n\n' if len([key for key, value in spec_object.custom_types.items() if is_spec_defined_type(value)]) > 0 else '') - + '\n\n'.join( - [ - f"{key} = {value}\n" - for key, value in spec_object.custom_types.items() - if is_spec_defined_type(value) - ] - ) - ) - - def format_protocol(protocol_name: str, protocol_def: ProtocolDefinition) -> str: - protocol = f"class {protocol_name}(Protocol):" - for fn_source in protocol_def.functions.values(): - fn_source = fn_source.replace("self: "+protocol_name, "self") - protocol += "\n\n" + textwrap.indent(fn_source, " ") - return protocol - - protocols_spec = '\n\n\n'.join(format_protocol(k, v) for k, v in spec_object.protocols.items()) - for k in list(spec_object.functions): - if "ceillog2" in k or "floorlog2" in k: - del spec_object.functions[k] - functions = builder.implement_optimizations(spec_object.functions) - functions_spec = '\n\n\n'.join(functions.values()) - - # Access global dict of config vars for runtime configurables - for name in spec_object.config_vars.keys(): - functions_spec = functions_spec.replace(name, 'config.' + name) - - def format_config_var(name: str, vardef: VariableDefinition) -> str: - if vardef.type_name is None: - out = f'{name}={vardef.value},' - else: - out = f'{name}={vardef.type_name}({vardef.value}),' - if vardef.comment is not None: - out += f' # {vardef.comment}' - return out - - config_spec = 'class Configuration(NamedTuple):\n' - config_spec += ' PRESET_BASE: str\n' - config_spec += '\n'.join(f' {k}: {v.type_name if v.type_name is not None else "int"}' - for k, v in spec_object.config_vars.items()) - config_spec += '\n\n\nconfig = Configuration(\n' - config_spec += f' PRESET_BASE="{preset_name}",\n' - config_spec += '\n'.join(' ' + format_config_var(k, v) for k, v in spec_object.config_vars.items()) - config_spec += '\n)\n' - - def format_constant(name: str, vardef: VariableDefinition) -> str: - if vardef.type_name is None: - out = f'{name} = {vardef.value}' - else: - out = f'{name} = {vardef.type_name}({vardef.value})' - if vardef.comment is not None: - out += f' # {vardef.comment}' - return out - - constant_vars_spec = '# Constant vars\n' + '\n'.join(format_constant(k, v) for k, v in spec_object.constant_vars.items()) - preset_vars_spec = '# Preset vars\n' + '\n'.join(format_constant(k, v) for k, v in spec_object.preset_vars.items()) - ordered_class_objects_spec = '\n\n\n'.join(ordered_class_objects.values()) - ssz_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_ssz_dep_constants()[x]), builder.hardcoded_ssz_dep_constants())) - ssz_dep_constants_verification = '\n'.join(map(lambda x: 'assert %s == %s' % (x, spec_object.ssz_dep_constants[x]), builder.hardcoded_ssz_dep_constants())) - custom_type_dep_constants = '\n'.join(map(lambda x: '%s = %s' % (x, builder.hardcoded_custom_type_dep_constants()[x]), builder.hardcoded_custom_type_dep_constants())) - spec = ( - builder.imports(preset_name) - + builder.preparations() - + '\n\n' + f"fork = \'{builder.fork}\'\n" - # The constants that some SSZ containers require. Need to be defined before `new_type_definitions` - + ('\n\n' + custom_type_dep_constants + '\n' if custom_type_dep_constants != '' else '') - + '\n\n' + new_type_definitions - + '\n' + CONSTANT_DEP_SUNDRY_CONSTANTS_FUNCTIONS - # The constants that some SSZ containers require. Need to be defined before `constants_spec` - + ('\n\n' + ssz_dep_constants if ssz_dep_constants != '' else '') - + '\n\n' + constant_vars_spec - + '\n\n' + preset_vars_spec - + '\n\n\n' + config_spec - + '\n\n' + ordered_class_objects_spec - + ('\n\n\n' + protocols_spec if protocols_spec != '' else '') - + '\n\n\n' + functions_spec - + '\n\n' + builder.sundry_functions() - # Since some constants are hardcoded in setup.py, the following assertions verify that the hardcoded constants are - # as same as the spec definition. - + ('\n\n\n' + ssz_dep_constants_verification if ssz_dep_constants_verification != '' else '') - + '\n' - ) - return spec - - -def combine_protocols(old_protocols: Dict[str, ProtocolDefinition], - new_protocols: Dict[str, ProtocolDefinition]) -> Dict[str, ProtocolDefinition]: - for key, value in new_protocols.items(): - if key not in old_protocols: - old_protocols[key] = value - else: - functions = combine_dicts(old_protocols[key].functions, value.functions) - old_protocols[key] = ProtocolDefinition(functions=functions) - return old_protocols - - -T = TypeVar('T') - - -def combine_dicts(old_dict: Dict[str, T], new_dict: Dict[str, T]) -> Dict[str, T]: - return {**old_dict, **new_dict} - - -ignored_dependencies = [ - 'bit', 'boolean', 'Vector', 'List', 'Container', 'BLSPubkey', 'BLSSignature', - 'Bytes1', 'Bytes4', 'Bytes20', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector', - 'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256', - 'bytes', 'byte', 'ByteList', 'ByteVector', - 'Dict', 'dict', 'field', 'ceillog2', 'floorlog2', 'Set', -] - - -def dependency_order_class_objects(objects: Dict[str, str], custom_types: Dict[str, str]) -> None: - """ - Determines which SSZ Object is dependent on which other and orders them appropriately - """ - items = list(objects.items()) - for key, value in items: - dependencies = [] - for line in value.split('\n'): - if not re.match(r'\s+\w+: .+', line): - continue # skip whitespace etc. - line = line[line.index(':') + 1:] # strip of field name - if '#' in line: - line = line[:line.index('#')] # strip of comment - dependencies.extend(re.findall(r'(\w+)', line)) # catch all legible words, potential dependencies - dependencies = filter(lambda x: '_' not in x and x.upper() != x, dependencies) # filter out constants - dependencies = filter(lambda x: x not in ignored_dependencies, dependencies) - dependencies = filter(lambda x: x not in custom_types, dependencies) - for dep in dependencies: - key_list = list(objects.keys()) - for item in [dep, key] + key_list[key_list.index(dep)+1:]: - objects[item] = objects.pop(item) - - -def combine_ssz_objects(old_objects: Dict[str, str], new_objects: Dict[str, str], custom_types) -> Dict[str, str]: - """ - Takes in old spec and new spec ssz objects, combines them, - and returns the newer versions of the objects in dependency order. - """ - for key, value in new_objects.items(): - old_objects[key] = value - return old_objects +def get_spec( + file_name: Path, + preset: dict[str, str], + config: dict[str, str | list[dict[str, str]]], + preset_name: str, +) -> SpecObject: + return MarkdownToSpec(file_name, preset, config, preset_name).run() -def combine_spec_objects(spec0: SpecObject, spec1: SpecObject) -> SpecObject: - """ - Takes in two spec variants (as tuples of their objects) and combines them using the appropriate combiner function. - """ - protocols = combine_protocols(spec0.protocols, spec1.protocols) - functions = combine_dicts(spec0.functions, spec1.functions) - custom_types = combine_dicts(spec0.custom_types, spec1.custom_types) - constant_vars = combine_dicts(spec0.constant_vars, spec1.constant_vars) - preset_vars = combine_dicts(spec0.preset_vars, spec1.preset_vars) - config_vars = combine_dicts(spec0.config_vars, spec1.config_vars) - ssz_dep_constants = combine_dicts(spec0.ssz_dep_constants, spec1.ssz_dep_constants) - ssz_objects = combine_ssz_objects(spec0.ssz_objects, spec1.ssz_objects, custom_types) - dataclasses = combine_dicts(spec0.dataclasses, spec1.dataclasses) - return SpecObject( - functions=functions, - protocols=protocols, - custom_types=custom_types, - constant_vars=constant_vars, - preset_vars=preset_vars, - config_vars=config_vars, - ssz_dep_constants=ssz_dep_constants, - ssz_objects=ssz_objects, - dataclasses=dataclasses, - ) - - -def parse_config_vars(conf: Dict[str, str]) -> Dict[str, str]: - """ - Parses a dict of basic str/int/list types into a dict for insertion into the spec code. +@cache +def load_preset(preset_files: Sequence[Path]) -> dict[str, str]: """ - out: Dict[str, str] = dict() - for k, v in conf.items(): - if isinstance(v, str) and (v.startswith("0x") or k == 'PRESET_BASE'): - # Represent byte data with string, to avoid misinterpretation as big-endian int. - # Everything is either byte data or an integer, with PRESET_BASE as one exception. - out[k] = f"'{v}'" - else: - out[k] = str(int(v)) - return out - - -def load_preset(preset_files: Sequence[Path]) -> Dict[str, str]: + Loads a directory of preset files, merges the result into one preset. """ - Loads the a directory of preset files, merges the result into one preset. - """ - preset = {} + preset: dict[str, str] = {} for fork_file in preset_files: - yaml = YAML(typ='base') + yaml = YAML(typ="base") fork_preset: dict = yaml.load(fork_file) if fork_preset is None: # for empty YAML files continue @@ -773,38 +70,46 @@ def load_preset(preset_files: Sequence[Path]) -> Dict[str, str]: raise Exception(f"duplicate config var(s) in preset files: {', '.join(duplicates)}") preset.update(fork_preset) assert preset != {} - return parse_config_vars(preset) + return cast(dict[str, str], parse_config_vars(preset)) -def load_config(config_path: Path) -> Dict[str, str]: +@cache +def load_config(config_path: Path) -> dict[str, str | list[dict[str, str]]]: """ Loads the given configuration file. """ - yaml = YAML(typ='base') + yaml = YAML(typ="base") config_data = yaml.load(config_path) return parse_config_vars(config_data) -def _build_spec(preset_name: str, fork: str, - source_files: Sequence[Path], preset_files: Sequence[Path], config_file: Path) -> str: - preset = load_preset(preset_files) +def build_spec( + fork: str, + preset_name: str, + source_files: Sequence[Path], + preset_files: Sequence[Path], + config_file: Path, +) -> str: + preset = load_preset(tuple(preset_files)) config = load_config(config_file) - all_specs = [get_spec(spec, preset, config) for spec in source_files] + all_specs = [get_spec(spec, preset, config, preset_name) for spec in source_files] spec_object = all_specs[0] for value in all_specs[1:]: spec_object = combine_spec_objects(spec_object, value) class_objects = {**spec_object.ssz_objects, **spec_object.dataclasses} - dependency_order_class_objects(class_objects, spec_object.custom_types) - - return objects_to_spec(preset_name, spec_object, spec_builders[fork], class_objects) + # Ensure it's ordered after multiple forks + new_objects: dict[str, str] = {} + while OrderedDict(new_objects) != OrderedDict(class_objects): + new_objects = copy.deepcopy(class_objects) + dependency_order_class_objects( + class_objects, + spec_object.custom_types | spec_object.preset_dep_custom_types, + ) -class BuildTarget(NamedTuple): - name: str - preset_paths: List[Path] - config_path: Path + return objects_to_spec(preset_name, spec_object, fork, class_objects) class PySpecCommand(Command): @@ -814,102 +119,94 @@ class PySpecCommand(Command): spec_fork: str md_doc_paths: str - parsed_md_doc_paths: List[str] + parsed_md_doc_paths: list[Path] build_targets: str - parsed_build_targets: List[BuildTarget] + parsed_build_targets: list[BuildTarget] out_dir: str # The format is (long option, short option, description). user_options = [ - ('spec-fork=', None, "Spec fork to tag build with. Used to select md-docs defaults."), - ('md-doc-paths=', None, "List of paths of markdown files to build spec with"), - ('build-targets=', None, "Names, directory paths of compile-time presets, and default config paths."), - ('out-dir=', None, "Output directory to write spec package to") + ("spec-fork=", None, "Spec fork to tag build with. Used to select md-docs defaults."), + ("md-doc-paths=", None, "List of paths of markdown files to build spec with"), + ( + "build-targets=", + None, + "Names, directory paths of compile-time presets, and default config paths.", + ), + ("out-dir=", None, "Output directory to write spec package to"), ] - def initialize_options(self): + def initialize_options(self) -> None: """Set default values for options.""" # Each user option must be listed here with their default value. self.spec_fork = PHASE0 - self.md_doc_paths = '' - self.out_dir = 'pyspec_output' + self.md_doc_paths = "" + self.out_dir = "pyspec_output" self.build_targets = """ minimal:presets/minimal:configs/minimal.yaml mainnet:presets/mainnet:configs/mainnet.yaml """ - def finalize_options(self): + def finalize_options(self) -> None: """Post-process options.""" if len(self.md_doc_paths) == 0: - print("no paths were specified, using default markdown file paths for pyspec" - " build (spec fork: %s)" % self.spec_fork) - if self.spec_fork in (PHASE0, ALTAIR, MERGE): - self.md_doc_paths = """ - specs/phase0/beacon-chain.md - specs/phase0/fork-choice.md - specs/phase0/validator.md - specs/phase0/weak-subjectivity.md - """ - if self.spec_fork in (ALTAIR, MERGE): - self.md_doc_paths += """ - specs/altair/beacon-chain.md - specs/altair/bls.md - specs/altair/fork.md - specs/altair/validator.md - specs/altair/p2p-interface.md - specs/altair/sync-protocol.md - """ - if self.spec_fork == MERGE: - self.md_doc_paths += """ - specs/merge/beacon-chain.md - specs/merge/fork.md - specs/merge/fork-choice.md - specs/merge/validator.md - """ + self.md_doc_paths = get_md_doc_paths(self.spec_fork) if len(self.md_doc_paths) == 0: - raise Exception('no markdown files specified, and spec fork "%s" is unknown', self.spec_fork) + raise Exception( + f"No markdown files specified, and spec fork {self.spec_fork!r} is unknown" + ) - self.parsed_md_doc_paths = self.md_doc_paths.split() + self.parsed_md_doc_paths = [Path(p) for p in self.md_doc_paths.split()] for filename in self.parsed_md_doc_paths: if not os.path.exists(filename): - raise Exception('Pyspec markdown input file "%s" does not exist.' % filename) + raise Exception(f"Pyspec markdown input file {filename!r} does not exist") self.parsed_build_targets = [] for target in self.build_targets.split(): target = target.strip() - data = target.split(':') + data = target.split(":") if len(data) != 3: - raise Exception('invalid target, expected "name:preset_dir:config_file" format, but got: %s' % target) + raise Exception( + f"invalid target, expected 'name:preset_dir:config_file' format, but got: {target}" + ) name, preset_dir_path, config_path = data if any((c not in string.digits + string.ascii_letters) for c in name): - raise Exception('invalid target name: "%s"' % name) + raise Exception(f"invalid target name: {name!r}") if not os.path.exists(preset_dir_path): - raise Exception('Preset dir "%s" does not exist' % preset_dir_path) + raise Exception(f"Preset dir {preset_dir_path!r} does not exist") _, _, preset_file_names = next(os.walk(preset_dir_path)) preset_paths = [(Path(preset_dir_path) / name) for name in preset_file_names] if not os.path.exists(config_path): - raise Exception('Config file "%s" does not exist' % config_path) + raise Exception(f"Config file {config_path!r} does not exist") self.parsed_build_targets.append(BuildTarget(name, preset_paths, Path(config_path))) - def run(self): + def run(self) -> None: if not self.dry_run: dir_util.mkpath(self.out_dir) - for (name, preset_paths, config_path) in self.parsed_build_targets: - spec_str = spec_builders[self.spec_fork].build_spec( - name, self.parsed_md_doc_paths, preset_paths, config_path) + print(f"Building pyspec: {self.spec_fork}") + for name, preset_paths, config_path in self.parsed_build_targets: + spec_str = build_spec( + spec_builders[self.spec_fork].fork, + name, + self.parsed_md_doc_paths, + preset_paths, + config_path, + ) if self.dry_run: - self.announce('dry run successfully prepared contents for spec.' - f' out dir: "{self.out_dir}", spec fork: "{self.spec_fork}", build target: "{name}"') + self.announce( + "dry run successfully prepared contents for spec." + f' out dir: "{self.out_dir}", spec fork: "{self.spec_fork}", build target: "{name}"' + ) self.debug_print(spec_str) else: - with open(os.path.join(self.out_dir, name+'.py'), 'w') as out: + with open(os.path.join(self.out_dir, name + ".py"), "w") as out: out.write(spec_str) if not self.dry_run: - with open(os.path.join(self.out_dir, '__init__.py'), 'w') as out: + with open(os.path.join(self.out_dir, "__init__.py"), "w") as out: # `mainnet` is the default spec. out.write("from . import mainnet as spec # noqa:F401\n") @@ -917,56 +214,52 @@ def run(self): class BuildPyCommand(build_py): """Customize the build command to run the spec-builder on setup.py build""" - def initialize_options(self): - super(BuildPyCommand, self).initialize_options() + def initialize_options(self) -> None: + super().initialize_options() - def run_pyspec_cmd(self, spec_fork: str, **opts): - cmd_obj: PySpecCommand = self.distribution.reinitialize_command("pyspec") + def run_pyspec_cmd(self, spec_fork: str) -> None: + cmd_obj = cast(PySpecCommand, self.distribution.reinitialize_command("pyspec")) cmd_obj.spec_fork = spec_fork - cmd_obj.out_dir = os.path.join(self.build_lib, 'eth2spec', spec_fork) - for k, v in opts.items(): - setattr(cmd_obj, k, v) - self.run_command('pyspec') + cmd_obj.out_dir = os.path.join(self.build_lib, "eth2spec", spec_fork) + self.run_command("pyspec") - def run(self): + def run(self) -> None: for spec_fork in spec_builders: self.run_pyspec_cmd(spec_fork=spec_fork) - super(BuildPyCommand, self).run() + super().run() class PyspecDevCommand(Command): """Build the markdown files in-place to their source location for testing.""" + description = "Build the markdown files in-place to their source location for testing." - user_options = [] - def initialize_options(self): + def initialize_options(self) -> None: pass - def finalize_options(self): + def finalize_options(self) -> None: pass - def run_pyspec_cmd(self, spec_fork: str, **opts): - cmd_obj: PySpecCommand = self.distribution.reinitialize_command("pyspec") + def run_pyspec_cmd(self, spec_fork: str) -> None: + cmd_obj = cast(PySpecCommand, self.distribution.reinitialize_command("pyspec")) cmd_obj.spec_fork = spec_fork - eth2spec_dir = convert_path(self.distribution.package_dir['eth2spec']) + eth2spec_dir = convert_path(self.distribution.package_dir["eth2spec"]) cmd_obj.out_dir = os.path.join(eth2spec_dir, spec_fork) - for k, v in opts.items(): - setattr(cmd_obj, k, v) - self.run_command('pyspec') + self.run_command("pyspec") - def run(self): - print("running build_py command") + def run(self) -> None: for spec_fork in spec_builders: self.run_pyspec_cmd(spec_fork=spec_fork) + commands = { - 'pyspec': PySpecCommand, - 'build_py': BuildPyCommand, - 'pyspecdev': PyspecDevCommand, + "pyspec": PySpecCommand, + "build_py": BuildPyCommand, + "pyspecdev": PyspecDevCommand, } -with open("README.md", "rt", encoding="utf8") as f: +with open("README.md", encoding="utf8") as f: readme = f.read() # How to use "VERSION.txt" file: @@ -977,47 +270,31 @@ def run(self): # -> In case of a commit on master without git tag, target the next version # with ".postN" (release candidate, numbered) suffixed. # See https://www.python.org/dev/peps/pep-0440/#public-version-identifiers -with open(os.path.join('tests', 'core', 'pyspec', 'eth2spec', 'VERSION.txt')) as f: +with open(os.path.join("tests", "core", "pyspec", "eth2spec", "VERSION.txt")) as f: spec_version = f.read().strip() setup( - name='eth2spec', version=spec_version, - description="Eth2 spec, provided as Python package for tooling and testing", long_description=readme, long_description_content_type="text/markdown", - author="ethereum", - url="https://github.com/ethereum/eth2.0-specs", + url="https://github.com/ethereum/consensus-specs", include_package_data=False, - package_data={'configs': ['*.yaml'], - 'presets': ['*.yaml'], - 'specs': ['**/*.md'], - 'eth2spec': ['VERSION.txt']}, + package_data={ + "configs": ["*.yaml"], + "eth2spec": ["VERSION.txt"], + "presets": ["**/*.yaml", "**/*.json"], + "specs": ["**/*.md"], + "sync": ["optimistic.md"], + }, package_dir={ - "eth2spec": "tests/core/pyspec/eth2spec", "configs": "configs", + "eth2spec": "tests/core/pyspec/eth2spec", "presets": "presets", "specs": "specs", + "sync": "sync", }, - packages=find_packages(where='tests/core/pyspec') + ['configs', 'specs'], + packages=find_packages(where="tests/core/pyspec") + + ["configs", "presets", "specs", "presets", "sync"], py_modules=["eth2spec"], cmdclass=commands, - python_requires=">=3.8, <4", - extras_require={ - "test": ["pytest>=4.4", "pytest-cov", "pytest-xdist"], - "lint": ["flake8==3.7.7", "mypy==0.812"], - "generator": ["python-snappy==0.5.4"], - }, - install_requires=[ - "eth-utils>=1.3.0,<2", - "eth-typing>=2.1.0,<3.0.0", - "pycryptodome==3.9.4", - "py_ecc==5.2.0", - "milagro_bls_binding==1.6.3", - "dataclasses==0.6", - "remerkleable==0.1.22", - RUAMEL_YAML_VERSION, - "lru-dict==1.1.6", - MARKO_VERSION, - ] ) diff --git a/solidity_deposit_contract/Makefile b/solidity_deposit_contract/Makefile new file mode 100644 index 0000000000..60d45ede49 --- /dev/null +++ b/solidity_deposit_contract/Makefile @@ -0,0 +1,42 @@ +SOLIDITY_FILE_NAME = deposit_contract.json +SOLIDITY_DEPOSIT_CONTRACT_SOURCE = deposit_contract.sol +DEPOSIT_CONTRACT_TESTER_DIR = web3_tester + +export DAPP_SKIP_BUILD:=1 +export DAPP_SRC:=$(CURDIR) +export DAPP_LIB:=$(CURDIR)/lib +export DAPP_JSON:=build/combined.json + +all: \ + compile_deposit_contract \ + install_deposit_contract_web3_tester \ + test_deposit_contract_web3_tests + +compile_deposit_contract: + @git submodule update --recursive --init + @solc --metadata-literal --optimize --optimize-runs 5000000 --bin --abi \ + --combined-json=abi,bin,bin-runtime,srcmap,srcmap-runtime,ast,metadata,storage-layout \ + --overwrite -o build $(SOLIDITY_DEPOSIT_CONTRACT_SOURCE) tests/deposit_contract.t.sol + @/bin/echo -n '{"abi": ' > $(SOLIDITY_FILE_NAME) + @cat build/DepositContract.abi >> $(SOLIDITY_FILE_NAME) + @/bin/echo -n ', "bytecode": "0x' >> $(SOLIDITY_FILE_NAME) + @cat build/DepositContract.bin >> $(SOLIDITY_FILE_NAME) + @/bin/echo -n '"}' >> $(SOLIDITY_FILE_NAME) + +test_deposit_contract: + @dapp test -v --fuzz-runs 5 + +install_deposit_contract_web3_tester: + @cd $(DEPOSIT_CONTRACT_TESTER_DIR); \ + python3 -m venv venv; \ + source venv/bin/activate; \ + python3 -m pip install -r requirements_preinstallation.txt; \ + python3 -m pip install -r requirements.txt + +test_deposit_contract_web3_tests: + @cd $(DEPOSIT_CONTRACT_TESTER_DIR); \ + source venv/bin/activate; \ + python3 -m pytest . + +clean: + @git clean -fdx diff --git a/solidity_deposit_contract/README.md b/solidity_deposit_contract/README.md index 0388d7d2f5..2687aa266c 100644 --- a/solidity_deposit_contract/README.md +++ b/solidity_deposit_contract/README.md @@ -2,28 +2,38 @@ ## History -This is a rewrite of the [Vyper Eth 2.0 deposit contract](https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/deposit_contract/contracts/validator_registration.vy) to Solidity. +This is a rewrite of the +[Vyper Eth 2.0 deposit contract](https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/deposit_contract/contracts/validator_registration.vy) +to Solidity. -The original motivation was to run the SMTChecker and the new Yul IR generator option (`--ir`) in the compiler. +The original motivation was to run the SMTChecker and the new Yul IR generator +option (`--ir`) in the compiler. -As of June 2020, version `r1` of the Solidity deposit contract has been verified and is considered for adoption. -See this [blog post](https://blog.ethereum.org/2020/06/23/eth2-quick-update-no-12/) for more information. +As of June 2020, version `r1` of the Solidity deposit contract has been verified +and is considered for adoption. See this +[blog post](https://blog.ethereum.org/2020/06/23/eth2-quick-update-no-12/) for +more information. -In August 2020, version `r2` was released with metadata modifications and relicensed to CC0-1.0. Afterward, this contract has been ported back to from [`axic/eth2-deposit-contract`](https://github.com/axic/eth2-deposit-contract) to this repository and replaced the Vyper deposit contract. +In August 2020, version `r2` was released with metadata modifications and +relicensed to CC0-1.0. Afterward, this contract has been ported back to from +[`axic/eth2-deposit-contract`](https://github.com/axic/eth2-deposit-contract) to +this repository and replaced the Vyper deposit contract. ## Compiling solidity deposit contract -In the `eth2.0-specs` directory run: +In this directory run: + ```sh make compile_deposit_contract ``` -The following parameters were used to generate the bytecode for the `DepositContract` available in this repository: +The following parameters were used to generate the bytecode for the +`DepositContract` available in this repository: -* Contract Name: `DepositContract` -* Compiler Version: Solidity `v0.6.11+commit.5ef660b1` -* Optimization Enabled: `Yes` with `5000000` runs -* Metadata Options: `--metadata-literal` (to verify metadata hash) +- Contract Name: `DepositContract` +- Compiler Version: Solidity `v0.6.11+commit.5ef660b1` +- Optimization Enabled: `Yes` with `5000000` runs +- Metadata Options: `--metadata-literal` (to verify metadata hash) ```sh solc --optimize --optimize-runs 5000000 --metadata-literal --bin deposit_contract.sol @@ -31,12 +41,15 @@ solc --optimize --optimize-runs 5000000 --metadata-literal --bin deposit_contrac ## Running web3 tests -1. In the `eth2.0-specs` directory run `make install_deposit_contract_web3_tester` to install the tools needed (make sure to have Python 3.7 and pip installed). -2. In the `eth2.0-specs` directory run `make test_deposit_contract_web3_tests` to execute the tests. +1. In this directory run `make install_deposit_contract_web3_tester` to install + the tools needed (make sure to have Python 3 and pip installed). +2. In this directory run `make test_deposit_contract_web3_tests` to execute the + tests. ## Running randomized `dapp` tests: -Install the latest version of `dapp` by following the instructions at [dapp.tools](https://dapp.tools/). Then in the `eth2.0-specs` directory run: +Install the latest version of `dapp` by following the instructions at +[dapp.tools](https://dapp.tools/). Then in the `eth2.0-specs` directory run: ```sh make test_deposit_contract diff --git a/solidity_deposit_contract/web3_tester/requirements.txt b/solidity_deposit_contract/web3_tester/requirements.txt index 8dadab0bc2..92c8298bfb 100644 --- a/solidity_deposit_contract/web3_tester/requirements.txt +++ b/solidity_deposit_contract/web3_tester/requirements.txt @@ -1,5 +1,5 @@ -eth-tester[py-evm]>=0.3.0b1,<0.4 -web3==5.4.0 -pytest==3.6.1 +eth-tester[py-evm]>=0.12.0b1 +web3>=6.11.0 +pytest>=4.4 # The eth2spec ../../ diff --git a/solidity_deposit_contract/web3_tester/requirements_preinstallation.txt b/solidity_deposit_contract/web3_tester/requirements_preinstallation.txt new file mode 100644 index 0000000000..83128122e5 --- /dev/null +++ b/solidity_deposit_contract/web3_tester/requirements_preinstallation.txt @@ -0,0 +1,5 @@ +pip>=24.0.0 +wheel>=0.44.0 +setuptools>=72.0.0 +pylint>=3.2.0 +build>=1.2.2 \ No newline at end of file diff --git a/solidity_deposit_contract/web3_tester/tests/conftest.py b/solidity_deposit_contract/web3_tester/tests/conftest.py index 57b5f6b7a7..31686eabf3 100644 --- a/solidity_deposit_contract/web3_tester/tests/conftest.py +++ b/solidity_deposit_contract/web3_tester/tests/conftest.py @@ -51,7 +51,7 @@ def registration_contract(w3, tester): abi=contract_abi, bytecode=contract_bytecode) tx_hash = registration.constructor().transact() - tx_receipt = w3.eth.waitForTransactionReceipt(tx_hash) + tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash) registration_deployed = w3.eth.contract( address=tx_receipt.contractAddress, abi=contract_abi diff --git a/solidity_deposit_contract/web3_tester/tests/test_deposit.py b/solidity_deposit_contract/web3_tester/tests/test_deposit.py index 4b16446a16..e4b794ffbf 100644 --- a/solidity_deposit_contract/web3_tester/tests/test_deposit.py +++ b/solidity_deposit_contract/web3_tester/tests/test_deposit.py @@ -2,7 +2,7 @@ import pytest import eth_utils -from eth2spec.phase0.spec import DepositData +from eth2spec.phase0.mainnet import DepositData from eth2spec.utils.ssz.ssz_typing import List from eth2spec.utils.ssz.ssz_impl import hash_tree_root @@ -119,7 +119,7 @@ def test_deposit_inputs(registration_contract, def test_deposit_event_log(registration_contract, a0, w3): - log_filter = registration_contract.events.DepositEvent.createFilter( + log_filter = registration_contract.events.DepositEvent.create_filter( fromBlock='latest', ) deposit_amount_list = [randint(MIN_DEPOSIT_AMOUNT, FULL_DEPOSIT_AMOUNT * 2) for _ in range(3)] @@ -154,7 +154,7 @@ def test_deposit_event_log(registration_contract, a0, w3): def test_deposit_tree(registration_contract, w3, assert_tx_failed): - log_filter = registration_contract.events.DepositEvent.createFilter( + log_filter = registration_contract.events.DepositEvent.create_filter( fromBlock='latest', ) @@ -178,7 +178,7 @@ def test_deposit_tree(registration_contract, w3, assert_tx_failed): tx_hash = registration_contract.functions.deposit( *deposit_input, ).transact({"value": deposit_amount_list[i] * eth_utils.denoms.gwei}) - receipt = w3.eth.getTransactionReceipt(tx_hash) + receipt = w3.eth.get_transaction_receipt(tx_hash) print("deposit transaction consumes %d gas" % receipt['gasUsed']) logs = log_filter.get_new_entries() diff --git a/specs/.pages b/specs/.pages new file mode 100644 index 0000000000..7e47dc5f75 --- /dev/null +++ b/specs/.pages @@ -0,0 +1,4 @@ +nav: + - phase0 + - ... + - _features diff --git a/specs/custody_game/beacon-chain.md b/specs/_deprecated/custody_game/beacon-chain.md similarity index 73% rename from specs/custody_game/beacon-chain.md rename to specs/_deprecated/custody_game/beacon-chain.md index 6f9f61cf92..8588a54104 100644 --- a/specs/custody_game/beacon-chain.md +++ b/specs/_deprecated/custody_game/beacon-chain.md @@ -1,23 +1,20 @@ # Custody Game -- The Beacon Chain -**Notice**: This document is a work-in-progress for researchers and implementers. +*Note*: This document is a work-in-progress for researchers and implementers. -## Table of contents - - - - + - [Introduction](#introduction) - [Constants](#constants) - [Misc](#misc) -- [Configuration](#configuration) + - [Domain types](#domain-types) +- [Preset](#preset) - [Time parameters](#time-parameters) - [Max operations per block](#max-operations-per-block) - [Size parameters](#size-parameters) - [Reward and penalty quotients](#reward-and-penalty-quotients) - [Data structures](#data-structures) - - [Extended types](#extended-types) + - [Modified types](#modified-types) - [`Validator`](#validator) - [`BeaconBlockBody`](#beaconblockbody) - [`BeaconState`](#beaconstate) @@ -51,73 +48,71 @@ - [Handling of reveal deadlines](#handling-of-reveal-deadlines) - [Final updates](#final-updates) - - - + ## Introduction -This document details the beacon chain additions and changes of to support the shard data custody game, -building upon the [Sharding](../sharding/beacon-chain.md) specification. +This document details the beacon chain additions and changes of to support the +shard data custody game, building upon the +[Sharding](../sharding/beacon-chain.md) specification. ## Constants ### Misc -| Name | Value | Unit | -| - | - | - | -| `CUSTODY_PRIME` | `int(2 ** 256 - 189)` | - | -| `CUSTODY_SECRETS` | `uint64(3)` | - | -| `BYTES_PER_CUSTODY_ATOM` | `uint64(32)` | bytes | -| `CUSTODY_PROBABILITY_EXPONENT` | `uint64(10)` | - | +| Name | Value | Unit | +| ------------------------------ | --------------------- | ----- | +| `CUSTODY_PRIME` | `int(2 ** 256 - 189)` | - | +| `CUSTODY_SECRETS` | `uint64(3)` | - | +| `BYTES_PER_CUSTODY_ATOM` | `uint64(32)` | bytes | +| `CUSTODY_PROBABILITY_EXPONENT` | `uint64(10)` | - | ### Domain types -| Name | Value | -| - | - | +| Name | Value | +| ----------------------------- | -------------------------- | | `DOMAIN_CUSTODY_BIT_SLASHING` | `DomainType('0x83000000')` | ## Preset ### Time parameters -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `RANDAO_PENALTY_EPOCHS` | `uint64(2**1)` (= 2) | epochs | 12.8 minutes | -| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `uint64(2**15)` (= 32,768) | epochs | ~146 days | -| `EPOCHS_PER_CUSTODY_PERIOD` | `uint64(2**14)` (= 16,384) | epochs | ~73 days | -| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `uint64(2**11)` (= 2,048) | epochs | ~9 days | -| `MAX_CHUNK_CHALLENGE_DELAY` | `uint64(2**15)` (= 32,768) | epochs | ~146 days | +| Name | Value | Unit | Duration | +| ------------------------------------------------ | -------------------------- | :----: | :----------: | +| `RANDAO_PENALTY_EPOCHS` | `uint64(2**1)` (= 2) | epochs | 12.8 minutes | +| `EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS` | `uint64(2**15)` (= 32,768) | epochs | ~146 days | +| `EPOCHS_PER_CUSTODY_PERIOD` | `uint64(2**14)` (= 16,384) | epochs | ~73 days | +| `CUSTODY_PERIOD_TO_RANDAO_PADDING` | `uint64(2**11)` (= 2,048) | epochs | ~9 days | +| `MAX_CHUNK_CHALLENGE_DELAY` | `uint64(2**15)` (= 32,768) | epochs | ~146 days | ### Max operations per block -| Name | Value | -| - | - | -| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `uint64(2**20)` (= 1,048,576) | -| `MAX_CUSTODY_KEY_REVEALS` | `uint64(2**8)` (= 256) | -| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `uint64(2**0)` (= 1) | -| `MAX_CUSTODY_CHUNK_CHALLENGES` | `uint64(2**2)` (= 4) | -| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `uint64(2**4)` (= 16) | -| `MAX_CUSTODY_SLASHINGS` | `uint64(2**0)` (= 1) | - +| Name | Value | +| --------------------------------------- | ----------------------------- | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS` | `uint64(2**20)` (= 1,048,576) | +| `MAX_CUSTODY_KEY_REVEALS` | `uint64(2**8)` (= 256) | +| `MAX_EARLY_DERIVED_SECRET_REVEALS` | `uint64(2**0)` (= 1) | +| `MAX_CUSTODY_CHUNK_CHALLENGES` | `uint64(2**2)` (= 4) | +| `MAX_CUSTODY_CHUNK_CHALLENGE_RESPONSES` | `uint64(2**4)` (= 16) | +| `MAX_CUSTODY_SLASHINGS` | `uint64(2**0)` (= 1) | ### Size parameters -| Name | Value | Unit | -| - | - | - | -| `BYTES_PER_CUSTODY_CHUNK` | `uint64(2**12)` (= 4,096) | bytes | -| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | +| Name | Value | Unit | +| ------------------------- | ----------------------------------------------------------- | ----- | +| `BYTES_PER_CUSTODY_CHUNK` | `uint64(2**12)` (= 4,096) | bytes | +| `CUSTODY_RESPONSE_DEPTH` | `ceillog2(MAX_SHARD_BLOCK_SIZE // BYTES_PER_CUSTODY_CHUNK)` | - | ### Reward and penalty quotients -| Name | Value | -| - | - | -| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `uint64(2**1)` (= 2) | -| `MINOR_REWARD_QUOTIENT` | `uint64(2**8)` (= 256) | +| Name | Value | +| -------------------------------------------------- | ---------------------- | +| `EARLY_DERIVED_SECRET_REVEAL_SLOT_REWARD_MULTIPLE` | `uint64(2**1)` (= 2) | +| `MINOR_REWARD_QUOTIENT` | `uint64(2**8)` (= 256) | ## Data structures -### Extended types +### Modified types #### `Validator` @@ -150,9 +145,13 @@ class BeaconBlockBody(sharding.BeaconBlockBody): class BeaconState(sharding.BeaconState): # Future derived secrets already exposed; contains the indices of the exposed validator # at RANDAO reveal period % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS - exposed_derived_secrets: Vector[List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH], - EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] - custody_chunk_challenge_records: List[CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS] + exposed_derived_secrets: Vector[ + List[ValidatorIndex, MAX_EARLY_DERIVED_SECRET_REVEALS * SLOTS_PER_EPOCH], + EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, + ] + custody_chunk_challenge_records: List[ + CustodyChunkChallengeRecord, MAX_CUSTODY_CHUNK_CHALLENGE_RECORDS + ] custody_chunk_challenge_index: uint64 ``` @@ -225,7 +224,9 @@ class CustodyKeyReveal(Container): #### `EarlyDerivedSecretReveal` -Represents an early (punishable) reveal of one of the derived secrets, where derived secrets are RANDAO reveals and custody reveals (both are part of the same domain). +Represents an early (punishable) reveal of one of the derived secrets, where +derived secrets are RANDAO reveals and custody reveals (both are part of the +same domain). ```python class EarlyDerivedSecretReveal(Container): @@ -257,7 +258,9 @@ def replace_empty_or_append(l: List, new_element: Any) -> int: ### `legendre_bit` -Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this. +Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. +`((a/q) + 1) // 2`). In a production implementation, a well-optimized library +(e.g. GMP) should be used for this. ```python def legendre_bit(a: int, q: int) -> int: @@ -265,7 +268,7 @@ def legendre_bit(a: int, q: int) -> int: return legendre_bit(a % q, q) if a == 0: return 0 - assert(q > a > 0 and q % 2 == 1) + assert q > a > 0 and q % 2 == 1 t = 1 n = q while a != 0: @@ -286,15 +289,17 @@ def legendre_bit(a: int, q: int) -> int: ### `get_custody_atoms` -Given one set of data, return the custody atoms: each atom will be combined with one legendre bit. +Given one set of data, return the custody atoms: each atom will be combined with +one legendre bit. ```python def get_custody_atoms(bytez: bytes) -> Sequence[bytes]: length_remainder = len(bytez) % BYTES_PER_CUSTODY_ATOM - bytez += b'\x00' * ((BYTES_PER_CUSTODY_ATOM - length_remainder) % BYTES_PER_CUSTODY_ATOM) # right-padding + bytez += b"\x00" * ( + (BYTES_PER_CUSTODY_ATOM - length_remainder) % BYTES_PER_CUSTODY_ATOM + ) # right-padding return [ - bytez[i:i + BYTES_PER_CUSTODY_ATOM] - for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM) + bytez[i : i + BYTES_PER_CUSTODY_ATOM] for i in range(0, len(bytez), BYTES_PER_CUSTODY_ATOM) ] ``` @@ -307,8 +312,10 @@ def get_custody_secrets(key: BLSSignature) -> Sequence[int]: full_G2_element = bls.signature_to_G2(key) signature = full_G2_element[0].coeffs signature_bytes = b"".join(x.to_bytes(48, "little") for x in signature) - secrets = [int.from_bytes(signature_bytes[i:i + BYTES_PER_CUSTODY_ATOM], "little") - for i in range(0, len(signature_bytes), 32)] + secrets = [ + int.from_bytes(signature_bytes[i : i + BYTES_PER_CUSTODY_ATOM], "little") + for i in range(0, len(signature_bytes), 32) + ] return secrets ``` @@ -319,9 +326,10 @@ def universal_hash_function(data_chunks: Sequence[bytes], secrets: Sequence[int] n = len(data_chunks) return ( sum( - secrets[i % CUSTODY_SECRETS]**i * int.from_bytes(atom, "little") % CUSTODY_PRIME + secrets[i % CUSTODY_SECRETS] ** i * int.from_bytes(atom, "little") % CUSTODY_PRIME for i, atom in enumerate(data_chunks) - ) + secrets[n % CUSTODY_SECRETS]**n + ) + + secrets[n % CUSTODY_SECRETS] ** n ) % CUSTODY_PRIME ``` @@ -332,7 +340,10 @@ def compute_custody_bit(key: BLSSignature, data: ByteList) -> bit: custody_atoms = get_custody_atoms(data) secrets = get_custody_secrets(key) uhf = universal_hash_function(custody_atoms, secrets) - legendre_bits = [legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) for i in range(CUSTODY_PROBABILITY_EXPONENT)] + legendre_bits = [ + legendre_bit(uhf + secrets[0] + i, CUSTODY_PRIME) + for i in range(CUSTODY_PROBABILITY_EXPONENT) + ] return bit(all(legendre_bits)) ``` @@ -340,7 +351,9 @@ def compute_custody_bit(key: BLSSignature, data: ByteList) -> bit: ```python def get_randao_epoch_for_custody_period(period: uint64, validator_index: ValidatorIndex) -> Epoch: - next_period_start = (period + 1) * EPOCHS_PER_CUSTODY_PERIOD - validator_index % EPOCHS_PER_CUSTODY_PERIOD + next_period_start = ( + period + 1 + ) * EPOCHS_PER_CUSTODY_PERIOD - validator_index % EPOCHS_PER_CUSTODY_PERIOD return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING) ``` @@ -348,13 +361,12 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat ```python def get_custody_period_for_validator(validator_index: ValidatorIndex, epoch: Epoch) -> uint64: - ''' + """ Return the reveal period for a given validator. - ''' + """ return (epoch + validator_index % EPOCHS_PER_CUSTODY_PERIOD) // EPOCHS_PER_CUSTODY_PERIOD ``` - ## Per-block processing ### Block processing @@ -389,9 +401,13 @@ def process_custody_game_operations(state: BeaconState, body: BeaconBlockBody) - ```python def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge) -> None: # Verify the attestation - assert is_valid_indexed_attestation(state, get_indexed_attestation(state, challenge.attestation)) + assert is_valid_indexed_attestation( + state, get_indexed_attestation(state, challenge.attestation) + ) # Verify it is not too late to challenge the attestation - max_attestation_challenge_epoch = Epoch(challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY) + max_attestation_challenge_epoch = Epoch( + challenge.attestation.data.target.epoch + MAX_CHUNK_CHALLENGE_DELAY + ) assert get_current_epoch(state) <= max_attestation_challenge_epoch # Verify it is not too late to challenge the responder responder = state.validators[challenge.responder_index] @@ -400,20 +416,22 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge # Verify responder is slashable assert is_slashable_validator(responder, get_current_epoch(state)) # Verify the responder participated in the attestation - attesters = get_attesting_indices(state, challenge.attestation.data, challenge.attestation.aggregation_bits) + attesters = get_attesting_indices(state, challenge) assert challenge.responder_index in attesters # Verify shard transition is correctly given - assert hash_tree_root(challenge.shard_transition) == challenge.attestation.data.shard_transition_root + assert ( + hash_tree_root(challenge.shard_transition) + == challenge.attestation.data.shard_transition_root + ) data_root = challenge.shard_transition.shard_data_roots[challenge.data_index] # Verify the challenge is not a duplicate for record in state.custody_chunk_challenge_records: - assert ( - record.data_root != data_root or - record.chunk_index != challenge.chunk_index - ) + assert record.data_root != data_root or record.chunk_index != challenge.chunk_index # Verify depth shard_block_length = challenge.shard_transition.shard_block_lengths[challenge.data_index] - transition_chunks = (shard_block_length + BYTES_PER_CUSTODY_CHUNK - 1) // BYTES_PER_CUSTODY_CHUNK + transition_chunks = ( + shard_block_length + BYTES_PER_CUSTODY_CHUNK - 1 + ) // BYTES_PER_CUSTODY_CHUNK assert challenge.chunk_index < transition_chunks # Add new chunk challenge record new_record = CustodyChunkChallengeRecord( @@ -434,11 +452,11 @@ def process_chunk_challenge(state: BeaconState, challenge: CustodyChunkChallenge #### Custody chunk response ```python -def process_chunk_challenge_response(state: BeaconState, - response: CustodyChunkResponse) -> None: +def process_chunk_challenge_response(state: BeaconState, response: CustodyChunkResponse) -> None: # Get matching challenge (if any) from records matching_challenges = [ - record for record in state.custody_chunk_challenge_records + record + for record in state.custody_chunk_challenge_records if record.challenge_index == response.challenge_index ] assert len(matching_challenges) == 1 @@ -458,7 +476,9 @@ def process_chunk_challenge_response(state: BeaconState, state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() # Reward the proposer proposer_index = get_beacon_proposer_index(state) - increase_balance(state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT)) + increase_balance( + state, proposer_index, Gwei(get_base_reward(state, proposer_index) // MINOR_REWARD_QUOTIENT) + ) ``` #### Custody key reveals @@ -470,9 +490,13 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> Note that this function mutates ``state``. """ revealer = state.validators[reveal.revealer_index] - epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index) + epoch_to_sign = get_randao_epoch_for_custody_period( + revealer.next_custody_secret_to_reveal, reveal.revealer_index + ) - custody_reveal_period = get_custody_period_for_validator(reveal.revealer_index, get_current_epoch(state)) + custody_reveal_period = get_custody_period_for_validator( + reveal.revealer_index, get_current_epoch(state) + ) # Only past custody periods can be revealed, except after exiting the exit period can be revealed is_past_reveal = revealer.next_custody_secret_to_reveal < custody_reveal_period is_exited = revealer.exit_epoch <= get_current_epoch(state) @@ -500,14 +524,16 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) -> increase_balance( state, proposer_index, - Gwei(get_base_reward(state, reveal.revealer_index) // MINOR_REWARD_QUOTIENT) + Gwei(get_base_reward(state, reveal.revealer_index) // MINOR_REWARD_QUOTIENT), ) ``` #### Early derived secret reveals ```python -def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerivedSecretReveal) -> None: +def process_early_derived_secret_reveal( + state: BeaconState, reveal: EarlyDerivedSecretReveal +) -> None: """ Process ``EarlyDerivedSecretReveal`` operation. Note that this function mutates ``state``. @@ -525,7 +551,9 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived pubkeys = [revealed_validator.pubkey, masker.pubkey] domain = get_domain(state, DOMAIN_RANDAO, reveal.epoch) - signing_roots = [compute_signing_root(root, domain) for root in [hash_tree_root(reveal.epoch), reveal.mask]] + signing_roots = [ + compute_signing_root(root, domain) for root in [hash_tree_root(reveal.epoch), reveal.mask] + ] assert bls.AggregateVerify(pubkeys, signing_roots, reveal.reveal) if reveal.epoch >= get_current_epoch(state) + CUSTODY_PERIOD_TO_RANDAO_PADDING: @@ -566,13 +594,15 @@ def process_early_derived_secret_reveal(state: BeaconState, reveal: EarlyDerived #### Custody Slashings ```python -def process_custody_slashing(state: BeaconState, signed_custody_slashing: SignedCustodySlashing) -> None: +def process_custody_slashing( + state: BeaconState, signed_custody_slashing: SignedCustodySlashing +) -> None: custody_slashing = signed_custody_slashing.message attestation = custody_slashing.attestation # 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) @@ -590,15 +620,23 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed shard_transition = custody_slashing.shard_transition assert hash_tree_root(shard_transition) == attestation.data.shard_transition_root # Verify that the provided data matches the shard-transition - assert len(custody_slashing.data) == shard_transition.shard_block_lengths[custody_slashing.data_index] - assert hash_tree_root(custody_slashing.data) == shard_transition.shard_data_roots[custody_slashing.data_index] + assert ( + len(custody_slashing.data) + == shard_transition.shard_block_lengths[custody_slashing.data_index] + ) + assert ( + hash_tree_root(custody_slashing.data) + == shard_transition.shard_data_roots[custody_slashing.data_index] + ) # Verify existence and participation of claimed malefactor - attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + 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), + get_custody_period_for_validator( + custody_slashing.malefactor_index, attestation.data.target.epoch + ), custody_slashing.malefactor_index, ) domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) @@ -606,7 +644,9 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed assert bls.Verify(malefactor.pubkey, signing_root, custody_slashing.malefactor_secret) # Compute the custody bit - computed_custody_bit = compute_custody_bit(custody_slashing.malefactor_secret, custody_slashing.data) + computed_custody_bit = compute_custody_bit( + custody_slashing.malefactor_secret, custody_slashing.data + ) # Verify the claim if computed_custody_bit == 1: @@ -614,11 +654,13 @@ def process_custody_slashing(state: BeaconState, signed_custody_slashing: Signed slash_validator(state, custody_slashing.malefactor_index) committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) others_count = len(committee) - 1 - whistleblower_reward = Gwei(malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count) + whistleblower_reward = Gwei( + malefactor.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT // others_count + ) 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 whistleblower 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) @@ -675,8 +717,15 @@ def process_reveal_deadlines(state: BeaconState) -> None: ```python def process_challenge_deadlines(state: BeaconState) -> None: for custody_chunk_challenge in state.custody_chunk_challenge_records: - if get_current_epoch(state) > custody_chunk_challenge.inclusion_epoch + EPOCHS_PER_CUSTODY_PERIOD: - slash_validator(state, custody_chunk_challenge.responder_index, custody_chunk_challenge.challenger_index) + if ( + get_current_epoch(state) + > custody_chunk_challenge.inclusion_epoch + EPOCHS_PER_CUSTODY_PERIOD + ): + slash_validator( + state, + custody_chunk_challenge.responder_index, + custody_chunk_challenge.challenger_index, + ) index_in_records = state.custody_chunk_challenge_records.index(custody_chunk_challenge) state.custody_chunk_challenge_records[index_in_records] = CustodyChunkChallengeRecord() ``` @@ -686,21 +735,32 @@ def process_challenge_deadlines(state: BeaconState) -> None: ```python def process_custody_final_updates(state: BeaconState) -> None: # Clean up exposed RANDAO key reveals - state.exposed_derived_secrets[get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS] = [] + state.exposed_derived_secrets[ + get_current_epoch(state) % EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS + ] = [] # Reset withdrawable epochs if challenge records are empty records = state.custody_chunk_challenge_records - validator_indices_in_records = set(record.responder_index for record in records) # non-duplicate + validator_indices_in_records = set( + record.responder_index for record in records + ) # non-duplicate for index, validator in enumerate(state.validators): if validator.exit_epoch != FAR_FUTURE_EPOCH: - not_all_secrets_are_revealed = validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH - if ValidatorIndex(index) in validator_indices_in_records or not_all_secrets_are_revealed: + not_all_secrets_are_revealed = ( + validator.all_custody_secrets_revealed_epoch == FAR_FUTURE_EPOCH + ) + if ( + ValidatorIndex(index) in validator_indices_in_records + or not_all_secrets_are_revealed + ): # Delay withdrawable epochs if challenge records are not empty or not all # custody secrets revealed validator.withdrawable_epoch = FAR_FUTURE_EPOCH else: # Reset withdrawable epochs if challenge records are empty and all secrets are revealed if validator.withdrawable_epoch == FAR_FUTURE_EPOCH: - validator.withdrawable_epoch = Epoch(validator.all_custody_secrets_revealed_epoch - + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) + validator.withdrawable_epoch = Epoch( + validator.all_custody_secrets_revealed_epoch + + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) ``` diff --git a/specs/_deprecated/custody_game/validator.md b/specs/_deprecated/custody_game/validator.md new file mode 100644 index 0000000000..f2f51ea5b0 --- /dev/null +++ b/specs/_deprecated/custody_game/validator.md @@ -0,0 +1,110 @@ +# Custody Game -- Honest Validator + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Becoming a validator](#becoming-a-validator) +- [Beacon chain validator assignments](#beacon-chain-validator-assignments) + - [Custody slashings](#custody-slashings) + - [Custody key reveals](#custody-key-reveals) + - [Early derived secret reveals](#early-derived-secret-reveals) + - [Construct attestation](#construct-attestation) +- [How to avoid slashing](#how-to-avoid-slashing) + - [Custody slashing](#custody-slashing) + + + +## Introduction + +This is an accompanying document to +[Custody Game -- The Beacon Chain](./beacon-chain.md), which describes the +expected actions of a "validator" participating in the shard data Custody Game. + +## Prerequisites + +This document is an extension of the +[Sharding -- Validator](../sharding/validator.md). All behaviors and definitions +defined in the Sharding doc carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +[Custody Game -- The Beacon Chain](./beacon-chain.md) docs are requisite for +this document and used throughout. Please see the Custody Game docs before +continuing and use them as a reference throughout. + +## Becoming a validator + +Becoming a validator in Custody Game is unchanged from Phase 0. See the +[Phase 0 validator guide](../../phase0/validator.md#becoming-a-validator) for +details. + +## Beacon chain validator assignments + +Beacon chain validator assignments to beacon committees and beacon block +proposal are unchanged from Phase 0. See the +[Phase 0 validator guide](../../phase0/validator.md#validator-assignments) for +details. + +##### Custody slashings + +Up to `MAX_CUSTODY_SLASHINGS`, +[`CustodySlashing`](./beacon-chain.md#custodyslashing) objects can be included +in the `block`. The custody slashings must satisfy the verification conditions +found in [custody slashings processing](beacon-chain.md#custody-slashings). The +validator receives a small "whistleblower" reward for each custody slashing +included (THIS IS NOT CURRENTLY THE CASE BUT PROBABLY SHOULD BE). + +##### Custody key reveals + +Up to `MAX_CUSTODY_KEY_REVEALS`, +[`CustodyKeyReveal`](./beacon-chain.md#custodykeyreveal) objects can be included +in the `block`. The custody key reveals must satisfy the verification conditions +found in [custody key reveal processing](beacon-chain.md#custody-key-reveals). +The validator receives a small reward for each custody key reveal included. + +##### Early derived secret reveals + +Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, +[`EarlyDerivedSecretReveal`](./beacon-chain.md#earlyderivedsecretreveal) objects +can be included in the `block`. The early derived secret reveals must satisfy +the verification conditions found in +[early derived secret reveal processing](beacon-chain.md#custody-key-reveals). +The validator receives a small "whistleblower" reward for each early derived +secret reveal included. + +#### Construct attestation + +`attestation.data`, `attestation.aggregation_bits`, and `attestation.signature` +are unchanged from Phase 0. But safety/validity in signing the message is +premised upon calculation of the "custody bit" [TODO]. + +## How to avoid slashing + +Proposer and Attester slashings described in Phase 0 remain in place with the +addition of the following. + +### Custody slashing + +To avoid custody slashings, the attester must never sign any shard transition +for which the custody bit is one. The custody bit is computed using the custody +secret: + +```python +def get_custody_secret( + state: BeaconState, validator_index: ValidatorIndex, privkey: int, epoch: Epoch = None +) -> BLSSignature: + if epoch is None: + epoch = get_current_epoch(state) + period = get_custody_period_for_validator(validator_index, epoch) + epoch_to_sign = get_randao_epoch_for_custody_period(period, validator_index) + domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) + signing_root = compute_signing_root(Epoch(epoch_to_sign), domain) + return bls.Sign(privkey, signing_root) +``` + +Note that the valid custody secret is always the one for the **attestation +target epoch**, not to be confused with the epoch in which the shard block was +generated. While they are the same most of the time, getting this wrong at +custody epoch boundaries would result in a custody slashing. diff --git a/specs/das/das-core.md b/specs/_deprecated/das/das-core.md similarity index 72% rename from specs/das/das-core.md rename to specs/_deprecated/das/das-core.md index f683cbbe13..c030cd38ca 100644 --- a/specs/das/das-core.md +++ b/specs/_deprecated/das/das-core.md @@ -1,12 +1,8 @@ # Data Availability Sampling -- Core -**Notice**: This document is a work-in-progress for researchers and implementers. +*Note*: This document is a work-in-progress for researchers and implementers. -## Table of contents - - - - + - [Custom types](#custom-types) - [Configuration](#configuration) @@ -21,28 +17,24 @@ - [Data recovery](#data-recovery) - [DAS functions](#das-functions) - - - + ## Custom types We define the following Python custom types for type hinting and readability: -| Name | SSZ equivalent | Description | -| - | - | - | -| `SampleIndex` | `uint64` | A sample index, corresponding to chunk of extended data | - +| Name | SSZ equivalent | Description | +| ------------- | -------------- | ------------------------------------------------------- | +| `SampleIndex` | `uint64` | A sample index, corresponding to chunk of extended data | ## Configuration ### Misc -| Name | Value | Notes | -| - | - | - | +| Name | Value | Notes | +| ------------------- | --------------- | ----------------------------------------------------------------- | | `MAX_RESAMPLE_TIME` | `TODO` (= TODO) | Time window to sample a shard blob and put it on vertical subnets | - ## New containers ### `DASSample` @@ -68,7 +60,7 @@ def reverse_bit_order(n: int, order: int): Reverse the bit order of an integer n """ assert is_power_of_two(order) - return int(('{:0' + str(order.bit_length() - 1) + 'b}').format(n)[::-1], 2) + return int(("{:0" + str(order.bit_length() - 1) + "b}").format(n)[::-1], 2) ``` #### `reverse_bit_order_list` @@ -83,6 +75,7 @@ def reverse_bit_order_list(elements: Sequence[int]) -> Sequence[int]: ### Data extension Implementations: + - [Python](https://github.com/protolambda/partial_fft/blob/master/das_fft.py) - [Go](https://github.com/protolambda/go-kate/blob/master/das_extension.go) @@ -93,20 +86,22 @@ def das_fft_extension(data: Sequence[Point]) -> Sequence[Point]: such that the second output half of the IFFT is all zeroes. """ poly = inverse_fft(data) - return fft(poly + [0]*len(poly))[1::2] + return fft(poly + [0] * len(poly))[1::2] ``` ### Data recovery -See [Reed-Solomon erasure code recovery in n*log^2(n) time with FFTs](https://ethresear.ch/t/reed-solomon-erasure-code-recovery-in-n-log-2-n-time-with-ffts/3039) for theory. -Implementations: +See +[Reed-Solomon erasure code recovery in `n*log^2(n)` time with FFTs](https://ethresear.ch/t/reed-solomon-erasure-code-recovery-in-n-log-2-n-time-with-ffts/3039) +for theory. Implementations: + - [Original Python](https://github.com/ethereum/research/blob/master/mimc_stark/recovery.py) - [New optimized approach in python](https://github.com/ethereum/research/tree/master/polynomial_reconstruction) -- [Old approach in Go](https://github.com/protolambda/go-kate/blob/master/recovery.go) +- [Old approach in Go](https://github.com/protolambda/go-kzg/blob/master/legacy_recovery.go) ```python def recover_data(data: Sequence[Optional[Sequence[Point]]]) -> Sequence[Point]: - """Given an a subset of half or more of subgroup-aligned ranges of values, recover the None values.""" + """Given a subset of half or more of subgroup-aligned ranges of values, recover the None values.""" ... ``` @@ -124,11 +119,13 @@ def extend_data(data: Sequence[Point]) -> Sequence[Point]: ```python def unextend_data(extended_data: Sequence[Point]) -> Sequence[Point]: - return extended_data[:len(extended_data)//2] + return extended_data[: len(extended_data) // 2] ``` ```python -def check_multi_kzg_proof(commitment: BLSCommitment, proof: BLSCommitment, x: Point, ys: Sequence[Point]) -> bool: +def check_multi_kzg_proof( + commitment: BLSCommitment, proof: BLSCommitment, x: Point, ys: Sequence[Point] +) -> bool: """ Run a KZG multi-proof check to verify that for the subgroup starting at x, the proof indeed complements the ys to match the commitment. @@ -142,12 +139,12 @@ def construct_proofs(extended_data_as_poly: Sequence[Point]) -> Sequence[BLSComm Constructs proofs for samples of extended data (in polynomial form, 2nd half being zeroes). Use the FK20 multi-proof approach to construct proofs for a chunk length of POINTS_PER_SAMPLE. """ - ... # Omitted for now, refer to KZG implementation resources. + ... # Omitted for now, refer to KZG implementation resources. ``` ```python def commit_to_data(data_as_poly: Sequence[Point]) -> BLSCommitment: - """Commit to a polynomial by """ + """Commit to a polynomial by""" ``` ```python @@ -156,7 +153,7 @@ def sample_data(slot: Slot, shard: Shard, extended_data: Sequence[Point]) -> Seq assert sample_count <= MAX_SAMPLES_PER_BLOCK # get polynomial form of full extended data, second half will be all zeroes. poly = ifft(reverse_bit_order_list(extended_data)) - assert all(v == 0 for v in poly[len(poly)//2:]) + assert all(v == 0 for v in poly[len(poly) // 2 :]) proofs = construct_proofs(poly) return [ DASSample( @@ -168,15 +165,17 @@ def sample_data(slot: Slot, shard: Shard, extended_data: Sequence[Point]) -> Seq proof=proofs[reverse_bit_order(i, sample_count)], # note: we leave the sample data as-is so it matches the original nicely. # The proof applies to `ys = reverse_bit_order_list(sample.data)` - data=extended_data[i*POINTS_PER_SAMPLE:(i+1)*POINTS_PER_SAMPLE] - ) for i in range(sample_count) + data=extended_data[i * POINTS_PER_SAMPLE : (i + 1) * POINTS_PER_SAMPLE], + ) + for i in range(sample_count) ] ``` ```python def verify_sample(sample: DASSample, sample_count: uint64, commitment: BLSCommitment): domain_pos = reverse_bit_order(sample.index, sample_count) - sample_root_of_unity = ROOT_OF_UNITY**MAX_SAMPLES_PER_BLOCK # change point-level to sample-level domain + # Change point-level to sample-level domain + sample_root_of_unity = ROOT_OF_UNITY**MAX_SAMPLES_PER_BLOCK x = sample_root_of_unity**domain_pos ys = reverse_bit_order_list(sample.data) assert check_multi_kzg_proof(commitment, sample.proof, x, ys) @@ -185,6 +184,8 @@ def verify_sample(sample: DASSample, sample_count: uint64, commitment: BLSCommit ```python def reconstruct_extended_data(samples: Sequence[Optional[DASSample]]) -> Sequence[Point]: # Instead of recovering with a point-by-point approach, recover the samples by recovering missing subgroups. - subgroups = [None if sample is None else reverse_bit_order_list(sample.data) for sample in samples] + subgroups = [ + None if sample is None else reverse_bit_order_list(sample.data) for sample in samples + ] return recover_data(subgroups) ``` diff --git a/specs/das/fork-choice.md b/specs/_deprecated/das/fork-choice.md similarity index 52% rename from specs/das/fork-choice.md rename to specs/_deprecated/das/fork-choice.md index f8ee68eabe..1c7a61b8c2 100644 --- a/specs/das/fork-choice.md +++ b/specs/_deprecated/das/fork-choice.md @@ -1,25 +1,23 @@ # Data Availability Sampling -- Fork Choice -**Notice**: This document is a work-in-progress for researchers and implementers. +*Note*: This document is a work-in-progress for researchers and implementers. -## Table of contents - - - - + - [Introduction](#introduction) - [Dependency calculation](#dependency-calculation) - - - + ## Introduction -This document is the beacon chain fork choice spec for Data Availability Sampling. The only change that we add from phase 0 is that we add a concept of "data dependencies"; -a block is only eligible for consideration in the fork choice after a data availability test has been successfully completed for all dependencies. -The "root" of a shard block for data dependency purposes is considered to be a `DataCommitment` object, which is a pair of a Kate commitment and a length. +This document is the beacon chain fork choice spec for Data Availability +Sampling. The only change that we add from phase 0 is that we add a concept of +"data dependencies"; a block is only eligible for consideration in the fork +choice after a data availability test has been successfully completed for all +dependencies. The "root" of a shard block for data dependency purposes is +considered to be a `DataCommitment` object, which is a pair of a Kate commitment +and a length. ## Dependency calculation @@ -27,11 +25,18 @@ The "root" of a shard block for data dependency purposes is considered to be a ` def get_new_dependencies(state: BeaconState) -> Set[DataCommitment]: return set( # Already confirmed during this epoch - [c.commitment for c in state.current_epoch_pending_headers if c.confirmed] + + [c.commitment for c in state.current_epoch_pending_headers if c.confirmed] + + # Already confirmed during previous epoch - [c.commitment for c in state.previous_epoch_pending_headers if c.confirmed] + + [c.commitment for c in state.previous_epoch_pending_headers if c.confirmed] + + # Confirmed in the epoch before the previous - [c for c in shard for shard in state.grandparent_epoch_confirmed_commitments if c != DataCommitment()] + [ + c + for c in shard + for shard in state.grandparent_epoch_confirmed_commitments + if c != DataCommitment() + ] ) ``` diff --git a/specs/_deprecated/das/p2p-interface.md b/specs/_deprecated/das/p2p-interface.md new file mode 100644 index 0000000000..4a8bdb9a05 --- /dev/null +++ b/specs/_deprecated/das/p2p-interface.md @@ -0,0 +1,276 @@ +# Data Availability Sampling -- Networking + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [DAS Subnets](#das-subnets) + - [Horizontal subnets](#horizontal-subnets) + - [Publishing](#publishing) + - [Horizontal propagation](#horizontal-propagation) + - [Horizontal to vertical](#horizontal-to-vertical) + - [Vertical subnets](#vertical-subnets) + - [Slow rotation: Backbone](#slow-rotation-backbone) + - [Quick Rotation: Sampling](#quick-rotation-sampling) +- [DAS in the Gossip domain: Push](#das-in-the-gossip-domain-push) + - [Topics and messages](#topics-and-messages) + - [Horizontal subnets: `shard_blob_{shard}`](#horizontal-subnets-shard_blob_shard) + - [Vertical subnets: `das_sample_{subnet_index}`](#vertical-subnets-das_sample_subnet_index) +- [DAS in the Req-Resp domain: Pull](#das-in-the-req-resp-domain-pull) + - [Messages](#messages) + - [DASQuery](#dasquery) + + + +## 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. + +For sampling, all nodes need to query for `k` random samples each slot. + +*__TODO__: describe big picture of sampling workload size* + +This is a lot of work, and ideally happens at a low latency. + +To achieve quick querying, the query model is changed to *push* the samples to +listeners instead, using GossipSub. The listeners then randomly rotate their +subscriptions to keep queries unpredictable. Except for a small subset of +subscriptions, which will function as a backbone to keep topics more stable and +allow for efficient peer discovery. + +Publishing can utilize the fan-out functionality in GossipSub, and is easier to +split between nodes: nodes on the horizontal networks can help by producing the +same samples and fan-out publishing to their own peers. + +This push model also helps to obfuscate the original source of a message: the +listeners do not have to make individual queries to some identified source. + +The push model does not aim to serve "historical" queries (anything older than +the most recent). 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, 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. +- Sinks: continuously look for samples + +At full operation, the network has one proposer, per shard, per slot. + +In the push-model, there are: + +- *Vertical subnets*: Sinks can subscribe to indices of samples: there is a + sample to subnet mapping. +- *Horizontal subnets*: Sources need to distribute samples to all vertical + networks: they participate in a fan-out layer. + +### Horizontal subnets + +The shift of the distribution responsibility to a proposer can only be achieved +with amplification: a regular proposer cannot reach every vertical subnet. + +#### Publishing + +To publish their work, proposers propagate the shard block as a whole on a +shard-block subnet. + +The proposer can fan-out their work more aggressively, by using the fan-out +functionality of GossipSub: it may publish to all its peers on the subnet, +instead of just those in its mesh. + +#### Horizontal propagation + +Peers on the horizontal subnet are expected to at least perform regular +propagation of shard blocks, like participation in any other topic. + +*Although this may be sufficient for testnets, expect parameter changes in the +spec here.* + +#### Horizontal to vertical + +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, 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, +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, nodes +can divert to the pull-model, or ultimately flag it as unavailable data. + +Note that the vertical subnets are shared between the different shards, and a +simple hash function `(shard, slot, sample_index) -> subnet_index` defines which +samples go where. This is to evenly distribute samples to subnets, even when one +shard has more activity than the other. + +TODO: define `(shard, slot, sample_index) -> subnet_index` hash function. + +#### Slow rotation: Backbone + +To allow for subscriptions to rotate quickly and randomly, a backbone is formed +to help onboard peers into other topics. + +This backbone is based on a pure function of the *node* identity and time: + +- Nodes can be found *without additional discovery overhead*: peers on a + vertical topic can be found by searching the local peerstore for identities + that hash to the desired topic(s), assuming the peerstore already has a large + enough variety of peers. +- Nodes can be held accountable for contributing to the backbone: peers that + participate in DAS but are not active on the appropriate backbone topics can + be scored down. *Note*: This is experimental, DAS should be light enough for + all participants to run, but scoring needs to undergo testing. + +A node should anticipate backbone topics to subscribe to based their own +identity. These subscriptions rotate slowly, and with different offsets per node +identity to avoid sudden network-wide rotations. + +```python +# TODO hash function: (node, time)->subnets +``` + +Backbone subscription work is outlined in the +[DAS participation spec](sampling.md#slow-rotation-backbone) + +#### Quick Rotation: Sampling + +A node MUST maintain `k` random subscriptions to topics, and rotate these +according to the [DAS participation spec](sampling.md#quick-rotation-sampling). +If the node does not already have connected peers on the topic it needs to +sample, it can search its peerstore and, if necessary, in the DHT for peers in +the topic backbone. + +## DAS in the Gossip domain: Push + +### Topics and messages + +Following the same scheme as the +[Phase0 gossip topics](../../phase0/p2p-interface.md#topics-and-messages), names +and payload types are: + +| Name | Message Type | +| --------------------------- | ------------ | +| `das_sample_{subnet_index}` | `DASSample` | + +Also see the [Sharding general networking spec](../sharding/p2p-interface.md) +for important topics such as that of the shard-blobs and shard-headers. + +#### Horizontal subnets: `shard_blob_{shard}` + +Extending the regular `shard_blob_{shard}` as +[defined in the Sharding networking specification](../sharding/p2p-interface.md#shard-blobs-shard_blob_shard) + +If participating in DAS, upon receiving a `signed_blob` for the first time with +a `slot` not older than `MAX_RESAMPLE_TIME`, a subscriber of a +`shard_blob_{shard}` SHOULD reconstruct the samples and publish them to vertical +subnets. Take `blob = signed_blob.blob`: + +1. Extend the data: `extended_data = extend_data(blob.data)` +2. Create samples with proofs: + `samples = sample_data(blob.slot, blob.shard, extended_data)` +3. Fanout-publish the samples to the vertical subnets of its peers (not all + vertical subnets may be reached). + +The [DAS participation spec](sampling.md#horizontal-subnets) outlines when and +where to participate in DAS on horizontal subnets. + +#### Vertical subnets: `das_sample_{subnet_index}` + +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. + +- _[IGNORE]_ The commitment for the (`sample.shard`, `sample.slot`, + `sample.index`) tuple must be known. If not known, the client MAY queue the + sample if it passes formatting conditions. +- _[REJECT]_ `sample.shard`, `sample.slot` and `sample.index` are hashed into a + `sbunet_index` (TODO: define hash) which MUST match the topic `{subnet_index}` + parameter. +- _[REJECT]_ `sample.shard` must be within valid range: + `0 <= sample.shard < get_active_shard_count(state, compute_epoch_at_slot(sample.slot))`. +- _[REJECT]_ `sample.index` must be within valid range: + `0 <= sample.index < sample_count`, where: + - `sample_count = (points_count + POINTS_PER_SAMPLE - 1) // POINTS_PER_SAMPLE` + - `points_count` is the length as claimed along with the commitment, which + must be smaller than `MAX_SAMPLES_PER_BLOCK`. +- _[IGNORE]_ The `sample` is not from a future slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `sample.slot <= current_slot`. A client MAY queue future samples for + processing at the appropriate slot if it passed formatting conditions. +- _[IGNORE]_ This is the first received sample with the (`sample.shard`, + `sample.slot`, `sample.index`) key tuple. +- _[REJECT]_ As already limited by the SSZ list-limit, it is important the + sample data is well-formatted and not too large. +- _[REJECT]_ The `sample.data` MUST NOT contain any point `p >= MODULUS`. + Although it is a `uint256`, not the full 256 bit range is valid. +- _[REJECT]_ The `sample.proof` MUST be valid: + `verify_sample(sample, sample_count, commitment)` + +Upon receiving a valid sample, it SHOULD be retained for a buffer period if the +local node is part of the backbone that covers this sample. This is to serve +other peers that may have missed it. + +## DAS in the Req-Resp domain: Pull + +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). + +Note that DAS networking uses a different protocol prefix: `/eth2/das/req` + +### Messages + +#### DASQuery + +**Protocol ID:** `/eth2/das/req/query/1/` + +Request Content: + +``` +( + sample_index: SampleIndex +) +``` + +Response Content: + +``` +( + DASSample +) +``` + +When the sample is: + +- Available: respond with a `Success` result code, and the encoded sample. +- Expected to be available, but not: respond with a `ResourceUnavailable` result + code. +- Not available, but never of interest to the node: respond with an + `InvalidRequest` result code. + +When the node is part of the backbone and expected to have the sample, the +validity of the quest MUST be recognized with `Success` or +`ResourceUnavailable`. diff --git a/specs/das/sampling.md b/specs/_deprecated/das/sampling.md similarity index 57% rename from specs/das/sampling.md rename to specs/_deprecated/das/sampling.md index 53685c6509..f7d6a47a97 100644 --- a/specs/das/sampling.md +++ b/specs/_deprecated/das/sampling.md @@ -1,12 +1,8 @@ # Data Availability Sampling -- Sampling -**Notice**: This document is a work-in-progress for researchers and implementers. +*Note*: This document is a work-in-progress for researchers and implementers. -## Table of contents - - - - + - [Data Availability Sampling](#data-availability-sampling) - [GossipSub](#gossipsub) @@ -19,9 +15,7 @@ - [Stage 1: Pulling missing samples from known peers](#stage-1-pulling-missing-samples-from-known-peers) - [Stage 2: Pulling missing data from validators with custody.](#stage-2-pulling-missing-data-from-validators-with-custody) - - - + ## Data Availability Sampling @@ -45,40 +39,51 @@ TODO TODO - ### DAS during network instability -The GossipSub based retrieval of samples may not always work. -In such event, a node can move through below stages until it recovers data availability. +The GossipSub based retrieval of samples may not always work. In such event, a +node can move through below stages until it recovers data availability. #### Stage 0: Waiting on missing samples -Wait for the sample to re-broadcast. Someone may be slow with publishing, or someone else is able to do the work. +Wait for the sample to re-broadcast. Someone may be slow with publishing, or +someone else is able to do the work. Any node can do the following work to keep the network healthy: -- Common: Listen on a horizontal subnet, chunkify the block data in samples, and propagate the samples to vertical subnets. -- Extreme: Listen on enough vertical subnets, reconstruct the missing samples by recovery, and propagate the recovered samples. -This is not a requirement, but should improve the network stability with little resources, and without any central party. +- Common: Listen on a horizontal subnet, chunkify the block data in samples, and + propagate the samples to vertical subnets. +- Extreme: Listen on enough vertical subnets, reconstruct the missing samples by + recovery, and propagate the recovered samples. + +This is not a requirement, but should improve the network stability with little +resources, and without any central party. #### Stage 1: Pulling missing samples from known peers -The more realistic option, to execute when a sample is missing, is to query any node that is known to hold it. -Since *consensus identity is disconnected from network identity*, there is no direct way to contact custody holders -without explicitly asking for the data. +The more realistic option, to execute when a sample is missing, is to query any +node that is known to hold it. Since *consensus identity is disconnected from +network identity*, there is no direct way to contact custody holders without +explicitly asking for the data. -However, *network identities* are still used to build a backbone for each vertical subnet. -These nodes should have received the samples, and can serve a buffer of them on demand. -Although serving these is not directly incentivised, it is little work: -1. Buffer any message you see on the backbone vertical subnets, for a buffer of up to two weeks. -2. Serve the samples on request. An individual sample is just expected to be `~ 0.5 KB`, and does not require any pre-processing to serve. +However, *network identities* are still used to build a backbone for each +vertical subnet. These nodes should have received the samples, and can serve a +buffer of them on demand. Although serving these is not directly incentivised, +it is little work: -A validator SHOULD make a `DASQuery` request to random peers, until failing more than the configured failure-rate. +1. Buffer any message you see on the backbone vertical subnets, for a buffer of + up to two weeks. +2. Serve the samples on request. An individual sample is just expected to be + `~ 0.5 KB`, and does not require any pre-processing to serve. -TODO: detailed failure-mode spec. Stop after trying e.g. 3 peers for any sample in a configured time window (after the gossip period). +A validator SHOULD make a `DASQuery` request to random peers, until failing more +than the configured failure-rate. -#### Stage 2: Pulling missing data from validators with custody. +TODO: detailed failure-mode spec. Stop after trying e.g. 3 peers for any sample +in a configured time window (after the gossip period). -Pulling samples directly from nodes with validators that have a custody responsibility, -without revealing their identity to the network, is an open problem. +#### Stage 2: Pulling missing data from validators with custody. +Pulling samples directly from nodes with validators that have a custody +responsibility, without revealing their identity to the network, is an open +problem. diff --git a/specs/_deprecated/sharding/beacon-chain.md b/specs/_deprecated/sharding/beacon-chain.md new file mode 100644 index 0000000000..478fce0e49 --- /dev/null +++ b/specs/_deprecated/sharding/beacon-chain.md @@ -0,0 +1,458 @@ +# Sharding -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) + - [Glossary](#glossary) +- [Constants](#constants) + - [Misc](#misc) + - [Domain types](#domain-types) +- [Preset](#preset) + - [Misc](#misc-1) + - [Time parameters](#time-parameters) + - [Shard blob samples](#shard-blob-samples) +- [Configuration](#configuration) + - [Time parameters](#time-parameters-1) +- [Containers](#containers) + - [New Containers](#new-containers) + - [`BuilderBlockBid`](#builderblockbid) + - [`BuilderBlockBidWithRecipientAddress`](#builderblockbidwithrecipientaddress) + - [`ShardedCommitmentsContainer`](#shardedcommitmentscontainer) + - [`ShardSample`](#shardsample) + - [Modified containers](#modified-containers) + - [`BeaconState`](#beaconstate) + - [`BuilderBlockData`](#builderblockdata) + - [`BeaconBlockBody`](#beaconblockbody) +- [Helper functions](#helper-functions) + - [Block processing](#block-processing) + - [`is_builder_block_slot`](#is_builder_block_slot) + - [Beacon state accessors](#beacon-state-accessors) + - [`get_active_shard_count`](#get_active_shard_count) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing-1) + - [`process_block`](#process_block) + - [Block header](#block-header) + - [Builder Block Bid](#builder-block-bid) + - [Sharded data](#sharded-data) + - [Execution payload](#execution-payload) + + + +## Introduction + +This document describes the extensions made to the Phase 0 design of The Beacon +Chain to support data sharding, based on the ideas +[here](https://notes.ethereum.org/@dankrad/new_sharding) and more broadly +[here](https://arxiv.org/abs/1809.09044), using KZG10 commitments to commit to +data to remove any need for fraud proofs (and hence, safety-critical synchrony +assumptions) in the design. + +### Glossary + +- **Data**: A list of KZG points, to translate a byte string into +- **Blob**: Data with commitments and meta-data, like a flattened bundle of L2 + transactions. + +## Constants + +The following values are (non-configurable) constants used throughout the +specification. + +### Misc + +| Name | Value | Notes | +| --------------------------- | --------------------- | ------------------- | +| `FIELD_ELEMENTS_PER_SAMPLE` | `uint64(2**4)` (= 16) | 31 * 16 = 496 bytes | + +### Domain types + +| Name | Value | +| --------------------- | -------------------------- | +| `DOMAIN_SHARD_SAMPLE` | `DomainType('0x10000000')` | + +## Preset + +### Misc + +| Name | Value | Notes | +| -------------------------------------------- | ------------------------- | ---------------------------------------------------------------------------------- | +| `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 | + +### Time parameters + +With the introduction of builder blocks the number of slots per epoch is doubled +(it counts beacon blocks and builder blocks). + +| Name | Value | Unit | Duration | +| ----------------- | --------------------- | :---: | :----------: | +| `SLOTS_PER_EPOCH` | `uint64(2**6)` (= 64) | slots | 8:32 minutes | + +### Shard blob samples + +| Name | Value | Notes | +| ------------------ | ---------------------- | ------------------------- | +| `SAMPLES_PER_BLOB` | `uint64(2**9)` (= 512) | 248 * 512 = 126,976 bytes | + +## Configuration + +*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 + +| Name | Value | Unit | Duration | +| ------------------ | ----------- | :-----: | :-------: | +| `SECONDS_PER_SLOT` | `uint64(8)` | seconds | 8 seconds | + +## Containers + +### New Containers + +#### `BuilderBlockBid` + +```python +class BuilderBlockBid(Container): + slot: Slot + parent_block_root: Root + + execution_payload_root: Root + + sharded_data_commitment_root: ( + Root # Root of the sharded data (only data, not beacon/builder block commitments) + ) + + sharded_data_commitment_count: uint64 # Count of sharded data commitments + + 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 +``` + +#### `BuilderBlockBidWithRecipientAddress` + +```python +class BuilderBlockBidWithRecipientAddress(Container): + builder_block_bid: Union[None, BuilderBlockBid] + recipient_address: ExecutionAddress # Address to receive the block builder bid +``` + +#### `ShardedCommitmentsContainer` + +```python +class ShardedCommitmentsContainer(Container): + sharded_commitments: List[KZGCommitment, 2 * MAX_SHARDS] + + # Aggregate degree proof for all sharded_commitments + degree_proof: KZGCommitment + + # 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 + + # Random evaluation of beacon blocks + execution payload (this helps with quick verification) + block_verification_kzg_proof: KZGCommitment +``` + +#### `ShardSample` + +```python +class ShardSample(Container): + slot: Slot + row: uint64 + column: uint64 + data: Vector[BLSFieldElement, FIELD_ELEMENTS_PER_SAMPLE] + proof: KZGCommitment + builder: ValidatorIndex + signature: BLSSignature +``` + +### Modified containers + +#### `BeaconState` + +```python +class BeaconState(bellatrix.BeaconState): + blocks_since_builder_block: List[BeaconBlock, MAX_PROPOSER_BLOCKS_BETWEEN_BUILDER_BLOCKS] +``` + +#### `BuilderBlockData` + +```python +class BuilderBlockData(Container): + execution_payload: ExecutionPayload + sharded_commitments_container: ShardedCommitmentsContainer +``` + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(altair.BeaconBlockBody): + payload_data: Union[BuilderBlockBid, BuilderBlockData] +``` + +## Helper functions + +### Block processing + +#### `is_builder_block_slot` + +```python +def is_builder_block_slot(slot: Slot) -> bool: + return slot % 2 == 1 +``` + +### Beacon state accessors + +#### `get_active_shard_count` + +```python +def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64: + """ + Return the number of active shards. + Note that this puts an upper bound on the number of committees per slot. + """ + return ACTIVE_SHARDS +``` + +## Beacon chain state transition function + +### Block processing + +#### `process_block` + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + verify_builder_block_bid(state, block) + process_sharded_data(state, block) + process_execution_payload(state, block, EXECUTION_ENGINE) + + if not is_builder_block_slot(block.slot): + process_randao(state, block.body) + + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + + if is_builder_block_slot(block.slot): + state.blocks_since_builder_block = [] + state.blocks_since_builder_block.append(block) +``` + +#### Block header + +```python +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + # Verify that proposer index is the correct index + if not is_builder_block_slot(block.slot): + assert block.proposer_index == get_beacon_proposer_index(state) + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed +``` + +#### Builder Block Bid + +```python +def verify_builder_block_bid(state: BeaconState, block: BeaconBlock) -> None: + if is_builder_block_slot(block.slot): + # Get last builder block bid + assert state.blocks_since_builder_block[-1].body.payload_data.selector == 0 + builder_block_bid = state.blocks_since_builder_block[ + -1 + ].body.payload_data.value.builder_block_bid + assert builder_block_bid.slot + 1 == block.slot + + assert ( + block.body.payload_data.selector == 1 + ) # Verify that builder block does not contain bid + + builder_block_data = block.body.payload_data.value + + assert builder_block_bid.execution_payload_root == hash_tree_root( + builder_block_data.execution_payload + ) + + assert ( + builder_block_bid.sharded_data_commitment_count + == builder_block_data.included_sharded_data_commitments + ) + + assert builder_block_bid.sharded_data_commitment_root == hash_tree_root( + builder_block_data.sharded_commitments[ + -builder_block_bid.included_sharded_data_commitments : + ] + ) + + assert builder_block_bid.validator_index == block.proposer_index + + else: + assert block.body.payload_data.selector == 0 + + builder_block_bid = block.body.payload_data.value.builder_block_bid + assert builder_block_bid.slot == block.slot + assert builder_block_bid.parent_block_root == block.parent_root + # We do not check that the builder address exists or has sufficient balance here. + # If it does not have sufficient balance, the block proposer loses out, so it is their + # responsibility to check. + + # Check that the builder is a slashable validator. We can probably reduce this requirement and only + # ensure that they have 1 ETH in their account as a DOS protection. + builder = state.validators[builder_block_bid.validator_index] + assert is_slashable_validator(builder, get_current_epoch(state)) +``` + +#### Sharded data + +```python +def process_sharded_data(state: BeaconState, block: BeaconBlock) -> None: + if is_builder_block_slot(block.slot): + assert block.body.payload_data.selector == 1 + sharded_commitments_container = block.body.payload_data.value.sharded_commitments_container + + # Verify not too many commitments + assert len( + sharded_commitments_container.sharded_commitments + ) // 2 <= get_active_shard_count(state, get_current_epoch(state)) + + # Verify the degree proof + r = hash_to_bls_field(sharded_commitments_container.sharded_commitments, 0) + r_powers = compute_powers(r, len(sharded_commitments_container.sharded_commitments)) + combined_commitment = elliptic_curve_lincomb( + sharded_commitments_container.sharded_commitments, r_powers + ) + + payload_field_elements_per_blob = SAMPLES_PER_BLOB * FIELD_ELEMENTS_PER_SAMPLE // 2 + + verify_degree_proof( + combined_commitment, + payload_field_elements_per_blob, + sharded_commitments_container.degree_proof, + ) + + # Verify that the 2*N commitments lie on a degree < N polynomial + low_degree_check(sharded_commitments_container.sharded_commitments) + + # Verify that blocks since the last builder block have been included + blocks_chunked = [ + bytes_to_field_elements(ssz_serialize(block)) + for block in state.blocks_since_builder_block + ] + block_vectors = [] + + for block_chunked in blocks_chunked: + for i in range(0, len(block_chunked), payload_field_elements_per_blob): + block_vectors.append(block_chunked[i : i + payload_field_elements_per_blob]) + + number_of_blobs = len(block_vectors) + r = hash_to_bls_field( + sharded_commitments_container.sharded_commitments[:number_of_blobs], 0 + ) + x = hash_to_bls_field( + sharded_commitments_container.sharded_commitments[:number_of_blobs], 1 + ) + + r_powers = compute_powers(r, number_of_blobs) + combined_vector = vector_lincomb(block_vectors, r_powers) + combined_commitment = elliptic_curve_lincomb( + sharded_commitments_container.sharded_commitments[:number_of_blobs], r_powers + ) + y = evaluate_polynomial_in_evaluation_form(combined_vector, x) + + verify_kzg_proof( + combined_commitment, x, y, sharded_commitments_container.block_verification_kzg_proof + ) + + # Verify that number of sharded data commitments is correctly indicated + assert 2 * (number_of_blobs + included_sharded_data_commitments) == len( + sharded_commitments_container.sharded_commitments + ) +``` + +#### Execution payload + +```python +def process_execution_payload( + state: BeaconState, block: BeaconBlock, execution_engine: ExecutionEngine +) -> None: + if is_builder_block_slot(block.slot): + assert block.body.payload_data.selector == 1 + payload = block.body.payload_data.value.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 random + assert payload.random == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_time_at_slot(state, state.slot) + + # Get sharded data commitments + sharded_commitments_container = block.body.sharded_commitments_container + sharded_data_commitments = sharded_commitments_container.sharded_commitments[ + -sharded_commitments_container.included_sharded_data_commitments : + ] + + # Get all unprocessed builder block bids + unprocessed_builder_block_bid_with_recipient_addresses = [] + for block in state.blocks_since_builder_block[1:]: + unprocessed_builder_block_bid_with_recipient_addresses.append( + block.body.builder_block_bid_with_recipient_address.value + ) + + # Verify the execution payload is valid + # The execution engine gets two extra payloads: One for the sharded data commitments (these are needed to verify type 3 transactions) + # and one for all so far unprocessed builder block bids: + # * The execution engine needs to transfer the balance from the bidder to the proposer. + # * The execution engine needs to deduct data gas fees from the bidder balances + assert execution_engine.execute_payload( + payload, + sharded_data_commitments, + unprocessed_builder_block_bid_with_recipient_addresses, + ) + + # 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, + receipt_root=payload.receipt_root, + logs_bloom=payload.logs_bloom, + random=payload.random, + 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), + ) +``` diff --git a/specs/_deprecated/sharding/p2p-interface.md b/specs/_deprecated/sharding/p2p-interface.md new file mode 100644 index 0000000000..1439818a04 --- /dev/null +++ b/specs/_deprecated/sharding/p2p-interface.md @@ -0,0 +1,102 @@ +# Sharding -- Networking + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Misc](#misc) +- [Gossip domain](#gossip-domain) + - [Topics and messages](#topics-and-messages) + - [Builder block bid](#builder-block-bid) + - [`builder_block_bid`](#builder_block_bid) + - [Shard sample subnets](#shard-sample-subnets) + - [`shard_row_{subnet_id}`](#shard_row_subnet_id) + + + +## Introduction + +The specification of these changes continues in the same format as the network +specifications of previous upgrades, and assumes them as pre-requisite. The +adjustments and additions for Shards are outlined in this document. + +## Constants + +### Misc + +| Name | Value | Description | +| --------------------------- | ----- | -------------------------------------------------------------------------------- | +| `SHARD_ROW_SUBNET_COUNT` | `512` | The number of `shard_row_{subnet_id}` subnets used in the gossipsub protocol. | +| `SHARD_COLUMN_SUBNET_COUNT` | `512` | The number of `shard_column_{subnet_id}` subnets used in the gossipsub protocol. | + +## Gossip domain + +### Topics and messages + +Following the same scheme as the +[Phase0 gossip topics](../../phase0/p2p-interface.md#topics-and-messages), names +and payload types are: + +| Name | Message Type | +| -------------------------- | ------------------- | +| `shard_row_{subnet_id}` | `SignedShardSample` | +| `shard_column_{subnet_id}` | `SignedShardSample` | +| `builder_block_bid` | `BuilderBlockBid` | + +The [DAS network specification](../das/das-core.md) defines additional topics. + +#### Builder block bid + +##### `builder_block_bid` + +- _[IGNORE]_ The `bid` is published 1 slot early or later (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `bid.slot <= current_slot + 1` (a client MAY queue future samples for + propagation at the appropriate slot). +- _[IGNORE]_ The `bid` is for the current or next block i.e. validate that + `bid.slot >= current_slot` +- _[IGNORE]_ The `bid` is the first `bid` valid bid for `bid.slot`, or the bid + is at least 1% higher than the previous known `bid` +- _[REJECT]_ The validator defined by `bid.validator_index` exists and is + slashable. +- _[REJECT]_ The bid signature, which is an Eth1 signature, needs to be valid + and the address needs to contain enough Ether to cover the bid and the data + gas base fee. + +#### Shard sample subnets + +Shard sample (row/column) subnets are used by builders to make their samples +available as part of their intermediate block release after selection by beacon +block proposers. + +##### `shard_row_{subnet_id}` + +Shard sample data, in the form of a `SignedShardSample` is published to the +`shard_row_{subnet_id}` and `shard_column_{subnet_id}` subnets. + +The following validations MUST pass before forwarding the `sample`. + +- _[IGNORE]_ The `sample` is published 1 slot early or later (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `sample.slot <= current_slot + 1` (a client MAY queue future samples for + propagation at the appropriate slot). +- _[IGNORE]_ The `sample` is new enough to still be processed -- i.e. validate + that `compute_epoch_at_slot(sample.slot) >= get_previous_epoch(state)` +- _[REJECT]_ The shard sample is for the correct subnet -- i.e. + `sample.row == subnet_id` for `shard_row_{subnet_id}` and + `sample.column == subnet_id` for `shard_column_{subnet_id}` +- _[IGNORE]_ The sample is the first sample with valid signature received for + the `(sample.builder, sample.slot, sample.row, sample.column)` combination. +- _[REJECT]_ The `sample.data` MUST NOT contain any point `x >= BLS_MODULUS`. + Although it is a `uint256`, not the full 256 bit range is valid. +- _[REJECT]_ The validator defined by `sample.builder` exists and is slashable. +- _[REJECT]_ The sample is proposed by the expected `builder` for the sample's + `slot`. i.e., the beacon block at `sample.slot - 1` according to the node's + fork choice contains an `IntermediateBlockBid` with + `intermediate_block_bid.validator_index == sample.builder` +- _[REJECT]_ The sample signature, `sample.signature`, is valid for the builder + -- i.e. `bls.Verify(builder_pubkey, sample_signing_root, sample.signature)` OR + `sample.signature == Bytes96(b"\0" * 96)` AND the sample verification + `verify_sample` passes diff --git a/specs/_deprecated/sharding/polynomial-commitments.md b/specs/_deprecated/sharding/polynomial-commitments.md new file mode 100644 index 0000000000..bfd43c3c3d --- /dev/null +++ b/specs/_deprecated/sharding/polynomial-commitments.md @@ -0,0 +1,412 @@ +# Sharding -- Polynomial Commitments + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [BLS Field](#bls-field) + - [KZG Trusted setup](#kzg-trusted-setup) +- [Custom types](#custom-types) +- [Helper functions](#helper-functions) + - [`next_power_of_two`](#next_power_of_two) + - [`reverse_bit_order`](#reverse_bit_order) + - [`list_to_reverse_bit_order`](#list_to_reverse_bit_order) +- [Field operations](#field-operations) + - [Generic field operations](#generic-field-operations) + - [`bls_modular_inverse`](#bls_modular_inverse) + - [`roots_of_unity`](#roots_of_unity) + - [Field helper functions](#field-helper-functions) + - [`compute_powers`](#compute_powers) + - [`low_degree_check`](#low_degree_check) + - [`vector_lincomb`](#vector_lincomb) + - [`bytes_to_field_elements`](#bytes_to_field_elements) +- [Polynomial operations](#polynomial-operations) + - [`add_polynomials`](#add_polynomials) + - [`multiply_polynomials`](#multiply_polynomials) + - [`interpolate_polynomial`](#interpolate_polynomial) + - [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form) +- [KZG Operations](#kzg-operations) + - [Elliptic curve helper functions](#elliptic-curve-helper-functions) + - [`elliptic_curve_lincomb`](#elliptic_curve_lincomb) + - [Hash to field](#hash-to-field) + - [`hash_to_bls_field`](#hash_to_bls_field) + - [KZG operations](#kzg-operations) + - [`verify_kzg_proof`](#verify_kzg_proof) + - [`verify_kzg_multiproof`](#verify_kzg_multiproof) + - [`verify_degree_proof`](#verify_degree_proof) + + + +## Introduction + +This document specifies basic polynomial operations and KZG polynomial +commitment operations as they are needed for the sharding specification. The +implementations are not optimized for performance, but readability. All +practical implementations should optimize the polynomial operations, and hints +what the best known algorithms for these implementations are included below. + +## Constants + +### BLS Field + +| Name | Value | Notes | +| ------------------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| `BLS_MODULUS` | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` (curve order of BLS12_381) | | +| `PRIMITIVE_ROOT_OF_UNITY` | `7` | Primitive root of unity of the BLS12_381 (inner) BLS_MODULUS | + +### KZG Trusted setup + +| Name | Value | +| ---------- | -------------------------------------------------------------------------------------------------------------- | +| `G1_SETUP` | Type `List[G1]`. The G1-side trusted setup `[G, G*s, G*s**2....]`; note that the first point is the generator. | +| `G2_SETUP` | Type `List[G2]`. The G2-side trusted setup `[G, G*s, G*s**2....]` | + +## Custom types + +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| ----------------------------- | ----------------------- | ---------------------------------------------------------- | +| `KZGCommitment` | `Bytes48` | A G1 curve point | +| `BLSFieldElement` | `uint256` | A number `x` in the range `0 <= x < BLS_MODULUS` | +| `BLSPolynomialByCoefficients` | `List[BLSFieldElement]` | A polynomial over the BLS field, given in coefficient form | +| `BLSPolynomialByEvaluations` | `List[BLSFieldElement]` | A polynomial over the BLS field, given in evaluation form | + +## Helper functions + +#### `next_power_of_two` + +```python +def next_power_of_two(x: int) -> int: + assert x > 0 + return 2 ** ((x - 1).bit_length()) +``` + +#### `reverse_bit_order` + +```python +def reverse_bit_order(n: int, order: int) -> int: + """ + Reverse the bit order of an integer n + """ + assert is_power_of_two(order) + # Convert n to binary with the same number of bits as "order" - 1, then reverse its bit order + return int(("{:0" + str(order.bit_length() - 1) + "b}").format(n)[::-1], 2) +``` + +#### `list_to_reverse_bit_order` + +```python +def list_to_reverse_bit_order(l: List[int]) -> List[int]: + """ + Convert a list between normal and reverse bit order. The permutation is an involution (inverts itself).. + """ + return [l[reverse_bit_order(i, len(l))] for i in range(len(l))] +``` + +## Field operations + +### Generic field operations + +#### `bls_modular_inverse` + +```python +def bls_modular_inverse(x: BLSFieldElement) -> BLSFieldElement: + """ + Compute the modular inverse of x, i.e. y such that x * y % BLS_MODULUS == 1 and return 1 for x == 0 + """ + lm, hm = 1, 0 + low, high = x % BLS_MODULUS, BLS_MODULUS + while low > 1: + r = high // low + nm, new = hm - lm * r, high - low * r + lm, low, hm, high = nm, new, lm, low + return lm % BLS_MODULUS +``` + +#### `roots_of_unity` + +```python +def roots_of_unity(order: uint64) -> List[BLSFieldElement]: + """ + Compute a list of roots of unity for a given order. + The order must divide the BLS multiplicative group order, i.e. BLS_MODULUS - 1 + """ + assert (BLS_MODULUS - 1) % order == 0 + roots = [] + root_of_unity = pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // order, BLS_MODULUS) + + current_root_of_unity = 1 + for i in range(SAMPLES_PER_BLOB * FIELD_ELEMENTS_PER_SAMPLE): + roots.append(current_root_of_unity) + current_root_of_unity = current_root_of_unity * root_of_unity % BLS_MODULUS + return roots +``` + +### Field helper functions + +#### `compute_powers` + +```python +def compute_powers(x: BLSFieldElement, n: uint64) -> List[BLSFieldElement]: + current_power = 1 + powers = [] + for _ in range(n): + powers.append(BLSFieldElement(current_power)) + current_power = current_power * int(x) % BLS_MODULUS + return powers +``` + +#### `low_degree_check` + +```python +def low_degree_check(commitments: List[KZGCommitment]): + """ + Checks that the commitments are on a low-degree polynomial. + If there are 2*N commitments, that means they should lie on a polynomial + of degree d = K - N - 1, where K = next_power_of_two(2*N) + (The remaining positions are filled with 0, this is to make FFTs usable) + + For details see here: https://notes.ethereum.org/@dankrad/barycentric_low_degree_check + """ + assert len(commitments) % 2 == 0 + N = len(commitments) // 2 + r = hash_to_bls_field(commitments, 0) + K = next_power_of_two(2 * N) + d = K - N - 1 + r_to_K = pow(r, N, K) + roots = list_to_reverse_bit_order(roots_of_unity(K)) + + # For an efficient implementation, B and Bprime should be precomputed + def B(z): + r = 1 + for w in roots[: d + 1]: + r = r * (z - w) % BLS_MODULUS + return r + + def Bprime(z): + r = 0 + for i in range(d + 1): + m = 1 + for w in roots[:i] + roots[i + 1 : d + 1]: + m = m * (z - w) % BLS_MODULUS + r = (r + m) % BLS_MODULUS + return r + + coefs = [] + for i in range(K): + 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() +``` + +#### `vector_lincomb` + +```python +def vector_lincomb( + vectors: List[List[BLSFieldElement]], scalars: List[BLSFieldElement] +) -> List[BLSFieldElement]: + """ + Compute a linear combination of field element vectors. + """ + r = [0] * len(vectors[0]) + for v, a in zip(vectors, scalars): + for i, x in enumerate(v): + r[i] = (r[i] + a * x) % BLS_MODULUS + return [BLSFieldElement(x) for x in r] +``` + +#### `bytes_to_field_elements` + +```python +def bytes_to_field_elements(block: bytes) -> List[BLSFieldElement]: + """ + Slices a block into 31-byte chunks that can fit into field elements. + """ + sliced_block = [block[i : i + 31] for i in range(0, len(bytes), 31)] + return [BLSFieldElement(int.from_bytes(x, "little")) for x in sliced_block] +``` + +## Polynomial operations + +#### `add_polynomials` + +```python +def add_polynomials( + a: BLSPolynomialByCoefficients, b: BLSPolynomialByCoefficients +) -> BLSPolynomialByCoefficients: + """ + Sum the polynomials ``a`` and ``b`` given by their coefficients. + """ + a, b = (a, b) if len(a) >= len(b) else (b, a) + return [(a[i] + (b[i] if i < len(b) else 0)) % BLS_MODULUS for i in range(len(a))] +``` + +#### `multiply_polynomials` + +```python +def multiply_polynomials( + a: BLSPolynomialByCoefficients, b: BLSPolynomialByCoefficients +) -> BLSPolynomialByCoefficients: + """ + Multiplies the polynomials `a` and `b` given by their coefficients + """ + r = [0] + for power, coef in enumerate(a): + summand = [0] * power + [coef * x % BLS_MODULUS for x in b] + r = add_polynomials(r, summand) + return r +``` + +#### `interpolate_polynomial` + +```python +def interpolate_polynomial( + xs: List[BLSFieldElement], ys: List[BLSFieldElement] +) -> BLSPolynomialByCoefficients: + """ + Lagrange interpolation + """ + assert len(xs) == len(ys) + r = [0] + + for i in range(len(xs)): + summand = [ys[i]] + for j in range(len(ys)): + if j != i: + weight_adjustment = bls_modular_inverse(xs[j] - xs[i]) + summand = multiply_polynomials( + summand, [weight_adjustment, ((BLS_MODULUS - weight_adjustment) * xs[i])] + ) + r = add_polynomials(r, summand) + + return r +``` + +#### `evaluate_polynomial_in_evaluation_form` + +```python +def evaluate_polynomial_in_evaluation_form( + poly: BLSPolynomialByEvaluations, x: BLSFieldElement +) -> BLSFieldElement: + """ + Evaluates a polynomial (in evaluation form) at an arbitrary point + """ + field_elements_per_blob = SAMPLES_PER_BLOB * FIELD_ELEMENTS_PER_SAMPLE + roots = roots_of_unity(field_elements_per_blob) + + def A(z): + r = 1 + for w in roots: + r = r * (z - w) % BLS_MODULUS + return r + + def Aprime(z): + 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] + for i, x in enumerate(inverses): + r += poly[i] * bls_modular_inverse(Aprime(roots[i])) * x % BLS_MODULUS + r = r * A(x) % BLS_MODULUS + return r +``` + +## KZG Operations + +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 + +#### `elliptic_curve_lincomb` + +```python +def elliptic_curve_lincomb( + points: List[KZGCommitment], scalars: List[BLSFieldElement] +) -> KZGCommitment: + """ + BLS multiscalar multiplication. This function can be optimized using Pippenger's algorithm and variants. + This is a non-optimized implementation. + """ + r = bls.inf_G1() + for x, a in zip(points, scalars): + r = r.add(x.mult(a)) + return r +``` + +### Hash to field + +#### `hash_to_bls_field` + +```python +def hash_to_bls_field(x: Container, challenge_number: uint64) -> BLSFieldElement: + """ + This function is used to generate Fiat-Shamir challenges. The output is not uniform over the BLS field. + """ + return ( + int.from_bytes( + hash(hash_tree_root(x) + int.to_bytes(challenge_number, 32, "little")), "little" + ) + ) % BLS_MODULUS +``` + +### KZG operations + +#### `verify_kzg_proof` + +```python +def verify_kzg_proof( + commitment: KZGCommitment, x: BLSFieldElement, y: BLSFieldElement, proof: KZGCommitment +) -> None: + """ + Check that `proof` is a valid KZG proof for the polynomial committed to by `commitment` evaluated + at `x` equals `y`. + """ + zero_poly = G2_SETUP[1].add(G2_SETUP[0].mult(x).neg()) + + assert bls.Pairing(proof, zero_poly) == bls.Pairing( + commitment.add(G1_SETUP[0].mult(y).neg), G2_SETUP[0] + ) +``` + +#### `verify_kzg_multiproof` + +```python +def verify_kzg_multiproof( + commitment: KZGCommitment, + xs: List[BLSFieldElement], + ys: List[BLSFieldElement], + proof: KZGCommitment, +) -> None: + """ + Verify a KZG multiproof. + """ + zero_poly = elliptic_curve_lincomb( + G2_SETUP[: len(xs)], interpolate_polynomial(xs, [0] * len(ys)) + ) + interpolated_poly = elliptic_curve_lincomb(G2_SETUP[: len(xs)], interpolate_polynomial(xs, ys)) + + assert bls.Pairing(proof, zero_poly) == bls.Pairing( + commitment.add(interpolated_poly.neg()), G2_SETUP[0] + ) +``` + +#### `verify_degree_proof` + +```python +def verify_degree_proof(commitment: KZGCommitment, degree_bound: uint64, proof: KZGCommitment): + """ + Verifies that the commitment is of polynomial degree < degree_bound. + """ + + assert bls.Pairing(proof, G2_SETUP[0]) == bls.Pairing(commitment, G2_SETUP[-degree_bound]) +``` diff --git a/specs/_deprecated/sharding/validator.md b/specs/_deprecated/sharding/validator.md new file mode 100644 index 0000000000..53355ef78c --- /dev/null +++ b/specs/_deprecated/sharding/validator.md @@ -0,0 +1,189 @@ +# Sharding -- Honest Validator + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Constants](#constants) + - [Sample counts](#sample-counts) +- [Helpers](#helpers) + - [`get_validator_row_subnets`](#get_validator_row_subnets) + - [`get_validator_column_subnets`](#get_validator_column_subnets) + - [`reconstruct_polynomial`](#reconstruct_polynomial) +- [Sample verification](#sample-verification) + - [`verify_sample`](#verify_sample) +- [Validator assignments](#validator-assignments) + - [Attesting](#attesting) +- [Minimum online validator requirement](#minimum-online-validator-requirement) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement executable beacon chain proposal. + +## Prerequisites + +This document is an extension of the +[Bellatrix -- Honest Validator](../../bellatrix/validator.md) guide. All +behaviors and definitions defined in this document, and documents it extends, +carry over unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated Beacon Chain doc of [Sharding](./beacon-chain.md) are requisite for this +document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Constants + +### Sample counts + +| Name | Value | +| ------------------------------- | ----- | +| `VALIDATOR_SAMPLE_ROW_COUNT` | `2` | +| `VALIDATOR_SAMPLE_COLUMN_COUNT` | `2` | + +## Helpers + +### `get_validator_row_subnets` + +TODO: Currently the subnets are public (i.e. anyone can derive them.) This is +good for a proof of custody with public verifiability, but bad for validator +privacy. + +```python +def get_validator_row_subnets(validator: Validator, epoch: Epoch) -> List[uint64]: + return [ + int.from_bytes(hash_tree_root([validator.pubkey, 0, i])) + for i in range(VALIDATOR_SAMPLE_ROW_COUNT) + ] +``` + +### `get_validator_column_subnets` + +```python +def get_validator_column_subnets(validator: Validator, epoch: Epoch) -> List[uint64]: + return [ + int.from_bytes(hash_tree_root([validator.pubkey, 1, i])) + for i in range(VALIDATOR_SAMPLE_COLUMN_COUNT) + ] +``` + +### `reconstruct_polynomial` + +```python +def reconstruct_polynomial(samples: List[SignedShardSample]) -> List[SignedShardSample]: + """ + Reconstructs one full row/column from at least 1/2 of the samples + """ +``` + +## Sample verification + +### `verify_sample` + +```python +def verify_sample(state: BeaconState, block: BeaconBlock, sample: SignedShardSample): + assert sample.row < 2 * get_active_shard_count(state, get_current_epoch(block.slot)) + assert sample.column < 2 * SAMPLES_PER_BLOB + assert block.slot == sample.slot + + # Verify builder signature. + # TODO: We should probably not do this. This should only be done by p2p to verify samples *before* intermediate block is in + # builder = state.validators[signed_block.message.proposer_index] + # signing_root = compute_signing_root(sample, get_domain(state, DOMAIN_SHARD_SAMPLE)) + # assert bls.Verify(sample.builder, signing_root, sample.signature) + + roots_in_rbo = list_to_reverse_bit_order( + roots_of_unity(SAMPLES_PER_BLOB * FIELD_ELEMENTS_PER_SAMPLE) + ) + + # Verify KZG proof + verify_kzg_multiproof( + block.body.payload_data.value.sharded_commitments_container.sharded_commitments[sample.row], + roots_in_rbo[ + sample.column * FIELD_ELEMENTS_PER_SAMPLE : (sample.column + 1) + * FIELD_ELEMENTS_PER_SAMPLE + ], + sample.data, + sample.proof, + ) +``` + +# Beacon chain responsibilities + +## Validator assignments + +### Attesting + +Every attester is assigned `VALIDATOR_SAMPLE_ROW_COUNT` rows and +`VALIDATOR_SAMPLE_COLUMN_COUNT` columns of shard samples. As part of their +validator duties, they should subscribe to the subnets given by +`get_validator_row_subnets` and `get_validator_column_subnets`, for the whole +epoch. + +A row or column is *available* for a `slot` if at least half of the total number +of samples were received on the subnet and passed `verify_sample`. Otherwise it +is called unavailable. + +If a validator is assigned to an attestation at slot `attestation_slot` and had +his previous attestation duty at `previous_attestation_slot`, then they should +only attest under the following conditions: + +- For all intermediate blocks `block` with + `previous_attestation_slot < block.slot <= attestation_slot`: All sample rows + and columns assigned to the validator were available. + +If this condition is not fulfilled, then the validator should instead attest to +the last block for which the condition holds. + +This leads to the security property that a chain that is not fully available +cannot have more than 1/16th of all validators voting for it. TODO: This claim +is for an "infinite number" of validators. Compute the concrete security due to +sampling bias. + +# Sample reconstruction + +A validator that has received enough samples of a row or column to mark it as +available, should reconstruct all samples in that row/column (if they aren't all +available already.) The function `reconstruct_polynomial` gives an example +implementation for this. + +Once they have run the reconstruction function, they should distribute the +samples that they reconstructed on all pubsub that the local node is subscribed +to, if they have not already received that sample on that pubsub. As an example: + +- The validator is subscribed to row `2` and column `5` +- The sample `(row, column) = (2, 5)` is missing in the column `5` pubsub +- After they have reconstruction of row `2`, the validator should send the + sample `(2, 5)` on to the row `2` pubsub (if it was missing) as well as the + column `5` pubsub. + +TODO: We need to verify the total complexity of doing this and make sure this +does not cause too much load on a validator + +## Minimum online validator requirement + +The data availability construction guarantees that reconstruction is possible if +75% of all samples are available. In this case, at least 50% of all rows and 50% +of all columns are independently available. In practice, it is likely that some +supernodes will centrally collect all samples and fill in any gaps. However, we +want to build a system that reliably reconstructs even absent all supernodes. +Any row or column with 50% of samples will easily be reconstructed even with +only 100s of validators online; so the only question is how we get to 50% of +samples for all rows and columns, when some of them might be completely +unseeded. + +Each validator will transfer 4 samples between rows and columns where there is +overlap. Without loss of generality, look at row 0. Each validator has 1/128 +chance of having a sample in this row, and we need 256 samples to reconstruct +it. So we expect that we need ~256 * 128 = 32,768 validators to have a fair +chance of reconstructing it if it was completely unseeded. + +A more elaborate estimate +[here](https://notes.ethereum.org/@dankrad/minimum-reconstruction-validators) +needs about 55,000 validators to be online for high safety that each row and +column will be reconstructed. diff --git a/specs/_features/eip6800/beacon-chain.md b/specs/_features/eip6800/beacon-chain.md new file mode 100644 index 0000000000..32e4707b50 --- /dev/null +++ b/specs/_features/eip6800/beacon-chain.md @@ -0,0 +1,220 @@ +# EIP6800 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Preset](#preset) + - [Execution](#execution) +- [Containers](#containers) + - [Modified containers](#modified-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [New containers](#new-containers) + - [`SuffixStateDiff`](#suffixstatediff) + - [`StemStateDiff`](#stemstatediff) + - [`IPAProof`](#ipaproof) + - [`VerkleProof`](#verkleproof) + - [`ExecutionWitness`](#executionwitness) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [`process_execution_payload`](#process_execution_payload) +- [Testing](#testing) + + + +## Introduction + +This upgrade adds transaction execution to the beacon chain as part of the +eip6800 upgrade. + +## Custom types + +| Name | SSZ equivalent | Description | +| ------------------------- | -------------- | ----------- | +| `BanderwagonGroupElement` | `Bytes32` | | +| `BanderwagonFieldElement` | `Bytes32` | | +| `Stem` | `Bytes31` | | + +## Preset + +### Execution + +| Name | Value | +| -------------------------- | -------------------------- | +| `MAX_STEMS` | `uint64(2**16)` (= 65,536) | +| `MAX_COMMITMENTS_PER_STEM` | `uint64(33)` | +| `VERKLE_WIDTH` | `uint64(2**8)` (= 256) | +| `IPA_PROOF_DEPTH` | `uint64(2**3)` (= 8) | + +## Containers + +### Modified containers + +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + blob_gas_used: uint64 + excess_blob_gas: uint64 + # [New in EIP6800] + execution_witness: ExecutionWitness +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions_root: Root + withdrawals_root: Root + blob_gas_used: uint64 + excess_data_gas: uint64 + # [New in EIP6800] + execution_witness_root: Root +``` + +### New containers + +#### `SuffixStateDiff` + +```python +class SuffixStateDiff(Container): + suffix: Bytes1 + current_value: Optional[Bytes32] + new_value: Optional[Bytes32] +``` + +*Note*: on the Kaustinen testnet, `new_value` is omitted from the container. + +#### `StemStateDiff` + +*Note*: `suffix_diffs` is only valid if the list is sorted by suffixes. + +```python +class StemStateDiff(Container): + stem: Stem + suffix_diffs: List[SuffixStateDiff, VERKLE_WIDTH] +``` + +#### `IPAProof` + +```python +class IPAProof(Container): + cl: Vector[BanderwagonGroupElement, IPA_PROOF_DEPTH] + cr: Vector[BanderwagonGroupElement, IPA_PROOF_DEPTH] + final_evaluation = BanderwagonFieldElement +``` + +#### `VerkleProof` + +```python +class VerkleProof(Container): + other_stems: List[Bytes31, MAX_STEMS] + depth_extension_present: ByteList[MAX_STEMS] + commitments_by_path: List[BanderwagonGroupElement, MAX_STEMS * MAX_COMMITMENTS_PER_STEM] + d: BanderwagonGroupElement + ipa_proof: IPAProof +``` + +#### `ExecutionWitness` + +```python +class ExecutionWitness(Container): + state_diff: List[StemStateDiff, MAX_STEMS] + verkle_proof: VerkleProof +``` + +## Beacon chain state transition function + +### Block processing + +#### Execution payload + +##### `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_time_at_slot(state, state.slot) + + # Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK + + # Verify the execution payload is valid + # Pass `versioned_hashes` to Execution Engine + # Pass `parent_beacon_block_root` to Execution Engine + 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, + ) + ) + + # 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), + excess_data_gas=payload.excess_data_gas, + execution_witness_root=hash_tree_root(payload.execution_witness), # [New in EIP6800] + ) +``` + +## Testing + +TBD diff --git a/specs/_features/eip6800/fork.md b/specs/_features/eip6800/fork.md new file mode 100644 index 0000000000..e5047bdb7f --- /dev/null +++ b/specs/_features/eip6800/fork.md @@ -0,0 +1,140 @@ +# EIP-6800 -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to eip6800](#fork-to-eip6800) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the eip6800 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | ------------------------------------- | +| `EIP6800_FORK_VERSION` | `Version('0x05000000')` | +| `EIP6800_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= EIP6800_FORK_EPOCH: + return EIP6800_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to eip6800 + +### Fork trigger + +The fork is triggered at epoch `EIP6800_FORK_EPOCH`. + +Note that for the pure eip6800 networks, we don't apply `upgrade_to_eip6800` +since it starts with the eip6800 version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == EIP6800_FORK_EPOCH`, an irregular state +change is made to upgrade to eip6800. + +The upgrade occurs after the completion of the inner loop of `process_slots` +that sets `state.slot` equal to `EIP6800_FORK_EPOCH * SLOTS_PER_EPOCH`. Care +must be taken when transitioning through the fork boundary as implementations +will need a modified +[state transition function](../../phase0/beacon-chain.md#beacon-chain-state-transition-function) +that deviates from the Phase 0 document. In particular, the outer +`state_transition` function defined in the Phase 0 document will not expose the +precise fork slot to execute the upgrade in the presence of skipped slots at the +fork boundary. Instead, the logic must be within `process_slots`. + +```python +def upgrade_to_eip6800(pre: deneb.BeaconState) -> BeaconState: + epoch = capella.get_current_epoch(pre) + latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=pre.latest_execution_payload_header.parent_hash, + fee_recipient=pre.latest_execution_payload_header.fee_recipient, + state_root=pre.latest_execution_payload_header.state_root, + receipts_root=pre.latest_execution_payload_header.receipts_root, + logs_bloom=pre.latest_execution_payload_header.logs_bloom, + prev_randao=pre.latest_execution_payload_header.prev_randao, + block_number=pre.latest_execution_payload_header.block_number, + gas_limit=pre.latest_execution_payload_header.gas_limit, + gas_used=pre.latest_execution_payload_header.gas_used, + timestamp=pre.latest_execution_payload_header.timestamp, + extra_data=pre.latest_execution_payload_header.extra_data, + base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas, + excess_data_gas=uint256(0), + block_hash=pre.latest_execution_payload_header.block_hash, + transactions_root=pre.latest_execution_payload_header.transactions_root, + withdrawals_root=pre.latest_execution_payload_header.withdrawals_root, + # [New in EIP6800] + execution_witness_root=hash_tree_root(ExecutionWitness([], [])), + ) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in EIP6800] + current_version=EIP6800_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + ) + + return post +``` diff --git a/specs/_features/eip6914/beacon-chain.md b/specs/_features/eip6914/beacon-chain.md new file mode 100644 index 0000000000..169fd5b312 --- /dev/null +++ b/specs/_features/eip6914/beacon-chain.md @@ -0,0 +1,62 @@ +# EIP-6914 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Preset](#preset) + - [Time parameters](#time-parameters) +- [Helper functions](#helper-functions) + - [Predicates](#predicates) + - [`is_reusable_validator`](#is_reusable_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Modified `get_index_for_new_validator`](#modified-get_index_for_new_validator) + + + +## Introduction + +This is the beacon chain specification to assign new deposits to existing +validator records. Refers to +[EIP-6914](https://github.com/ethereum/EIPs/pull/6914). + +*Note*: This specification is built upon +[Capella](../../capella/beacon-chain.md) and is under active development. + +## Preset + +### Time parameters + +| Name | Value | Unit | Duration | +| ---------------------------- | -------------------------- | ------ | --------- | +| `SAFE_EPOCHS_TO_REUSE_INDEX` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 year | + +## Helper functions + +### Predicates + +#### `is_reusable_validator` + +```python +def is_reusable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool: + """ + Check if ``validator`` index can be re-assigned to a new deposit. + """ + return epoch > validator.withdrawable_epoch + SAFE_EPOCHS_TO_REUSE_INDEX and balance == 0 +``` + +## Beacon chain state transition function + +### Block processing + +#### Modified `get_index_for_new_validator` + +```python +def get_index_for_new_validator(state: BeaconState) -> ValidatorIndex: + for index, validator in enumerate(state.validators): + if is_reusable_validator(validator, state.balances[index], get_current_epoch(state)): + return ValidatorIndex(index) + return ValidatorIndex(len(state.validators)) +``` diff --git a/specs/_features/eip6914/fork-choice.md b/specs/_features/eip6914/fork-choice.md new file mode 100644 index 0000000000..ebe9fa6819 --- /dev/null +++ b/specs/_features/eip6914/fork-choice.md @@ -0,0 +1,38 @@ +# EIP-6914 -- Fork Choice + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Fork choice](#fork-choice) + - [Handlers](#handlers) + - [`on_reused_index`](#on_reused_index) + + + +## Introduction + +This is the modification of the fork choice according to EIP-6914. + +## Fork choice + +A new handler is added with this upgrade: + +- `on_reused_index(store, index)` whenever a validator index + `index: ValidatorIndex` is reused. That is, + [`get_index_for_new_validator()`](./beacon-chain.md#get_index_for_new_validator) + provides an index due to a return value of `True` from + [`is_reusable_validator()`](./beacon-chain.md#is_reusable_validator). + +This new handler is used to update the list of equivocating indices to be +synchronized with the canonical chain. + +### Handlers + +#### `on_reused_index` + +```python +def on_reused_index(store: Store, index: ValidatorIndex) -> None: + store.equivocating_indices.discard(index) +``` diff --git a/specs/_features/eip7441/beacon-chain.md b/specs/_features/eip7441/beacon-chain.md new file mode 100644 index 0000000000..ab2ae9c8cf --- /dev/null +++ b/specs/_features/eip7441/beacon-chain.md @@ -0,0 +1,443 @@ +# EIP-7441 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Domain types](#domain-types) +- [Preset](#preset) +- [Configuration](#configuration) +- [Cryptography](#cryptography) + - [BLS](#bls) + - [Curdleproofs and opening proofs](#curdleproofs-and-opening-proofs) +- [Epoch processing](#epoch-processing) + - [`WhiskTracker`](#whisktracker) + - [`BeaconState`](#beaconstate) +- [Block processing](#block-processing) + - [Block header](#block-header) + - [Whisk](#whisk) + - [`BeaconBlockBody`](#beaconblockbody) + - [Deposits](#deposits) + - [`get_beacon_proposer_index`](#get_beacon_proposer_index) + + + +## Introduction + +This document details the beacon chain additions and changes of to support the +EIP-7441 (Whisk SSLE). + +*Note*: This specification is built upon +[capella](../../capella/beacon-chain.md) and is under active development. + +## Constants + +### Domain types + +| Name | Value | +| ---------------------------- | -------------------------- | +| `DOMAIN_CANDIDATE_SELECTION` | `DomainType('0x07000000')` | +| `DOMAIN_SHUFFLE` | `DomainType('0x07100000')` | +| `DOMAIN_PROPOSER_SELECTION` | `DomainType('0x07200000')` | + +## Preset + +| Name | Value | Description | +| -------------------------- | -------------------------- | ---------------------------------------------- | +| `CURDLEPROOFS_N_BLINDERS` | `uint64(4)` | number of blinders for curdleproofs | +| `CANDIDATE_TRACKERS_COUNT` | `uint64(2**14)` (= 16,384) | number of candidate trackers | +| `PROPOSER_TRACKERS_COUNT` | `uint64(2**13)` (= 8,192) | number of proposer trackers | +| `VALIDATORS_PER_SHUFFLE` | `uint64(2**7 - 4)` (= 124) | number of validators shuffled per shuffle step | +| `MAX_SHUFFLE_PROOF_SIZE` | `uint64(2**15)` | max size of a shuffle proof | +| `MAX_OPENING_PROOF_SIZE` | `uint64(2**10)` | max size of an opening proof | + +## Configuration + +| Name | Value | Description | +| ---------------------------- | --------------------- | ----------------------------------------------------------- | +| `EPOCHS_PER_SHUFFLING_PHASE` | `Epoch(2**8)` (= 256) | epochs per shuffling phase | +| `PROPOSER_SELECTION_GAP` | `Epoch(2)` | gap between proposer selection and the block proposal phase | + +## Cryptography + +### BLS + +| Name | SSZ equivalent | Description | +| ------------------- | ---------------------------------- | ----------------------------- | +| `BLSFieldElement` | `uint256` | BLS12-381 scalar | +| `BLSG1Point` | `Bytes48` | compressed BLS12-381 G1 point | +| `WhiskShuffleProof` | `ByteList[MAX_SHUFFLE_PROOF_SIZE]` | Serialized shuffle proof | +| `WhiskTrackerProof` | `ByteList[MAX_OPENING_PROOF_SIZE]` | Serialized tracker proof | + +*Note*: A subgroup check MUST be performed when deserializing a `BLSG1Point` for +use in any of the functions below. + +```python +def BLSG1ScalarMultiply(scalar: BLSFieldElement, point: BLSG1Point) -> BLSG1Point: + return bls.G1_to_bytes48(bls.multiply(bls.bytes48_to_G1(point), scalar)) +``` + +```python +def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement: + """ + Convert bytes to a BLS field scalar. The output is not uniform over the BLS field. + TODO: Deneb will introduces this helper too. Should delete it once it's rebased to post-Deneb. + """ + field_element = int.from_bytes(b, ENDIANNESS) + return BLSFieldElement(field_element % BLS_MODULUS) +``` + +| Name | Value | +| ------------------ | ------------------------------------------------------------------------------------------------------------------ | +| `BLS_G1_GENERATOR` | `BLSG1Point('0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb')` | +| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | +| `CURDLEPROOFS_CRS` | TBD | + +### Curdleproofs and opening proofs + +Note that Curdleproofs (Whisk Shuffle Proofs), the tracker opening proofs and +all related data structures and verifier code (along with tests) is specified in +[curdleproofs.pie](https://github.com/nalinbhardwaj/curdleproofs.pie/tree/dev) +repository. + +```python +def IsValidWhiskShuffleProof( + pre_shuffle_trackers: Sequence[WhiskTracker], + post_shuffle_trackers: Sequence[WhiskTracker], + shuffle_proof: WhiskShuffleProof, +) -> bool: + """ + Verify `post_shuffle_trackers` is a permutation of `pre_shuffle_trackers`. + Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/blob/dev/curdleproofs/curdleproofs/whisk_interface.py. + """ + return curdleproofs.IsValidWhiskShuffleProof( + CURDLEPROOFS_CRS, + pre_shuffle_trackers, + post_shuffle_trackers, + shuffle_proof, + ) +``` + +```python +def IsValidWhiskOpeningProof( + tracker: WhiskTracker, k_commitment: BLSG1Point, tracker_proof: WhiskTrackerProof +) -> bool: + """ + Verify knowledge of `k` such that `tracker.k_r_G == k * tracker.r_G` and `k_commitment == k * BLS_G1_GENERATOR`. + Defined in https://github.com/nalinbhardwaj/curdleproofs.pie/blob/dev/curdleproofs/curdleproofs/whisk_interface.py. + """ + return curdleproofs.IsValidWhiskOpeningProof(tracker, k_commitment, tracker_proof) +``` + +## Epoch processing + +### `WhiskTracker` + +```python +class WhiskTracker(Container): + r_G: BLSG1Point + k_r_G: BLSG1Point +``` + +### `BeaconState` + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + latest_execution_payload_header: ExecutionPayloadHeader + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + # [New in EIP7441] + whisk_candidate_trackers: Vector[WhiskTracker, CANDIDATE_TRACKERS_COUNT] + # [New in EIP7441] + whisk_proposer_trackers: Vector[WhiskTracker, PROPOSER_TRACKERS_COUNT] + # [New in EIP7441] + whisk_trackers: List[WhiskTracker, VALIDATOR_REGISTRY_LIMIT] + # [New in EIP7441] + whisk_k_commitments: List[BLSG1Point, VALIDATOR_REGISTRY_LIMIT] +``` + +```python +def select_whisk_proposer_trackers(state: BeaconState, epoch: Epoch) -> None: + # Select proposer trackers from candidate trackers + proposer_seed = get_seed( + state, Epoch(saturating_sub(epoch, PROPOSER_SELECTION_GAP)), DOMAIN_PROPOSER_SELECTION + ) + for i in range(PROPOSER_TRACKERS_COUNT): + index = compute_shuffled_index( + uint64(i), uint64(len(state.whisk_candidate_trackers)), proposer_seed + ) + state.whisk_proposer_trackers[i] = state.whisk_candidate_trackers[index] +``` + +```python +def select_whisk_candidate_trackers(state: BeaconState, epoch: Epoch) -> None: + # Select candidate trackers from active validator trackers + active_validator_indices = get_active_validator_indices(state, epoch) + for i in range(CANDIDATE_TRACKERS_COUNT): + seed = hash(get_seed(state, epoch, DOMAIN_CANDIDATE_SELECTION) + uint_to_bytes(uint64(i))) + candidate_index = compute_proposer_index( + state, active_validator_indices, seed + ) # sample by effective balance + state.whisk_candidate_trackers[i] = state.whisk_trackers[candidate_index] +``` + +```python +def process_whisk_updates(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + if ( + next_epoch % EPOCHS_PER_SHUFFLING_PHASE == 0 + ): # select trackers at the start of shuffling phases + select_whisk_proposer_trackers(state, next_epoch) + select_whisk_candidate_trackers(state, next_epoch) +``` + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_summaries_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_whisk_updates(state) # [New in EIP7441] +``` + +## Block processing + +### Block header + +```python +def process_whisk_opening_proof(state: BeaconState, block: BeaconBlock) -> None: + tracker = state.whisk_proposer_trackers[state.slot % PROPOSER_TRACKERS_COUNT] + k_commitment = state.whisk_k_commitments[block.proposer_index] + assert IsValidWhiskOpeningProof(tracker, k_commitment, block.body.whisk_opening_proof) +``` + +Removed `assert block.proposer_index == get_beacon_proposer_index(state)` check +in Whisk. + +```python +def process_block_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify that the slots match + assert block.slot == state.slot + # Verify that the block is newer than latest block header + assert block.slot > state.latest_block_header.slot + + # # Verify that proposer index is the correct index + # assert block.proposer_index == get_beacon_proposer_index(state) + + # Verify that the parent matches + assert block.parent_root == hash_tree_root(state.latest_block_header) + # Cache current block as the new latest block + state.latest_block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=Bytes32(), # Overwritten in the next process_slot call + body_root=hash_tree_root(block.body), + ) + + # Verify proposer is not slashed + proposer = state.validators[block.proposer_index] + assert not proposer.slashed + process_whisk_opening_proof(state, block) # [New in EIP7441] +``` + +### Whisk + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + execution_payload: ExecutionPayload + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + # [New in EIP7441] + whisk_opening_proof: WhiskTrackerProof + # [New in EIP7441] + whisk_post_shuffle_trackers: Vector[WhiskTracker, VALIDATORS_PER_SHUFFLE] + # [New in EIP7441] + whisk_shuffle_proof: WhiskShuffleProof + # [New in EIP7441] + whisk_registration_proof: WhiskTrackerProof + # [New in EIP7441] + whisk_tracker: WhiskTracker + # [New in EIP7441] + whisk_k_commitment: BLSG1Point +``` + +```python +def get_shuffle_indices(randao_reveal: BLSSignature) -> Sequence[uint64]: + """ + Given a `randao_reveal` return the list of indices that got shuffled from the entire candidate set. + """ + indices = [] + for i in range(0, VALIDATORS_PER_SHUFFLE): + # XXX ensure we are not suffering from modulo bias + pre_image = randao_reveal + uint_to_bytes(uint64(i)) + shuffle_index = bytes_to_uint64(hash(pre_image)[0:8]) % CANDIDATE_TRACKERS_COUNT + indices.append(shuffle_index) + + return indices +``` + +```python +def process_shuffled_trackers(state: BeaconState, body: BeaconBlockBody) -> None: + shuffle_epoch = get_current_epoch(state) % EPOCHS_PER_SHUFFLING_PHASE + if shuffle_epoch + PROPOSER_SELECTION_GAP + 1 >= EPOCHS_PER_SHUFFLING_PHASE: + # Require trackers set to zero during cooldown + assert body.whisk_post_shuffle_trackers == Vector[WhiskTracker, VALIDATORS_PER_SHUFFLE]() + assert body.whisk_shuffle_proof == WhiskShuffleProof() + else: + # Require shuffled trackers during shuffle + shuffle_indices = get_shuffle_indices(body.randao_reveal) + pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] + assert IsValidWhiskShuffleProof( + pre_shuffle_trackers, + body.whisk_post_shuffle_trackers, + body.whisk_shuffle_proof, + ) + # Shuffle candidate trackers + for i, shuffle_index in enumerate(shuffle_indices): + state.whisk_candidate_trackers[shuffle_index] = body.whisk_post_shuffle_trackers[i] +``` + +```python +def is_k_commitment_unique(state: BeaconState, k_commitment: BLSG1Point) -> bool: + return all( + [whisk_k_commitment != k_commitment for whisk_k_commitment in state.whisk_k_commitments] + ) +``` + +```python +def process_whisk_registration(state: BeaconState, body: BeaconBlockBody) -> None: + proposer_index = get_beacon_proposer_index(state) + if state.whisk_trackers[proposer_index].r_G == BLS_G1_GENERATOR: # first Whisk proposal + assert body.whisk_tracker.r_G != BLS_G1_GENERATOR + assert is_k_commitment_unique(state, body.whisk_k_commitment) + assert IsValidWhiskOpeningProof( + body.whisk_tracker, + body.whisk_k_commitment, + body.whisk_registration_proof, + ) + state.whisk_trackers[proposer_index] = body.whisk_tracker + state.whisk_k_commitments[proposer_index] = body.whisk_k_commitment + else: # next Whisk proposals + assert body.whisk_registration_proof == WhiskTrackerProof() + assert body.whisk_tracker == WhiskTracker() + assert body.whisk_k_commitment == BLSG1Point() +``` + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_withdrawals(state, block.body.execution_payload) + process_execution_payload(state, block.body, EXECUTION_ENGINE) + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) + process_shuffled_trackers(state, block.body) # [New in EIP7441] + process_whisk_registration(state, block.body) # [New in EIP7441] +``` + +### Deposits + +```python +def get_initial_whisk_k(validator_index: ValidatorIndex, counter: int) -> BLSFieldElement: + # hash `validator_index || counter` + return BLSFieldElement( + bytes_to_bls_field(hash(uint_to_bytes(validator_index) + uint_to_bytes(uint64(counter)))) + ) +``` + +```python +def get_unique_whisk_k(state: BeaconState, validator_index: ValidatorIndex) -> BLSFieldElement: + counter = 0 + while True: + k = get_initial_whisk_k(validator_index, counter) + if is_k_commitment_unique(state, BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)): + return k # unique by trial and error + counter += 1 +``` + +```python +def get_k_commitment(k: BLSFieldElement) -> BLSG1Point: + return BLSG1ScalarMultiply(k, BLS_G1_GENERATOR) +``` + +```python +def get_initial_tracker(k: BLSFieldElement) -> WhiskTracker: + return WhiskTracker(r_G=BLS_G1_GENERATOR, k_r_G=BLSG1ScalarMultiply(k, BLS_G1_GENERATOR)) +``` + +```python +def add_validator_to_registry( + state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> None: + index = get_index_for_new_validator(state) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) + # [New in EIP7441] + 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)) +``` + +### `get_beacon_proposer_index` + +```python +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + assert ( + state.latest_block_header.slot == state.slot + ) # sanity check `process_block_header` has been called + return state.latest_block_header.proposer_index +``` diff --git a/specs/_features/eip7441/fork.md b/specs/_features/eip7441/fork.md new file mode 100644 index 0000000000..7a9b7df835 --- /dev/null +++ b/specs/_features/eip7441/fork.md @@ -0,0 +1,119 @@ +# EIP-7441 -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to EIP-7441](#fork-to-eip-7441) + + + +## Introduction + +This document describes the process of the EIP-7441 upgrade. + +``` +""" +EIP7441_FORK_EPOCH + | cooldown + | | || + v vsvv + --+~~~~~~~~~~~~~~~~~~~~~----+- + shuffling ^ + | + | + proposer selection + candidate selection +""" +``` + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | ------------------------------------- | +| `EIP7441_FORK_VERSION` | `Version('0x08000000')` | +| `EIP7441_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Fork to EIP-7441 + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == EIP7441_FORK_EPOCH`, an irregular state +change is made to upgrade to Whisk. `EIP7441_FORK_EPOCH` must be a multiple of +`RUN_DURATION_IN_EPOCHS`. + +The upgrade occurs after the completion of the inner loop of `process_slots` +that sets `state.slot` equal to `EIP7441_FORK_EPOCH * SLOTS_PER_EPOCH`. + +This ensures that we drop right into the beginning of the shuffling phase but +without `process_whisk_epoch()` triggering for this Whisk run. Hence we handle +all the setup ourselves in `upgrade_to_whisk()` below. + +```python +def upgrade_to_eip7441(pre: capella.BeaconState) -> BeaconState: + # Compute initial unsafe trackers for all validators + ks = [ + get_initial_whisk_k(ValidatorIndex(validator_index), 0) + for validator_index in range(len(pre.validators)) + ] + whisk_k_commitments = [get_k_commitment(k) for k in ks] + whisk_trackers = [get_initial_tracker(k) for k in ks] + + epoch = get_current_epoch(pre) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=EIP7441_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=[], + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=pre.latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + # [New in EIP7441] + whisk_proposer_trackers=[WhiskTracker() for _ in range(PROPOSER_TRACKERS_COUNT)], + # [New in EIP7441] + whisk_candidate_trackers=[WhiskTracker() for _ in range(CANDIDATE_TRACKERS_COUNT)], + # [New in EIP7441] + whisk_trackers=whisk_trackers, + # [New in EIP7441] + whisk_k_commitments=whisk_k_commitments, + ) + + # Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day + # Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection + select_whisk_candidate_trackers(post, Epoch(saturating_sub(epoch, PROPOSER_SELECTION_GAP + 1))) + select_whisk_proposer_trackers(post, epoch) + + # Do a final round of candidate selection. + # We need it so that we have something to shuffle over the upcoming shuffling phase. + select_whisk_candidate_trackers(post, epoch) + + return post +``` diff --git a/specs/_features/eip7732/beacon-chain.md b/specs/_features/eip7732/beacon-chain.md new file mode 100644 index 0000000000..54f668d408 --- /dev/null +++ b/specs/_features/eip7732/beacon-chain.md @@ -0,0 +1,815 @@ +# EIP-7732 -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Payload status](#payload-status) +- [Preset](#preset) + - [Misc](#misc) + - [Domain types](#domain-types) + - [Max operations per block](#max-operations-per-block) +- [Containers](#containers) + - [New containers](#new-containers) + - [`PayloadAttestationData`](#payloadattestationdata) + - [`PayloadAttestation`](#payloadattestation) + - [`PayloadAttestationMessage`](#payloadattestationmessage) + - [`IndexedPayloadAttestation`](#indexedpayloadattestation) + - [`SignedExecutionPayloadHeader`](#signedexecutionpayloadheader) + - [`ExecutionPayloadEnvelope`](#executionpayloadenvelope) + - [`SignedExecutionPayloadEnvelope`](#signedexecutionpayloadenvelope) + - [Modified containers](#modified-containers) + - [`BeaconBlockBody`](#beaconblockbody) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`BeaconState`](#beaconstate) +- [Helper functions](#helper-functions) + - [Math](#math) + - [`bit_floor`](#bit_floor) + - [Misc](#misc-1) + - [`remove_flag`](#remove_flag) + - [Predicates](#predicates) + - [`is_valid_indexed_payload_attestation`](#is_valid_indexed_payload_attestation) + - [`is_parent_block_full`](#is_parent_block_full) + - [Beacon State accessors](#beacon-state-accessors) + - [`get_ptc`](#get_ptc) + - [Modified `get_attesting_indices`](#modified-get_attesting_indices) + - [`get_payload_attesting_indices`](#get_payload_attesting_indices) + - [`get_indexed_payload_attestation`](#get_indexed_payload_attestation) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Withdrawals](#withdrawals) + - [Modified `process_withdrawals`](#modified-process_withdrawals) + - [Execution payload header](#execution-payload-header) + - [New `verify_execution_payload_header_signature`](#new-verify_execution_payload_header_signature) + - [New `process_execution_payload_header`](#new-process_execution_payload_header) + - [Operations](#operations) + - [Modified `process_operations`](#modified-process_operations) + - [Payload Attestations](#payload-attestations) + - [`process_payload_attestation`](#process_payload_attestation) + - [Modified `is_merge_transition_complete`](#modified-is_merge_transition_complete) + - [Modified `validate_merge_block`](#modified-validate_merge_block) + - [Execution payload processing](#execution-payload-processing) + - [New `verify_execution_payload_envelope_signature`](#new-verify_execution_payload_envelope_signature) + - [New `process_execution_payload`](#new-process_execution_payload) +- [Testing](#testing) + - [Modified `is_merge_transition_complete`](#modified-is_merge_transition_complete-1) + + + +## Introduction + +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). + +## Constants + +### Payload status + +| Name | Value | +| ------------------------ | ---------- | +| `PAYLOAD_ABSENT` | `uint8(0)` | +| `PAYLOAD_PRESENT` | `uint8(1)` | +| `PAYLOAD_WITHHELD` | `uint8(2)` | +| `PAYLOAD_INVALID_STATUS` | `uint8(3)` | + +## Preset + +### Misc + +| Name | Value | +| ---------- | ----------------------------------------- | +| `PTC_SIZE` | `uint64(2**9)` (=512) # (New in EIP-7732) | + +### Domain types + +| Name | Value | +| ----------------------- | ---------------------------------------------- | +| `DOMAIN_BEACON_BUILDER` | `DomainType('0x1B000000')` # (New in EIP-7732) | +| `DOMAIN_PTC_ATTESTER` | `DomainType('0x0C000000')` # (New in EIP-7732) | + +### Max operations per block + +| Name | Value | +| -------------------------- | -------------------------------- | +| `MAX_PAYLOAD_ATTESTATIONS` | `2**2` (= 4) # (New in EIP-7732) | + +## Containers + +### New containers + +#### `PayloadAttestationData` + +```python +class PayloadAttestationData(Container): + beacon_block_root: Root + slot: Slot + payload_status: uint8 +``` + +#### `PayloadAttestation` + +```python +class PayloadAttestation(Container): + aggregation_bits: Bitvector[PTC_SIZE] + data: PayloadAttestationData + signature: BLSSignature +``` + +#### `PayloadAttestationMessage` + +```python +class PayloadAttestationMessage(Container): + validator_index: ValidatorIndex + data: PayloadAttestationData + signature: BLSSignature +``` + +#### `IndexedPayloadAttestation` + +```python +class IndexedPayloadAttestation(Container): + attesting_indices: List[ValidatorIndex, PTC_SIZE] + data: PayloadAttestationData + signature: BLSSignature +``` + +#### `SignedExecutionPayloadHeader` + +```python +class SignedExecutionPayloadHeader(Container): + message: ExecutionPayloadHeader + signature: BLSSignature +``` + +#### `ExecutionPayloadEnvelope` + +```python +class ExecutionPayloadEnvelope(Container): + payload: ExecutionPayload + execution_requests: ExecutionRequests + builder_index: ValidatorIndex + beacon_block_root: Root + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + payload_withheld: boolean + state_root: Root +``` + +#### `SignedExecutionPayloadEnvelope` + +```python +class SignedExecutionPayloadEnvelope(Container): + message: ExecutionPayloadEnvelope + signature: BLSSignature +``` + +### Modified containers + +#### `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*: `execution_payload`, `blob_kzg_commitments`, and `execution_requests` +have been removed. + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] + attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + # [New in EIP-7732] + signed_execution_payload_header: SignedExecutionPayloadHeader + # [New in EIP-7732] + payload_attestations: List[PayloadAttestation, MAX_PAYLOAD_ATTESTATIONS] +``` + +#### `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. + +```python +class ExecutionPayloadHeader(Container): + parent_block_hash: Hash32 + parent_block_root: Root + block_hash: Hash32 + gas_limit: uint64 + builder_index: ValidatorIndex + slot: Slot + value: Gwei + blob_kzg_commitments_root: Root +``` + +#### `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. + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + latest_execution_payload_header: ExecutionPayloadHeader + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + deposit_requests_start_index: uint64 + deposit_balance_to_consume: Gwei + exit_balance_to_consume: Gwei + earliest_exit_epoch: Epoch + consolidation_balance_to_consume: Gwei + earliest_consolidation_epoch: Epoch + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + # [New in EIP-7732] + latest_block_hash: Hash32 + # [New in EIP-7732] + latest_full_slot: Slot + # [New in EIP-7732] + latest_withdrawals_root: Root +``` + +## Helper functions + +### Math + +#### `bit_floor` + +```python +def bit_floor(n: uint64) -> uint64: + """ + if ``n`` is not zero, returns the largest power of `2` that is not greater than `n`. + """ + if n == 0: + return 0 + return uint64(1) << (n.bit_length() - 1) +``` + +### Misc + +#### `remove_flag` + +```python +def remove_flag(flags: ParticipationFlags, flag_index: int) -> ParticipationFlags: + flag = ParticipationFlags(2**flag_index) + return flags & ~flag +``` + +### Predicates + +#### `is_valid_indexed_payload_attestation` + +```python +def is_valid_indexed_payload_attestation( + state: BeaconState, indexed_payload_attestation: IndexedPayloadAttestation +) -> bool: + """ + Check if ``indexed_payload_attestation`` is not empty, has sorted and unique indices and has + a valid aggregate signature. + """ + # Verify the data is valid + if indexed_payload_attestation.data.payload_status >= PAYLOAD_INVALID_STATUS: + return False + + # Verify indices are sorted and unique + indices = indexed_payload_attestation.attesting_indices + if len(indices) == 0 or not indices == sorted(set(indices)): + return False + + # Verify aggregate signature + pubkeys = [state.validators[i].pubkey for i in indices] + domain = get_domain(state, DOMAIN_PTC_ATTESTER, None) + signing_root = compute_signing_root(indexed_payload_attestation.data, domain) + return bls.FastAggregateVerify(pubkeys, signing_root, indexed_payload_attestation.signature) +``` + +#### `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. + +```python +def is_parent_block_full(state: BeaconState) -> bool: + return state.latest_execution_payload_header.block_hash == state.latest_block_hash +``` + +### Beacon State accessors + +#### `get_ptc` + +```python +def get_ptc(state: BeaconState, slot: Slot) -> Vector[ValidatorIndex, PTC_SIZE]: + """ + Get the payload timeliness committee for the given ``slot`` + """ + 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] = [] + for idx in range(committees_per_slot): + beacon_committee = get_beacon_committee(state, slot, CommitteeIndex(idx)) + validator_indices += beacon_committee[:members_per_committee] + return validator_indices +``` + +#### Modified `get_attesting_indices` + +`get_attesting_indices` is modified to ignore PTC votes + +```python +def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``. + """ + output: Set[ValidatorIndex] = set() + committee_indices = get_committee_indices(attestation.committee_bits) + committee_offset = 0 + for index in committee_indices: + committee = get_beacon_committee(state, attestation.data.slot, index) + committee_attesters = set( + index + for i, index in enumerate(committee) + if attestation.aggregation_bits[committee_offset + i] + ) + output = output.union(committee_attesters) + committee_offset += len(committee) + + if compute_epoch_at_slot(attestation.data.slot) < EIP7732_FORK_EPOCH: + return output + ptc = get_ptc(state, attestation.data.slot) + return set(i for i in output if i not in ptc) +``` + +#### `get_payload_attesting_indices` + +```python +def get_payload_attesting_indices( + state: BeaconState, slot: Slot, payload_attestation: PayloadAttestation +) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``payload_attestation``. + """ + ptc = get_ptc(state, slot) + return set(index for i, index in enumerate(ptc) if payload_attestation.aggregation_bits[i]) +``` + +#### `get_indexed_payload_attestation` + +```python +def get_indexed_payload_attestation( + state: BeaconState, slot: Slot, payload_attestation: PayloadAttestation +) -> IndexedPayloadAttestation: + """ + Return the indexed payload attestation corresponding to ``payload_attestation``. + """ + attesting_indices = get_payload_attesting_indices(state, slot, payload_attestation) + + return IndexedPayloadAttestation( + attesting_indices=sorted(attesting_indices), + data=payload_attestation.data, + signature=payload_attestation.signature, + ) +``` + +## Beacon chain state transition function + +*Note*: state transition is fundamentally modified in EIP-7732. The full state +transition is broken in two parts, first importing a signed block and then +importing an execution payload. + +The post-state corresponding to a pre-state `state` and a signed beacon block +`signed_block` is defined as `state_transition(state, signed_block)`. State +transitions that trigger an unhandled exception (e.g. a failed `assert` or an +out-of-range list access) are considered invalid. State transitions that cause a +`uint64` overflow or underflow are also considered invalid. + +The post-state corresponding to a pre-state `state` and a signed execution +payload envelope `signed_envelope` is defined as +`process_execution_payload(state, signed_envelope)`. State transitions that +trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list +access) are considered invalid. State transitions that cause an `uint64` +overflow or underflow are also considered invalid. + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + process_withdrawals(state) # [Modified in EIP-7732] + # Removed `process_execution_payload` in EIP-7732 + process_execution_payload_header(state, block) # [New in EIP-7732] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) # [Modified in EIP-7732] + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### Withdrawals + +##### 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. + +```python +def process_withdrawals(state: BeaconState) -> None: + # return early if the parent block was empty + if not is_parent_block_full(state): + return + + withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) + withdrawals_list = List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD](withdrawals) + state.latest_withdrawals_root = hash_tree_root(withdrawals_list) + for withdrawal in withdrawals: + decrease_balance(state, withdrawal.validator_index, withdrawal.amount) + + # Update pending partial withdrawals + state.pending_partial_withdrawals = state.pending_partial_withdrawals[ + partial_withdrawals_count: + ] + + # Update the next withdrawal index if this block contained withdrawals + if len(withdrawals) != 0: + latest_withdrawal = withdrawals[-1] + state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) + + # Update the next validator index to start the next withdrawal sweep + if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + # Next sweep starts after the latest withdrawal's validator index + next_validator_index = ValidatorIndex( + (withdrawals[-1].validator_index + 1) % len(state.validators) + ) + state.next_withdrawal_validator_index = next_validator_index + else: + # Advance sweep by the max length of the sweep if there was not a full set of withdrawals + next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + next_validator_index = ValidatorIndex(next_index % len(state.validators)) + state.next_withdrawal_validator_index = next_validator_index +``` + +#### Execution payload header + +##### New `verify_execution_payload_header_signature` + +```python +def verify_execution_payload_header_signature( + state: BeaconState, signed_header: SignedExecutionPayloadHeader +) -> bool: + # Check the signature + builder = state.validators[signed_header.message.builder_index] + signing_root = compute_signing_root( + signed_header.message, get_domain(state, DOMAIN_BEACON_BUILDER) + ) + return bls.Verify(builder.pubkey, signing_root, signed_header.signature) +``` + +##### New `process_execution_payload_header` + +```python +def process_execution_payload_header(state: BeaconState, block: BeaconBlock) -> None: + # Verify the header signature + signed_header = block.body.signed_execution_payload_header + assert verify_execution_payload_header_signature(state, signed_header) + + # Check that the builder is active non-slashed has funds to cover the bid + header = signed_header.message + builder_index = header.builder_index + builder = state.validators[builder_index] + assert is_active_validator(builder, get_current_epoch(state)) + assert not builder.slashed + amount = header.value + assert state.balances[builder_index] >= amount + + # Verify that the bid is for the current slot + assert header.slot == block.slot + # Verify that the bid is for the right parent block + assert header.parent_block_hash == state.latest_block_hash + assert header.parent_block_root == block.parent_root + + # Transfer the funds from the builder to the proposer + decrease_balance(state, builder_index, amount) + increase_balance(state, block.proposer_index, amount) + + # Cache the signed execution payload header + state.latest_execution_payload_header = header +``` + +#### Operations + +##### Modified `process_operations` + +*Note*: `process_operations` is modified to process PTC attestations + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min( + MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index + ) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + # Removed `process_deposit_request` in EIP-7732 + # Removed `process_withdrawal_request` in EIP-7732 + # Removed `process_consolidation_request` in EIP-7732 + for_ops(body.payload_attestations, process_payload_attestation) # [New in EIP-7732] +``` + +##### Payload Attestations + +###### `process_payload_attestation` + +```python +def process_payload_attestation( + state: BeaconState, payload_attestation: PayloadAttestation +) -> None: + # Check that the attestation is for the parent beacon block + 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 + + # Verify signature + indexed_payload_attestation = get_indexed_payload_attestation( + state, data.slot, payload_attestation + ) + assert is_valid_indexed_payload_attestation(state, indexed_payload_attestation) + + if state.slot % SLOTS_PER_EPOCH == 0: + epoch_participation = state.previous_epoch_participation + else: + epoch_participation = state.current_epoch_participation + + # Return early if the attestation is for the wrong payload status + payload_was_present = data.slot == state.latest_full_slot + voted_present = data.payload_status == PAYLOAD_PRESENT + proposer_reward_denominator = ( + (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + ) + proposer_index = get_beacon_proposer_index(state) + if voted_present != payload_was_present: + # Unset the flags in case they were set by an equivocating ptc attestation + proposer_penalty_numerator = 0 + for index in indexed_payload_attestation.attesting_indices: + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = remove_flag(epoch_participation[index], flag_index) + proposer_penalty_numerator += get_base_reward(state, index) * weight + # Penalize the proposer + proposer_penalty = Gwei(2 * proposer_penalty_numerator // proposer_reward_denominator) + decrease_balance(state, proposer_index, proposer_penalty) + return + + # Reward the proposer and set all the participation flags in case of correct attestations + proposer_reward_numerator = 0 + for index in indexed_payload_attestation.attesting_indices: + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if not has_flag(epoch_participation[index], flag_index): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, proposer_index, proposer_reward) +``` + +#### Modified `is_merge_transition_complete` + +`is_merge_transition_complete` is modified only for testing purposes to add the +blob kzg commitments root for an empty list + +```python +def is_merge_transition_complete(state: BeaconState) -> bool: + header = ExecutionPayloadHeader() + kzgs = List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]() + header.blob_kzg_commitments_root = kzgs.hash_tree_root() + + return state.latest_execution_payload_header != header +``` + +#### Modified `validate_merge_block` + +`validate_merge_block` is modified to use the new +`signed_execution_payload_header` message in the Beacon Block Body + +```python +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if TERMINAL_BLOCK_HASH != Hash32(): + # If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert ( + block.body.signed_execution_payload_header.message.parent_block_hash + == TERMINAL_BLOCK_HASH + ) + return + + # Modified in EIP-7732 + pow_block = get_pow_block(block.body.signed_execution_payload_header.message.parent_block_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) +``` + +### Execution payload processing + +#### New `verify_execution_payload_envelope_signature` + +```python +def verify_execution_payload_envelope_signature( + state: BeaconState, signed_envelope: SignedExecutionPayloadEnvelope +) -> bool: + builder = state.validators[signed_envelope.message.builder_index] + signing_root = compute_signing_root( + signed_envelope.message, get_domain(state, DOMAIN_BEACON_BUILDER) + ) + return bls.Verify(builder.pubkey, signing_root, signed_envelope.signature) +``` + +#### New `process_execution_payload` + +*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, + execution_engine: ExecutionEngine, + verify: bool = True, +) -> None: + # Verify signature + if verify: + assert verify_execution_payload_envelope_signature(state, signed_envelope) + envelope = signed_envelope.message + payload = envelope.payload + # Cache latest block header state root + 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) + + # Verify consistency with the committed header + committed_header = state.latest_execution_payload_header + 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: + # 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 + # Verify consistency of the parent hash with respect to the previous execution payload + assert payload.parent_hash == state.latest_block_hash + # Verify prev_randao + assert payload.prev_randao == get_randao_mix(state, get_current_epoch(state)) + # Verify timestamp + assert payload.timestamp == compute_time_at_slot(state, state.slot) + # 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) + for commitment in envelope.blob_kzg_commitments + ] + requests = envelope.execution_requests + 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=requests, + ) + ) + + # Process Electra operations + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(requests.deposits, process_deposit_request) + for_ops(requests.withdrawals, process_withdrawal_request) + for_ops(requests.consolidations, process_consolidation_request) + + # Cache the execution payload header and proposer + state.latest_block_hash = payload.block_hash + state.latest_full_slot = state.slot + + # Verify the state root + if verify: + assert envelope.state_root == hash_tree_root(state) +``` + +## Testing + +### Modified `is_merge_transition_complete` + +The function `is_merge_transition_complete` is modified for test purposes only +to include the hash tree root of the empty KZG commitment list + +```python +def is_merge_transition_complete(state: BeaconState) -> bool: + header = ExecutionPayloadHeader() + kzgs = List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK]() + header.blob_kzg_commitments_root = kzgs.hash_tree_root() + + return state.latest_execution_payload_header != header +``` diff --git a/specs/_features/eip7732/builder.md b/specs/_features/eip7732/builder.md new file mode 100644 index 0000000000..b33e90f257 --- /dev/null +++ b/specs/_features/eip7732/builder.md @@ -0,0 +1,195 @@ +# EIP-7732 -- Honest Builder + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Builders attributions](#builders-attributions) + - [Constructing the payload bid](#constructing-the-payload-bid) + - [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars) + - [Constructing the execution payload envelope](#constructing-the-execution-payload-envelope) + - [Honest payload withheld messages](#honest-payload-withheld-messages) + + + +## Introduction + +This is an accompanying document which describes the expected actions of a +"builder" participating in the Ethereum proof-of-stake protocol. + +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. + +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. + +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. +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`. +6. Set `header.builder_index` to be the validator index of the builder + performing these actions. +7. Set `header.slot` to be the slot for which this bid is aimed. This slot + **MUST** be either the current slot or the next slot. +8. Set `header.value` to be the value that the builder will pay the proposer if + the bid is accepted. The builder **MUST** have balance enough to fulfill this + bid. +9. Set `header.kzg_commitments_root` to be the `hash_tree_root` of the + `blobsbundle.commitments` field returned by `engine_getPayloadV4`. + +After building the `header`, the builder obtains a `signature` of the header by +using + +```python +def get_execution_payload_header_signature( + state: BeaconState, header: ExecutionPayloadHeader, privkey: int +) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(header.slot)) + signing_root = compute_signing_root(header, domain) + 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. + +### 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. + +```python +def get_blob_sidecars( + signed_block: SignedBeaconBlock, + blobs: Sequence[Blob], + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], + blob_kzg_proofs: Sequence[KZGProof], +) -> Sequence[BlobSidecar]: + block = signed_block.message + block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=block.state_root, + body_root=hash_tree_root(block.body), + ) + signed_block_header = SignedBeaconBlockHeader( + message=block_header, signature=signed_block.signature + ) + sidecars: List[BlobSidecar] = [] + for index, blob in enumerate(blobs): + proof = compute_merkle_proof( + blob_kzg_commitments, + get_generalized_index(List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], index), + ) + proof += compute_merkle_proof( + block.body, + get_generalized_index( + BeaconBlockBody, + "signed_execution_payload_header", + "message", + "blob_kzg_commitments_root", + ), + ) + sidecars.append( + BlobSidecar( + index=index, + blob=blob, + kzg_commitment=blob_kzg_commitments[index], + kzg_proof=blob_kzg_proofs[index], + signed_block_header=signed_block_header, + kzg_commitment_inclusion_proof=proof, + ) + ) + return sidecars +``` + +### 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*. + +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`. +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_withheld` 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)`. + +After preparing the `envelope` the builder should sign the envelope using: + +```python +def get_execution_payload_envelope_signature( + state: BeaconState, envelope: ExecutionPayloadEnvelope, privkey: int +) -> BLSSignature: + domain = get_domain(state, DOMAIN_BEACON_BUILDER, compute_epoch_at_slot(state.slot)) + 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. + +### 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. diff --git a/specs/_features/eip7732/fork-choice.md b/specs/_features/eip7732/fork-choice.md new file mode 100644 index 0000000000..bd512e3bc4 --- /dev/null +++ b/specs/_features/eip7732/fork-choice.md @@ -0,0 +1,664 @@ +# EIP-7732 -- Fork Choice + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Constants](#constants) +- [Containers](#containers) + - [New `ChildNode`](#new-childnode) +- [Helpers](#helpers) + - [Modified `LatestMessage`](#modified-latestmessage) + - [Modified `update_latest_messages`](#modified-update_latest_messages) + - [Modified `Store`](#modified-store) + - [Modified `get_forkchoice_store`](#modified-get_forkchoice_store) + - [`notify_ptc_messages`](#notify_ptc_messages) + - [`is_payload_present`](#is_payload_present) + - [`is_parent_node_full`](#is_parent_node_full) + - [Modified `get_ancestor`](#modified-get_ancestor) + - [Modified `get_checkpoint_block`](#modified-get_checkpoint_block) + - [`is_supporting_vote`](#is_supporting_vote) + - [New `compute_proposer_boost`](#new-compute_proposer_boost) + - [New `compute_withhold_boost`](#new-compute_withhold_boost) + - [New `compute_reveal_boost`](#new-compute_reveal_boost) + - [Modified `get_weight`](#modified-get_weight) + - [Modified `get_head`](#modified-get_head) +- [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [Modified `on_block`](#modified-on_block) +- [New fork-choice handlers](#new-fork-choice-handlers) + - [New `on_execution_payload`](#new-on_execution_payload) + - [`seconds_into_slot`](#seconds_into_slot) + - [Modified `on_tick_per_slot`](#modified-on_tick_per_slot) + - [`on_payload_attestation_message`](#on_payload_attestation_message) + - [Modified `validate_merge_block`](#modified-validate_merge_block) + + + +## Introduction + +This is the modification of the fork choice accompanying the EIP-7732 upgrade. + +## Constants + +| Name | Value | +| ------------------------------ | ----------------------------- | +| `PAYLOAD_TIMELY_THRESHOLD` | `PTC_SIZE // 2` (= 256) | +| `INTERVALS_PER_SLOT` | `4` # [modified in EIP-7732] | +| `PROPOSER_SCORE_BOOST_EIP7732` | `20` # [modified in EIP-7732] | +| `PAYLOAD_WITHHOLD_BOOST` | `40` | +| `PAYLOAD_REVEAL_BOOST` | `40` | + +## Containers + +### New `ChildNode` + +Auxiliary class to consider `(block, slot, bool)` LMD voting + +```python +class ChildNode(Container): + root: Root + slot: Slot + is_payload_present: boolean +``` + +## Helpers + +### Modified `LatestMessage` + +*Note*: The class is modified to keep track of the slot instead of the epoch. + +```python +@dataclass(eq=True, frozen=True) +class LatestMessage(object): + slot: Slot + root: Root +``` + +### 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`. + +```python +def update_latest_messages( + store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation +) -> None: + slot = attestation.data.slot + beacon_block_root = attestation.data.beacon_block_root + non_equivocating_attesting_indices = [ + i for i in attesting_indices if i not in store.equivocating_indices + ] + for i in non_equivocating_attesting_indices: + if i not in store.latest_messages or slot > store.latest_messages[i].slot: + 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. + +```python +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + unrealized_justified_checkpoint: Checkpoint + unrealized_finalized_checkpoint: Checkpoint + proposer_boost_root: Root + payload_withhold_boost_root: Root # [New in EIP-7732] + payload_withhold_boost_full: boolean # [New in EIP-7732] + payload_reveal_boost_root: Root # [New in EIP-7732] + equivocating_indices: Set[ValidatorIndex] + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + block_timeliness: Dict[Root, boolean] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) + execution_payload_states: Dict[Root, BeaconState] = field( + default_factory=dict + ) # [New in EIP-7732] + ptc_vote: Dict[Root, Vector[uint8, PTC_SIZE]] = field(default_factory=dict) # [New in EIP-7732] +``` + +### Modified `get_forkchoice_store` + +```python +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + unrealized_justified_checkpoint=justified_checkpoint, + unrealized_finalized_checkpoint=finalized_checkpoint, + proposer_boost_root=proposer_boost_root, + payload_withhold_boost_root=proposer_boost_root, # [New in EIP-7732] + payload_withhold_boost_full=True, # [New in EIP-7732] + payload_reveal_boost_root=proposer_boost_root, # [New in EIP-7732] + equivocating_indices=set(), + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + unrealized_justifications={anchor_root: justified_checkpoint}, + execution_payload_states={anchor_root: copy(anchor_state)}, # [New in EIP-7732] + ptc_vote={anchor_root: Vector[uint8, PTC_SIZE]()}, + ) +``` + +### `notify_ptc_messages` + +```python +def notify_ptc_messages( + store: Store, state: BeaconState, payload_attestations: Sequence[PayloadAttestation] +) -> None: + """ + Extracts a list of ``PayloadAttestationMessage`` from ``payload_attestations`` and updates the store with them + These Payload attestations are assumed to be in the beacon block hence signature verification is not needed + """ + if state.slot == 0: + return + for payload_attestation in payload_attestations: + indexed_payload_attestation = get_indexed_payload_attestation( + state, Slot(state.slot - 1), payload_attestation + ) + for idx in indexed_payload_attestation.attesting_indices: + on_payload_attestation_message( + store, + PayloadAttestationMessage( + validator_index=idx, + data=payload_attestation.data, + signature=BLSSignature(), + is_from_block=True, + ), + ) +``` + +### `is_payload_present` + +```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 + by the PTC + """ + # The beacon block root must be known + assert beacon_block_root in store.ptc_vote + return store.ptc_vote[beacon_block_root].count(PAYLOAD_PRESENT) > PAYLOAD_TIMELY_THRESHOLD +``` + +### `is_parent_node_full` + +```python +def is_parent_node_full(store: Store, block: BeaconBlock) -> bool: + parent = store.blocks[block.parent_root] + parent_block_hash = block.body.signed_execution_payload_header.message.parent_block_hash + message_block_hash = parent.body.signed_execution_payload_header.message.block_hash + 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. + +```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. + """ + block = store.blocks[root] + if block.slot <= slot: + return ChildNode(root=root, slot=slot, is_payload_present=is_payload_present(store, root)) + + parent = store.blocks[block.parent_root] + if parent.slot > slot: + return get_ancestor(store, block.parent_root, slot) + return ChildNode( + root=block.parent_root, + slot=parent.slot, + is_payload_present=is_parent_node_full(store, block), + ) +``` + +### Modified `get_checkpoint_block` + +*Note*: `get_checkpoint_block` is modified to use the new `get_ancestor` + +```python +def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root: + """ + Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root`` + """ + epoch_first_slot = compute_start_slot_at_epoch(epoch) + return get_ancestor(store, root, epoch_first_slot).root +``` + +### `is_supporting_vote` + +```python +def is_supporting_vote(store: Store, node: ChildNode, message: LatestMessage) -> bool: + """ + Returns whether a vote for ``message.root`` supports the chain containing the beacon block ``node.root`` with the + payload contents indicated by ``node.is_payload_present`` as head during slot ``node.slot``. + """ + 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. + return node.slot <= message.slot + message_block = store.blocks[message.root] + if node.slot >= message_block.slot: + return False + ancestor = get_ancestor(store, message.root, node.slot) + return (node.root == ancestor.root) and (node.is_payload_present == ancestor.is_payload_present) +``` + +### 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. + +```python +def compute_proposer_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei: + if store.proposer_boost_root == Root(): + return Gwei(0) + ancestor = get_ancestor(store, store.proposer_boost_root, node.slot) + if ancestor.root != node.root: + return Gwei(0) + proposer_boost_slot = store.blocks[store.proposer_boost_root].slot + # Proposer boost is not applied after skipped slots + if node.slot > proposer_boost_slot: + return Gwei(0) + if (node.slot < proposer_boost_slot) and ( + ancestor.is_payload_present != node.is_payload_present + ): + return Gwei(0) + committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH + return (committee_weight * PROPOSER_SCORE_BOOST_EIP7732) // 100 +``` + +### New `compute_withhold_boost` + +This is a similar helper that applies for the withhold boost. In this case this +always takes into account the reveal status. + +```python +def compute_withhold_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei: + if store.payload_withhold_boost_root == Root(): + return Gwei(0) + ancestor = get_ancestor(store, store.payload_withhold_boost_root, node.slot) + if ancestor.root != node.root: + return Gwei(0) + if node.slot >= store.blocks[store.payload_withhold_boost_root].slot: + ancestor.is_payload_present = store.payload_withhold_boost_full + if ancestor.is_payload_present != node.is_payload_present: + return Gwei(0) + + committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH + return (committee_weight * PAYLOAD_WITHHOLD_BOOST) // 100 +``` + +### 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. + +```python +def compute_reveal_boost(store: Store, state: BeaconState, node: ChildNode) -> Gwei: + if store.payload_reveal_boost_root == Root(): + return Gwei(0) + ancestor = get_ancestor(store, store.payload_reveal_boost_root, node.slot) + if ancestor.root != node.root: + return Gwei(0) + if node.slot >= store.blocks[store.payload_reveal_boost_root].slot: + ancestor.is_payload_present = True + if ancestor.is_payload_present != node.is_payload_present: + return Gwei(0) + committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH + return (committee_weight * PAYLOAD_REVEAL_BOOST) // 100 +``` + +### 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. + +```python +def get_weight(store: Store, node: ChildNode) -> Gwei: + state = store.checkpoint_states[store.justified_checkpoint] + unslashed_and_active_indices = [ + i + for i in get_active_validator_indices(state, get_current_epoch(state)) + if not state.validators[i].slashed + ] + attestation_score = Gwei( + sum( + state.validators[i].effective_balance + for i in unslashed_and_active_indices + if ( + i in store.latest_messages + and i not in store.equivocating_indices + and is_supporting_vote(store, node, store.latest_messages[i]) + ) + ) + ) + + # Compute boosts + proposer_score = compute_proposer_boost(store, state, node) + builder_reveal_score = compute_reveal_boost(store, state, node) + builder_withhold_score = compute_withhold_boost(store, state, node) + + return attestation_score + proposer_score + builder_reveal_score + builder_withhold_score +``` + +### Modified `get_head` + +*Note*: `get_head` is a modified to use the new `get_weight` function. It +returns the `ChildNode` object corresponding to the head block. + +```python +def get_head(store: Store) -> ChildNode: + # Get filtered block tree that only includes viable branches + blocks = get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + justified_root = store.justified_checkpoint.root + justified_block = store.blocks[justified_root] + justified_slot = justified_block.slot + justified_full = is_payload_present(store, justified_root) + best_child = ChildNode( + root=justified_root, slot=justified_slot, is_payload_present=justified_full + ) + 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 ( + 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 + 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, + ) + ] + # Sort by latest attesting balance with + # Ties broken by the block's slot + # Ties are broken by the PTC vote + # 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), + blocks[child.root].slot, + is_payload_present(store, child.root), + child.is_payload_present, + child.root, + ), + ) + if new_best_child.root == best_child.root and new_best_child.slot >= highest_child_slot: + return new_best_child + best_child = new_best_child +``` + +## Updated fork-choice handlers + +### 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. + +```python +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + + # Check if this blocks builds on empty or full parent block + parent_block = store.blocks[block.parent_root] + header = block.body.signed_execution_payload_header.message + parent_header = parent_block.body.signed_execution_payload_header.message + # Make a copy of the state to avoid mutability issues + if is_parent_node_full(store, block): + assert block.parent_root in store.execution_payload_states + state = copy(store.execution_payload_states[block.parent_root]) + else: + assert header.parent_block_hash == parent_header.parent_block_hash + state = copy(store.block_states[block.parent_root]) + + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. + current_slot = get_current_slot(store) + assert current_slot >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block + + # Check the block is valid and compute the post-state + block_root = hash_tree_root(block) + state_transition(state, signed_block, True) + + # Add new block to the store + store.blocks[block_root] = block + # Add new state for this block to the store + store.block_states[block_root] = state + # Add a new PTC voting for this block to the store + store.ptc_vote[block_root] = [PAYLOAD_ABSENT] * PTC_SIZE + + # Notify the store about the payload_attestations in the block + notify_ptc_messages(store, state, block.body.payload_attestations) + # Add proposer score boost if the block is timely + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely + + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) + + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) + + # Eagerly compute unrealized justification and finality. + compute_pulled_up_tip(store, block_root) +``` + +## New fork-choice handlers + +### New `on_execution_payload` + +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 + # The corresponding beacon block root needs to be known + assert envelope.beacon_block_root in store.block_states + + # Check if blob data is available + # If not, this payload MAY be queued and subsequently considered when blob data becomes available + assert is_data_available(envelope.beacon_block_root, envelope.blob_kzg_commitments) + + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[envelope.beacon_block_root]) + + # Process the execution payload + process_execution_payload(state, signed_envelope, EXECUTION_ENGINE) + + # Add new state for this payload to the store + store.execution_payload_states[envelope.beacon_block_root] = state +``` + +### `seconds_into_slot` + +```python +def seconds_into_slot(store: Store) -> uint64: + return (store.time - store.genesis_time) % SECONDS_PER_SLOT +``` + +### Modified `on_tick_per_slot` + +Modified to reset the payload boost roots + +```python +def on_tick_per_slot(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # Update store time + store.time = time + + current_slot = get_current_slot(store) + + # If this is a new slot, reset store.proposer_boost_root + if current_slot > previous_slot: + store.proposer_boost_root = Root() + 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() + store.payload_withhold_boost_full = False + store.payload_reveal_boost_root = Root() + + # If a new epoch, pull-up justification and finalization from previous epoch + if current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0: + update_checkpoints( + store, store.unrealized_justified_checkpoint, store.unrealized_finalized_checkpoint + ) +``` + +### `on_payload_attestation_message` + +```python +def on_payload_attestation_message( + store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False +) -> None: + """ + Run ``on_payload_attestation_message`` upon receiving a new ``ptc_message`` directly on the wire. + """ + # The beacon block root must be known + data = ptc_message.data + # PTC attestation must be for a known block. If block is unknown, delay consideration until the block is found + state = store.block_states[data.beacon_block_root] + ptc = get_ptc(state, data.slot) + # PTC votes can only change the vote for their assigned beacon block, return early otherwise + if data.slot != state.slot: + return + # Check that the attester is from the PTC + assert ptc_message.validator_index in ptc + + # Verify the signature and check that its for the current slot if it is coming from the wire + if not is_from_block: + # Check that the attestation is for the current slot + assert data.slot == get_current_slot(store) + # Verify the signature + assert is_valid_indexed_payload_attestation( + state, + IndexedPayloadAttestation( + attesting_indices=[ptc_message.validator_index], + data=data, + signature=ptc_message.signature, + ), + ) + # Update the ptc vote for the block + 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 + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + if is_from_block and time_into_slot >= SECONDS_PER_SLOT // INTERVALS_PER_SLOT: + return + + # Update the payload boosts if threshold has been achieved + if ptc_vote.count(PAYLOAD_PRESENT) > PAYLOAD_TIMELY_THRESHOLD: + store.payload_reveal_boost_root = data.beacon_block_root + if ptc_vote.count(PAYLOAD_WITHHELD) > PAYLOAD_TIMELY_THRESHOLD: + block = store.blocks[data.beacon_block_root] + store.payload_withhold_boost_root = block.parent_root + store.payload_withhold_boost_full = is_parent_node_full(store, block) +``` + +### Modified `validate_merge_block` + +The function `validate_merge_block` is modified for test purposes + +```python +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if TERMINAL_BLOCK_HASH != Hash32(): + # If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert ( + block.body.signed_execution_payload_header.message.parent_block_hash + == TERMINAL_BLOCK_HASH + ) + return + + pow_block = get_pow_block(block.body.signed_execution_payload_header.message.parent_block_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) +``` diff --git a/specs/_features/eip7732/fork.md b/specs/_features/eip7732/fork.md new file mode 100644 index 0000000000..d5e1eb707d --- /dev/null +++ b/specs/_features/eip7732/fork.md @@ -0,0 +1,127 @@ +# EIP-7732 -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to EIP-7732](#fork-to-eip-7732) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the EIP-7732 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | ------------------------------------- | +| `EIP7732_FORK_VERSION` | `Version('0x09000000')` | +| `EIP7732_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= EIP7732_FORK_EPOCH: + return EIP7732_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to EIP-7732 + +### Fork trigger + +The fork is triggered at epoch `EIP7732_FORK_EPOCH`. The EIP may be combined +with other consensus-layer upgrade. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == EIP7732_FORK_EPOCH`, an irregular state +change is made to upgrade to EIP-7732. + +```python +def upgrade_to_eip7732(pre: electra.BeaconState) -> BeaconState: + epoch = electra.get_current_epoch(pre) + + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in EIP-7732] + current_version=EIP7732_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # [Modified in EIP-7732] + latest_execution_payload_header=ExecutionPayloadHeader(), + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + # [New in EIP-7732] + latest_block_hash=pre.latest_execution_payload_header.block_hash, + # [New in EIP-7732] + latest_full_slot=pre.slot, + # [New in EIP-7732] + latest_withdrawals_root=Root(), + ) + + return post +``` diff --git a/specs/_features/eip7732/p2p-interface.md b/specs/_features/eip7732/p2p-interface.md new file mode 100644 index 0000000000..e7cb747444 --- /dev/null +++ b/specs/_features/eip7732/p2p-interface.md @@ -0,0 +1,387 @@ +# EIP-7732 -- Networking + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Modification in EIP-7732](#modification-in-eip-7732) + - [Preset](#preset) + - [Configuration](#configuration) + - [Containers](#containers) + - [`BlobSidecar`](#blobsidecar) + - [Helpers](#helpers) + - [Modified `verify_blob_sidecar_inclusion_proof`](#modified-verify_blob_sidecar_inclusion_proof) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`execution_payload`](#execution_payload) + - [`payload_attestation_message`](#payload_attestation_message) + - [`execution_payload_header`](#execution_payload_header) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1) + - [ExecutionPayloadEnvelopesByRange v1](#executionpayloadenvelopesbyrange-v1) + - [ExecutionPayloadEnvelopesByRoot v1](#executionpayloadenvelopesbyroot-v1) + + + +## 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 + +*[Modified in EIP-7732]* + +| Name | Value | Description | +| ---------------------------------------------- | ------------ | ----------------------------------------------------------- | +| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732` | `21` **TBD** | Merkle proof depth for the `blob_kzg_commitments` list item | + +### Configuration + +*[New in EIP7732]* + +| Name | Value | Description | +| ---------------------- | -------------- | ----------------------------------------------------------------- | +| `MAX_REQUEST_PAYLOADS` | `2**7` (= 128) | Maximum number of execution payload envelopes in a single request | + +### Containers + +#### `BlobSidecar` + +The `BlobSidecar` container is modified indirectly because the constant +`KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` is modified. + +```python +class BlobSidecar(Container): + index: BlobIndex + blob: Blob + kzg_commitment: KZGCommitment + kzg_proof: KZGProof + signed_block_header: SignedBeaconBlockHeader + kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732] +``` + +#### Helpers + +##### 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. + +```python +def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool: + inner_gindex = get_generalized_index( + List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], blob_sidecar.index + ) + outer_gindex = get_generalized_index( + BeaconBlockBody, + "signed_execution_payload_header", + "message", + "blob_kzg_commitments_root", + ) + gindex = get_subtree_index(concat_generalized_indices(outer_gindex, inner_gindex)) + + return is_valid_merkle_branch( + leaf=blob_sidecar.kzg_commitment.hash_tree_root(), + branch=blob_sidecar.kzg_commitment_inclusion_proof, + depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732, + index=gindex, + root=blob_sidecar.signed_block_header.message.body_root, + ) +``` + +### The gossip domain: gossipsub + +Some gossip meshes are upgraded in the fork of EIP-7732 to support upgraded +types. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. + +The `beacon_block` topic is updated to support the modified type + +| Name | Message Type | +| -------------- | ------------------------------------------ | +| `beacon_block` | `SignedBeaconBlock` [modified in EIP-7732] | + +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: + +| Name | Message Type | +| ----------------------------- | -------------------------------------------------- | +| `execution_payload_header` | `SignedExecutionPayloadHeader` [New in EIP-7732] | +| `execution_payload` | `SignedExecutionPayloadEnvelope` [New in EIP-7732] | +| `payload_attestation_message` | `PayloadAttestationMessage` [New in EIP-7732] | + +##### Global topics + +EIP-7732 introduces new global topics for execution header, execution payload +and payload attestation. + +###### `beacon_block` + +[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). + +There are no new validations for this topic. However, all validations with +regards to the `ExecutionPayload` are removed: + +- _[REJECT]_ The length of KZG commitments is less than or equal to the + limitation defined in Consensus Layer -- i.e. validate that + `len(signed_beacon_block.message.body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK` +- _[REJECT]_ The block's execution payload timestamp is correct with respect to + the slot -- i.e. + `execution_payload.timestamp == compute_time_at_slot(state, block.slot)`. +- If `execution_payload` verification of block's parent by an execution node is + *not* complete: + - [REJECT] The block's parent (defined by `block.parent_root`) passes all + validation (excluding execution node verification of the + `block.body.execution_payload`). +- otherwise: + - [IGNORE] The block's parent (defined by `block.parent_root`) passes all + validation (including execution node verification of the + `block.body.execution_payload`). +- [REJECT] The block's parent (defined by `block.parent_root`) passes + validation. + +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 parent (defined by `block.parent_root`) passes + validation. + +###### `execution_payload` + +This topic is used to propagate execution payload messages as +`SignedExecutionPayloadEnvelope`. + +The following validations MUST pass before forwarding the +`signed_execution_payload_envelope` on the network, assuming the alias +`envelope = signed_execution_payload_envelope.message`, +`payload = payload_envelope.payload`: + +- _[IGNORE]_ The envelope's block root `envelope.block_root` has been seen (via + gossip or 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 `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]_ `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. + +###### `payload_attestation_message` + +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 + gossip or 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`. + +The following validations MUST pass before forwarding the +`signed_execution_payload_header` on the network, assuming the alias +`header = signed_execution_payload_header.message`: + +- _[IGNORE]_ this is the first signed bid seen with a valid signature from the + given builder for this slot. +- _[IGNORE]_ this bid is the highest value bid seen for the pair of the + corresponding slot and the given parent block hash. +- _[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. +- _[REJECT]_ The builder signature, + `signed_execution_payload_header_envelope.signature`, is valid with respect to + the `header_envelope.builder_index`. + +### The Req/Resp domain + +#### Messages + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `EIP7732_FORK_VERSION` | `eip7732.SignedBeaconBlock` | + +##### BlobSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/` + + + +| `fork_version` | Chunk SSZ type | +| ---------------------- | --------------------- | +| `DENEB_FORK_VERSION` | `deneb.BlobSidecar` | +| `EIP7732_FORK_VERSION` | `eip7732.BlobSidecar` | + +##### ExecutionPayloadEnvelopesByRange v1 + +**Protocol ID:** +`/eth2/beacon_chain/req/execution_payload_envelopes_by_range/1/` + +*[New in EIP-7732]* + +Request Content: + +``` +( + start_slot: Slot + count: uint64 +) +``` + +Response Content: + +``` +( + List[SignedExecutionPayloadEnvelope, MAX_REQUEST_BLOCKS_DENEB] +) +``` + +Specifications of req\\response methods are equivalent to +[BeaconBlocksByRange v2](#beaconblocksbyrange-v2), with the only difference +being the response content type. + +For each `response_chunk`, a `ForkDigest`-context based on +`compute_fork_version(compute_epoch_at_slot(signed_execution_payload_envelop.message.slot))` +is used to select the fork namespace of the Response type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ---------------------- | ---------------------------------------- | +| `EIP7732_FORK_VERSION` | `eip7732.SignedExecutionPayloadEnvelope` | + +##### ExecutionPayloadEnvelopesByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/execution_payload_envelopes_by_root/1/` + +The `` field is calculated as +`context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ---------------------- | ---------------------------------------- | +| `EIP7732_FORK_VERSION` | `eip7732.SignedExecutionPayloadEnvelope` | + +Request Content: + +``` +( + List[Root, MAX_REQUEST_PAYLOADS] +) +``` + +Response Content: + +``` +( + List[SignedExecutionPayloadEnvelope, MAX_REQUEST_PAYLOADS] +) +``` + +Requests execution payload envelopes by +`signed_execution_payload_envelope.message.block_root`. The response is a list +of `SignedExecutionPayloadEnvelope` whose length is less than or equal to the +number of requested execution payload envelopes. It may be less in the case that +the responding peer is missing payload envelopes. + +No more than `MAX_REQUEST_PAYLOADS` may be requested at a time. + +ExecutionPayloadEnvelopesByRoot is primarily used to recover recent execution +payload envelopes (e.g. when receiving a payload attestation with revealed +status as true but never received a payload). + +The request MUST be encoded as an SSZ-field. + +The response MUST consist of zero or more `response_chunk`. Each successful +`response_chunk` MUST contain a single `SignedExecutionPayloadEnvelope` payload. + +Clients MUST support requesting payload envelopes since the latest finalized +epoch. + +Clients MUST respond with at least one payload envelope, if they have it. +Clients MAY limit the number of payload envelopes in the response. diff --git a/specs/_features/eip7732/validator.md b/specs/_features/eip7732/validator.md new file mode 100644 index 0000000000..30cd6bad84 --- /dev/null +++ b/specs/_features/eip7732/validator.md @@ -0,0 +1,196 @@ +# EIP-7732 -- Honest Validator + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Validator assignment](#validator-assignment) + - [Lookahead](#lookahead) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Attestation](#attestation) + - [Sync Committee participations](#sync-committee-participations) + - [Block proposal](#block-proposal) + - [Constructing the new `signed_execution_payload_header` field in `BeaconBlockBody`](#constructing-the-new-signed_execution_payload_header-field-in-beaconblockbody) + - [Constructing the new `payload_attestations` field in `BeaconBlockBody`](#constructing-the-new-payload_attestations-field-in-beaconblockbody) + - [Blob sidecars](#blob-sidecars) + - [Payload timeliness attestation](#payload-timeliness-attestation) + - [Constructing a payload attestation](#constructing-a-payload-attestation) + + + +## Introduction + +This document represents the changes and additions to the Honest validator guide +included in the EIP-7732 fork. + +## Validator assignment + +A validator may be a member of the new Payload Timeliness Committee (PTC) for a +given slot. To check for PTC assignments the validator uses the helper +`get_ptc_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`. + +PTC committee selection is only stable within the context of the current and +next epoch. + +```python +def get_ptc_assignment( + state: BeaconState, epoch: Epoch, 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. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + if validator_index in get_ptc(state, Slot(slot)): + return Slot(slot) + return None +``` + +### Lookahead + +[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. + +## 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. + +### Attestation + +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`. + +### Block proposal + +Validators are still expected to propose `SignedBeaconBlock` at the beginning of +any slot during which `is_proposer(state, validator_index)` returns `true`. The +mechanism to prepare this beacon block and related sidecars differs from +previous forks as follows + +#### 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 + - 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 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 +- 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. + +#### 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. + +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` + +#### Constructing a payload attestation + +If a validator is in the payload attestation committee for the current slot (as +obtained from `get_ptc_assignment` above) then the validator should prepare a +`PayloadAttestationMessage` for the current slot, 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. +- 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.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`. + - If no `SignedExecutionPayloadEnvelope` has been seen referencing the block + `data.beacon_block_root` set to `PAYLOAD_ABSENT`. +- 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. + +```python +def get_payload_attestation_message_signature( + state: BeaconState, attestation: PayloadAttestationMessage, privkey: int +) -> BLSSignature: + domain = get_domain(state, DOMAIN_PTC_ATTESTER, compute_epoch_at_slot(attestation.data.slot)) + signing_root = compute_signing_root(attestation.data, domain) + 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`. diff --git a/specs/_features/eip7805/beacon-chain.md b/specs/_features/eip7805/beacon-chain.md new file mode 100644 index 0000000000..85698762e0 --- /dev/null +++ b/specs/_features/eip7805/beacon-chain.md @@ -0,0 +1,273 @@ +# EIP-7805 -- The Beacon Chain + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Domain types](#domain-types) +- [Preset](#preset) + - [Inclusion List Committee](#inclusion-list-committee) +- [Containers](#containers) + - [New containers](#new-containers) + - [`InclusionList`](#inclusionlist) + - [`SignedInclusionList`](#signedinclusionlist) + - [Predicates](#predicates) + - [New `is_valid_inclusion_list_signature`](#new-is_valid_inclusion_list_signature) + - [Beacon State accessors](#beacon-state-accessors) + - [New `get_inclusion_list_committee`](#new-get_inclusion_list_committee) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution engine](#execution-engine) + - [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) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + + + +## Introduction + +This is the beacon chain specification to add EIP-7805 / fork-choice enforced, +committee-based inclusion list (FOCIL) mechanism to allow forced transaction +inclusion. Refers to the following posts: + +- [Fork-Choice enforced Inclusion Lists (FOCIL): A simple committee-based inclusion list proposal](https://ethresear.ch/t/fork-choice-enforced-inclusion-lists-focil-a-simple-committee-based-inclusion-list-proposal/19870/1) +- [FOCIL CL & EL workflow](https://ethresear.ch/t/focil-cl-el-workflow/20526) + *Note*: This specification is built upon + [Electra](../../electra/beacon-chain.md) and is under active development. + +## Constants + +### Domain types + +| Name | Value | +| --------------------------------- | -------------------------- | +| `DOMAIN_INCLUSION_LIST_COMMITTEE` | `DomainType('0x0C000000')` | + +## Preset + +### Inclusion List Committee + +| Name | Value | +| ------------------------------- | -------------------- | +| `INCLUSION_LIST_COMMITTEE_SIZE` | `uint64(2**4)` (=16) | + +## Containers + +### New containers + +#### `InclusionList` + +```python +class InclusionList(Container): + slot: Slot + validator_index: ValidatorIndex + inclusion_list_committee_root: Root + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] +``` + +#### `SignedInclusionList` + +```python +class SignedInclusionList(Container): + message: InclusionList + signature: BLSSignature +``` + +### Predicates + +#### New `is_valid_inclusion_list_signature` + +```python +def is_valid_inclusion_list_signature( + state: BeaconState, signed_inclusion_list: SignedInclusionList +) -> bool: + """ + Check if ``signed_inclusion_list`` has a valid signature. + """ + message = signed_inclusion_list.message + index = message.validator_index + pubkey = state.validators[index].pubkey + domain = get_domain(state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(message.slot)) + signing_root = compute_signing_root(message, domain) + return bls.Verify(pubkey, signing_root, signed_inclusion_list.signature) +``` + +### Beacon State accessors + +#### New `get_inclusion_list_committee` + +```python +def get_inclusion_list_committee( + state: BeaconState, slot: Slot +) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]: + epoch = compute_epoch_at_slot(slot) + seed = get_seed(state, epoch, DOMAIN_INCLUSION_LIST_COMMITTEE) + indices = get_active_validator_indices(state, epoch) + start = (slot % SLOTS_PER_EPOCH) * INCLUSION_LIST_COMMITTEE_SIZE + end = start + INCLUSION_LIST_COMMITTEE_SIZE + return [ + indices[compute_shuffled_index(uint64(i % len(indices)), uint64(len(indices)), seed)] + for i in range(start, end) + ] +``` + +## Beacon chain state transition function + +### Execution engine + +#### Request data + +##### Modified `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload + versioned_hashes: Sequence[VersionedHash] + parent_beacon_block_root: Root + execution_requests: ExecutionRequests + inclusion_list_transactions: Sequence[Transaction] # [New in EIP-7805] +``` + +#### Engine APIs + +##### Modified `is_valid_block_hash` + +*Note*: The function `is_valid_block_hash` is modified to include the additional +`inclusion_list_transactions`. + +```python +def is_valid_block_hash( + self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes], + inclusion_list_transactions: Sequence[Transaction], +) -> 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 +`inclusion_list_transactions`. + +```python +def notify_new_payload( + self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes], + inclusion_list_transactions: Sequence[Transaction], +) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list`` + are valid with respect to ``self.execution_state``. + """ + # TODO: move this outside of notify_new_payload. + # If execution client returns block does not satisfy inclusion list transactions, cache the block + # store.unsatisfied_inclusion_list_blocks.add(execution_payload.block_root) + ... +``` + +##### Modified `verify_and_notify_new_payload` + +*Note*: The function `verify_and_notify_new_payload` is modified to pass the +additional parameter `inclusion_list_transactions` when calling +`notify_new_payload` in EIP-7805. + +```python +def verify_and_notify_new_payload( + self: ExecutionEngine, new_payload_request: NewPayloadRequest +) -> bool: + """ + Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``. + """ + execution_payload = new_payload_request.execution_payload + parent_beacon_block_root = new_payload_request.parent_beacon_block_root + execution_requests_list = get_execution_requests_list(new_payload_request.execution_requests) + # [New in EIP-7805] + inclusion_list_transactions = new_payload_request.inclusion_list_transactions + + if b"" in execution_payload.transactions: + return False + + 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): + return False + + # [Modified in EIP-7805] + if not self.notify_new_payload( + execution_payload, + parent_beacon_block_root, + execution_requests_list, + inclusion_list_transactions, + ): + return False + + return True +``` + +##### 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_time_at_slot(state, state.slot) + # Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA + # Verify the execution payload is valid + versioned_hashes = [ + kzg_commitment_to_versioned_hash(commitment) for commitment in body.blob_kzg_commitments + ] + # Verify inclusion list transactions + inclusion_list_transactions: Sequence[Transaction] = [] # TODO: where do we get this? + # Verify the payload with the execution engine + 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, + inclusion_list_transactions=inclusion_list_transactions, + ) + ) + # 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/eip7805/fork-choice.md b/specs/_features/eip7805/fork-choice.md new file mode 100644 index 0000000000..25d964eb75 --- /dev/null +++ b/specs/_features/eip7805/fork-choice.md @@ -0,0 +1,249 @@ +# EIP-7805 -- Fork Choice + + + +- [Introduction](#introduction) +- [Configuration](#configuration) + - [Time parameters](#time-parameters) +- [Fork choice](#fork-choice) + - [Helpers](#helpers) + - [Modified `Store`](#modified-store) + - [Modified `get_forkchoice_store`](#modified-get_forkchoice_store) + - [New `validate_inclusion_lists`](#new-validate_inclusion_lists) + - [New `get_attester_head`](#new-get_attester_head) + - [Modified `get_proposer_head`](#modified-get_proposer_head) + - [New `on_inclusion_list`](#new-on_inclusion_list) + + + +## Introduction + +This is the modification of the fork choice accompanying the EIP-7805 upgrade. + +## Configuration + +### Time parameters + +| Name | Value | Unit | Duration | +| ---------------------- | ------------------------------- | :-----: | :-------: | +| `VIEW_FREEZE_DEADLINE` | `SECONDS_PER_SLOT * 2 // 3 + 1` | seconds | 9 seconds | + +## Fork choice + +### Helpers + +#### Modified `Store` + +*Note*: `Store` is modified to track the seen inclusion lists and inclusion list +equivocators. + +```python +@dataclass +class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + unrealized_justified_checkpoint: Checkpoint + unrealized_finalized_checkpoint: Checkpoint + proposer_boost_root: Root + equivocating_indices: Set[ValidatorIndex] + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + block_timeliness: Dict[Root, boolean] = field(default_factory=dict) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) + # [New in EIP-7805] + inclusion_lists: Dict[Tuple[Slot, Root], Set[InclusionList]] = field(default_factory=dict) + inclusion_list_equivocators: Dict[Tuple[Slot, Root], Set[ValidatorIndex]] = field( + default_factory=dict + ) + unsatisfied_inclusion_list_blocks: Set[Root] = field(default_factory=Set) +``` + +### Modified `get_forkchoice_store` + +```python +def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + unrealized_justified_checkpoint=justified_checkpoint, + unrealized_finalized_checkpoint=finalized_checkpoint, + proposer_boost_root=proposer_boost_root, + equivocating_indices=set(), + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + unrealized_justifications={anchor_root: justified_checkpoint}, + # [New in EIP-7805] + unsatisfied_inclusion_list_blocks=set(), + ) +``` + +#### New `validate_inclusion_lists` + +```python +def validate_inclusion_lists( + _store: Store, + inclusion_list_transactions: Sequence[Transaction], + execution_payload: ExecutionPayload, +) -> None: + """ + The ``execution_payload`` satisfies ``inclusion_list_transactions`` validity conditions either + when all transactions are present in payload or when any missing transactions are found to be + invalid when appended to the end of the payload unless the block is full. + """ + # Verify inclusion list transactions are present in the execution payload + contains_all_txs = all( + tx in execution_payload.transactions for tx in inclusion_list_transactions + ) + if contains_all_txs: + return + + # TODO: check remaining validity conditions +``` + +#### New `get_attester_head` + +```python +def get_attester_head(store: Store, head_root: Root) -> Root: + head_block = store.blocks[head_root] + + if head_root in store.unsatisfied_inclusion_list_blocks: + return head_block.parent_root + return head_root +``` + +##### Modified `get_proposer_head` + +The implementation of `get_proposer_head` is modified to also account for +`store.unsatisfied_inclusion_list_blocks`. + +```python +def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root: + head_block = store.blocks[head_root] + parent_root = head_block.parent_root + parent_block = store.blocks[parent_root] + + # Only re-org the head block if it arrived later than the attestation deadline. + head_late = is_head_late(store, head_root) + + # Do not re-org on an epoch boundary where the proposer shuffling could change. + shuffling_stable = is_shuffling_stable(slot) + + # Ensure that the FFG information of the new head will be competitive with the current head. + ffg_competitive = is_ffg_competitive(store, head_root, parent_root) + + # Do not re-org if the chain is not finalizing with acceptable frequency. + finalization_ok = is_finalization_ok(store, slot) + + # Only re-org if we are proposing on-time. + proposing_on_time = is_proposing_on_time(store) + + # Only re-org a single slot at most. + parent_slot_ok = parent_block.slot + 1 == head_block.slot + current_time_ok = head_block.slot + 1 == slot + single_slot_reorg = parent_slot_ok and current_time_ok + + # Check that the head has few enough votes to be overpowered by our proposer boost. + assert store.proposer_boost_root != head_root # ensure boost has worn off + head_weak = is_head_weak(store, head_root) + + # Check that the missing votes are assigned to the parent and not being hoarded. + parent_strong = is_parent_strong(store, parent_root) + + reorg_prerequisites = all( + [ + shuffling_stable, + ffg_competitive, + finalization_ok, + proposing_on_time, + single_slot_reorg, + head_weak, + parent_strong, + ] + ) + + # Check that the head block is in the unsatisfied inclusion list blocks + inclusion_list_not_satisfied = ( + head_root in store.unsatisfied_inclusion_list_blocks + ) # [New in EIP-7805] + + if reorg_prerequisites and (head_late or inclusion_list_not_satisfied): + return parent_root + else: + return head_root +``` + +#### New `on_inclusion_list` + +`on_inclusion_list` is called to import `signed_inclusion_list` to the fork +choice store. + +```python +def on_inclusion_list( + store: Store, + state: BeaconState, + signed_inclusion_list: SignedInclusionList, + inclusion_list_committee: Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE], +) -> None: + """ + Verify the inclusion list and import it into the fork choice store. If there exists more than + one inclusion list in the store with the same slot and validator index, add the equivocator to + the ``inclusion_list_equivocators`` cache. Otherwise, add the inclusion list to the + ``inclusion_lists` cache. + """ + message = signed_inclusion_list.message + + # Verify inclusion list slot is either from the current or previous slot + assert get_current_slot(store) in [message.slot, message.slot + 1] + + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + + # If the inclusion list is from the previous slot, ignore it if already past the attestation deadline + if get_current_slot(store) == message.slot + 1: + assert is_before_attesting_interval + + # Sanity check that the given `inclusion_list_committee` matches the root in the inclusion list + root = message.inclusion_list_committee_root + assert hash_tree_root(inclusion_list_committee) == root + + # Verify inclusion list validator is part of the committee + validator_index = message.validator_index + assert validator_index in inclusion_list_committee + + # Verify inclusion list signature + assert is_valid_inclusion_list_signature(state, signed_inclusion_list) + + is_before_freeze_deadline = ( + get_current_slot(store) == message.slot and time_into_slot < VIEW_FREEZE_DEADLINE + ) + + # Do not process inclusion lists from known equivocators + if validator_index not in store.inclusion_list_equivocators[(message.slot, root)]: + if validator_index in [ + il.validator_index for il in store.inclusion_lists[(message.slot, root)] + ]: + validator_inclusion_list = [ + il + for il in store.inclusion_lists[(message.slot, root)] + if il.validator_index == validator_index + ][0] + if validator_inclusion_list != message: + # We have equivocation evidence for `validator_index`, record it as equivocator + store.inclusion_list_equivocators[(message.slot, root)].add(validator_index) + # This inclusion list is not an equivocation. Store it if prior to the view freeze deadline + elif is_before_freeze_deadline: + store.inclusion_lists[(message.slot, root)].add(message) +``` diff --git a/specs/_features/eip7805/fork.md b/specs/_features/eip7805/fork.md new file mode 100644 index 0000000000..1f22ee6bda --- /dev/null +++ b/specs/_features/eip7805/fork.md @@ -0,0 +1,114 @@ +# EIP-7805 -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to EIP-7805](#fork-to-eip-7805) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the EIP-7805 upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | ------------------------------------- | +| `EIP7805_FORK_VERSION` | `Version('0x0a000000')` | +| `EIP7805_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= EIP7805_FORK_EPOCH: + return EIP7805_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to EIP-7805 + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == EIP7805_FORK_EPOCH`, an irregular state +change is made to upgrade to EIP-7805. + +```python +def upgrade_to_eip7805(pre: electra.BeaconState) -> BeaconState: + epoch = electra.get_current_epoch(pre) + + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in EIP-7805] + current_version=EIP7805_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=pre.latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + ) + + return post +``` diff --git a/specs/_features/eip7805/p2p-interface.md b/specs/_features/eip7805/p2p-interface.md new file mode 100644 index 0000000000..19c274419f --- /dev/null +++ b/specs/_features/eip7805/p2p-interface.md @@ -0,0 +1,104 @@ +# EIP-7805 -- Networking + +This document contains the consensus-layer networking specification for +EIP-7805. + + + +- [Modifications in EIP-7805](#modifications-in-eip-7805) + - [Configuration](#configuration) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`inclusion_list`](#inclusion_list) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [InclusionListByCommitteeIndices v1](#inclusionlistbycommitteeindices-v1) + + + +## Modifications in EIP-7805 + +### Configuration + +| Name | Value | Unit | Duration | +| ---------------------- | ----------------------- | :-----: | :-------: | +| `ATTESTATION_DEADLINE` | `SECONDS_PER_SLOT // 3` | seconds | 4 seconds | + +| Name | Value | Description | +| ------------------------------ | ---------------- | ---------------------------------------------------------- | +| `MAX_REQUEST_INCLUSION_LIST` | `2**4` (= 16) | Maximum number of inclusion list in a single request | +| `MAX_BYTES_PER_INCLUSION_LIST` | `2**13` (= 8192) | Maximum size of the inclusion list's transactions in bytes | + +### The gossip domain: gossipsub + +#### Topics and messages + +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: + +| Name | Message Type | +| ---------------- | --------------------- | +| `inclusion_list` | `SignedInclusionList` | + +##### Global topics + +EIP-7805 introduces a new global topic for inclusion lists. + +###### `inclusion_list` + +This topic is used to propagate signed inclusion list as `SignedInclusionList`. +The following validations MUST pass before forwarding the `inclusion_list` on +the network, assuming the alias `message = signed_inclusion_list.message`: + +- _[REJECT]_ The size of `message.transactions` is within upperbound + `MAX_BYTES_PER_INCLUSION_LIST`. +- _[REJECT]_ The slot `message.slot` is equal to the previous or current slot. +- _[IGNORE]_ The slot `message.slot` is equal to the current slot, or it is + equal to the previous slot and the current time is less than + `ATTESTATION_DEADLINE` seconds into the slot. +- _[IGNORE]_ The `inclusion_list_committee` for slot `message.slot` on the + current branch corresponds to `message.inclusion_list_committee_root`, as + determined by + `hash_tree_root(inclusion_list_committee) == message.inclusion_list_committee_root`. +- _[REJECT]_ The validator index `message.validator_index` is within the + `inclusion_list_committee` corresponding to + `message.inclusion_list_committee_root`. +- _[IGNORE]_ The `message` is either the first or second valid message received + from the validator with index `message.validator_index`. +- _[REJECT]_ The signature of `inclusion_list.signature` is valid with respect + to the validator index. + +### The Req/Resp domain + +#### Messages + +##### InclusionListByCommitteeIndices v1 + +**Protocol ID:** `/eth2/beacon_chain/req/inclusion_list_by_committee_indices/1/` + +The `` field is calculated as +`context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ---------------------- | ------------------------------ | +| `EIP7805_FORK_VERSION` | `EIP-7805.SignedInclusionList` | + +Request Content: + +``` +( + slot: Slot + committee_indices: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE] +) +``` + +Response Content: + +``` +( + List[SignedInclusionList, MAX_REQUEST_INCLUSION_LIST] +) +``` diff --git a/specs/_features/eip7805/validator.md b/specs/_features/eip7805/validator.md new file mode 100644 index 0000000000..c903a46778 --- /dev/null +++ b/specs/_features/eip7805/validator.md @@ -0,0 +1,177 @@ +# EIP-7805 -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Configuration](#configuration) + - [Time parameters](#time-parameters) +- [Protocol](#protocol) + - [`ExecutionEngine`](#executionengine) +- [New inclusion list committee assignment](#new-inclusion-list-committee-assignment) + - [Lookahead](#lookahead) +- [New proposer duty](#new-proposer-duty) + - [Block proposal](#block-proposal) + - [Update execution client with inclusion lists](#update-execution-client-with-inclusion-lists) +- [New inclusion list committee duty](#new-inclusion-list-committee-duty) + - [Constructing a signed inclusion list](#constructing-a-signed-inclusion-list) +- [Modified attester duty](#modified-attester-duty) + - [Modified LMD GHOST vote](#modified-lmd-ghost-vote) +- [Modified sync committee duty](#modified-sync-committee-duty) + - [Modified beacon block root](#modified-beacon-block-root) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement EIP-7805. + +## Prerequisites + +This document is an extension of the +[Electra -- Honest Validator](../../electra/validator.md) guide. All behaviors +and definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated Beacon Chain doc of [EIP-7805](./beacon-chain.md) are requisite for this +document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Configuration + +### Time parameters + +| Name | Value | Unit | Duration | +| --------------------------------- | ---------------------- | :-----: | :--------: | +| `PROPOSER_INCLUSION_LIST_CUT_OFF` | `SECONDS_PER_SLOT - 1` | seconds | 11 seconds | + +## Protocol + +### `ExecutionEngine` + +*Note*: `engine_getInclusionListV1` and `engine_updateBlockWithInclusionListV1` +functions are added to the `ExecutionEngine` protocol for use as a validator. + +The body of these function is implementation dependent. The Engine API may be +used to implement it with an external execution engine. + +## New inclusion list committee assignment + +A validator may be a member of the new Inclusion List Committee (ILC) for a +given slot. To check for ILC assignments the validator uses the helper +`get_inclusion_committee_assignment(state, epoch, validator_index)` where +`epoch <= next_epoch`. + +Inclusion list committee selection is only stable within the context of the +current and next epoch. + +```python +def get_inclusion_committee_assignment( + state: BeaconState, epoch: Epoch, 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 ILC. Returns None if no assignment is found. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + if validator_index in get_inclusion_list_committee(state, Slot(slot)): + return Slot(slot) + return None +``` + +### Lookahead + +`get_inclusion_committee_assignment` should be called at the start of each epoch +to get the assignment for the next epoch (`current_epoch + 1`). A validator +should plan for future assignments by noting their assigned ILC slot. + +## New proposer duty + +### Block proposal + +Proposers are still expected to propose `SignedBeaconBlock` at the beginning of +any slot during which `is_proposer(state, validator_index)` returns true. The +mechanism to prepare this beacon block and related sidecars differs from +previous forks as follows: + +#### Update execution client with inclusion lists + +The proposer should call `engine_updateInclusionListV1` at +`PROPOSER_INCLUSION_LIST_CUT_OFF` into the slot with the list of the inclusion +lists that gathered up to `PROPOSER_INCLUSION_LIST_CUT_OFF`. + +## New inclusion list committee duty + +Some validators are selected to submit signed inclusion list. Validators should +call `get_inclusion_committee_assignment` at the beginning of an epoch to be +prepared to submit their inclusion list during the next epoch. + +A validator should create and broadcast the `signed_inclusion_list` to the +global `inclusion_list` subnet by `PROPOSER_INCLUSION_LIST_CUT_OFF` seconds into +the slot, unless a block for the current slot has been processed and is the head +of the chain and broadcast to the network. + +#### Constructing a signed inclusion list + +The validator creates the `signed_inclusion_list` as follows: + +- First, the validator creates the `inclusion_list`. +- Set `inclusion_list.slot` to the assigned slot returned by + `get_inclusion_committee_assignment`. +- Set `inclusion_list.validator_index` to the validator's index. +- Set `inclusion_list.inclusion_list_committee_root` to the hash tree root of + the committee that the validator is a member of. +- Set `inclusion_list.transactions` using the response from + `engine_getInclusionListV1` from the execution layer client. +- Sign the `inclusion_list` using the helper `get_inclusion_list_signature` and + obtain the `signature`. +- Set `signed_inclusion_list.message` to `inclusion_list`. +- Set `signed_inclusion_list.signature` to `signature`. + +```python +def get_inclusion_list_signature( + state: BeaconState, inclusion_list: InclusionList, privkey: int +) -> BLSSignature: + domain = get_domain( + state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(inclusion_list.slot) + ) + signing_root = compute_signing_root(inclusion_list, domain) + return bls.Sign(privkey, signing_root) +``` + +## Modified attester duty + +#### Modified LMD GHOST vote + +Set `attestation_data.beacon_block_root = get_attester_head(store, head_root)`. + +## Modified sync committee duty + +#### Modified beacon block root + +```python +def get_sync_committee_message( + state: BeaconState, + block_root: Root, + validator_index: ValidatorIndex, + privkey: int, + store: Store, +) -> SyncCommitteeMessage: + epoch = get_current_epoch(state) + domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) + signing_root = compute_signing_root(block_root, domain) + signature = bls.Sign(privkey, signing_root) + + return SyncCommitteeMessage( + slot=state.slot, + beacon_block_root=get_attester_head(store, block_root), + validator_index=validator_index, + signature=signature, + ) +``` diff --git a/specs/altair/beacon-chain.md b/specs/altair/beacon-chain.md index dbd3e63683..53210ae15a 100644 --- a/specs/altair/beacon-chain.md +++ b/specs/altair/beacon-chain.md @@ -1,10 +1,6 @@ # Altair -- The Beacon Chain -## Table of contents - - - - + - [Introduction](#introduction) - [Custom types](#custom-types) @@ -14,7 +10,7 @@ - [Domain types](#domain-types) - [Misc](#misc) - [Preset](#preset) - - [Updated penalty values](#updated-penalty-values) + - [Rewards and penalties](#rewards-and-penalties) - [Sync committee](#sync-committee) - [Configuration](#configuration) - [Inactivity penalties](#inactivity-penalties) @@ -30,6 +26,8 @@ - [Misc](#misc-1) - [`add_flag`](#add_flag) - [`has_flag`](#has_flag) + - [`get_index_for_new_validator`](#get_index_for_new_validator) + - [`set_or_append_list`](#set_or_append_list) - [Beacon state accessors](#beacon-state-accessors) - [`get_next_sync_committee_indices`](#get_next_sync_committee_indices) - [`get_next_sync_committee`](#get_next_sync_committee) @@ -43,99 +41,99 @@ - [Modified `slash_validator`](#modified-slash_validator) - [Block processing](#block-processing) - [Modified `process_attestation`](#modified-process_attestation) - - [Modified `process_deposit`](#modified-process_deposit) + - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) - [Sync aggregate processing](#sync-aggregate-processing) - [Epoch processing](#epoch-processing) - [Justification and finalization](#justification-and-finalization) - [Inactivity scores](#inactivity-scores) - - [Rewards and penalties](#rewards-and-penalties) + - [Rewards and penalties](#rewards-and-penalties-1) - [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) - - + ## Introduction Altair is the first beacon chain hard fork. Its main features are: -* sync committees to support light clients -* incentive accounting reforms to reduce spec complexity -* penalty parameter updates towards their planned maximally punitive values +- sync committees to support light clients +- incentive accounting reforms to reduce spec complexity +- penalty parameter updates towards their planned maximally punitive values ## Custom types -| Name | SSZ equivalent | Description | -| - | - | - | -| `ParticipationFlags` | `uint8` | a succinct representation of 8 boolean participation flags | +| Name | SSZ equivalent | Description | +| -------------------- | -------------- | ---------------------------------------------------------- | +| `ParticipationFlags` | `uint8` | a succinct representation of 8 boolean participation flags | ## Constants ### Participation flag indices -| Name | Value | -| - | - | -| `TIMELY_SOURCE_FLAG_INDEX` | `0` | -| `TIMELY_TARGET_FLAG_INDEX` | `1` | -| `TIMELY_HEAD_FLAG_INDEX` | `2` | +| Name | Value | +| -------------------------- | ----- | +| `TIMELY_SOURCE_FLAG_INDEX` | `0` | +| `TIMELY_TARGET_FLAG_INDEX` | `1` | +| `TIMELY_HEAD_FLAG_INDEX` | `2` | ### Incentivization weights -| Name | Value | -| - | - | +| Name | Value | +| ---------------------- | ------------ | | `TIMELY_SOURCE_WEIGHT` | `uint64(14)` | | `TIMELY_TARGET_WEIGHT` | `uint64(26)` | -| `TIMELY_HEAD_WEIGHT` | `uint64(14)` | -| `SYNC_REWARD_WEIGHT` | `uint64(2)` | -| `PROPOSER_WEIGHT` | `uint64(8)` | -| `WEIGHT_DENOMINATOR` | `uint64(64)` | +| `TIMELY_HEAD_WEIGHT` | `uint64(14)` | +| `SYNC_REWARD_WEIGHT` | `uint64(2)` | +| `PROPOSER_WEIGHT` | `uint64(8)` | +| `WEIGHT_DENOMINATOR` | `uint64(64)` | *Note*: The sum of the weights equal `WEIGHT_DENOMINATOR`. ### Domain types -| Name | Value | -| - | - | -| `DOMAIN_SYNC_COMMITTEE` | `DomainType('0x07000000')` | +| Name | Value | +| --------------------------------------- | -------------------------- | +| `DOMAIN_SYNC_COMMITTEE` | `DomainType('0x07000000')` | | `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` | `DomainType('0x08000000')` | -| `DOMAIN_CONTRIBUTION_AND_PROOF` | `DomainType('0x09000000')` | +| `DOMAIN_CONTRIBUTION_AND_PROOF` | `DomainType('0x09000000')` | ### Misc -| Name | Value | -| - | - | +| Name | Value | +| ---------------------------- | ------------------------------------------------------------------ | | `PARTICIPATION_FLAG_WEIGHTS` | `[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT]` | ## Preset -### Updated penalty values +### Rewards and penalties -This patch updates a few configuration values to move penalty parameters closer to their final, maximum security values. +This patch updates a few configuration values to move penalty parameters closer +to their final, maximum security values. -*Note*: The spec does *not* override previous configuration values but instead creates new values and replaces usage throughout. +*Note*: The spec does *not* override previous configuration values but instead +creates new values and replaces usage throughout. -| Name | Value | -| - | - | -| `INACTIVITY_PENALTY_QUOTIENT_ALTAIR` | `uint64(3 * 2**24)` (= 50,331,648) | -| `MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR` | `uint64(2**6)` (= 64) | -| `PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR` | `uint64(2)` | +| Name | Value | +| ----------------------------------------- | ---------------------------------- | +| `INACTIVITY_PENALTY_QUOTIENT_ALTAIR` | `uint64(3 * 2**24)` (= 50,331,648) | +| `MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR` | `uint64(2**6)` (= 64) | +| `PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR` | `uint64(2)` | ### Sync committee -| Name | Value | Unit | Duration | -| - | - | - | - | -| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | Validators | | -| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | +| Name | Value | Unit | Duration | +| ---------------------------------- | ---------------------- | ---------- | --------- | +| `SYNC_COMMITTEE_SIZE` | `uint64(2**9)` (= 512) | validators | | +| `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | ## Configuration ### Inactivity penalties -| Name | Value | Description | -| - | - | - | -| `INACTIVITY_SCORE_BIAS` | `uint64(2**2)` (= 4) | score points per inactive epoch | +| Name | Value | Description | +| -------------------------------- | --------------------- | -------------------------------- | +| `INACTIVITY_SCORE_BIAS` | `uint64(2**2)` (= 4) | score points per inactive epoch | | `INACTIVITY_SCORE_RECOVERY_RATE` | `uint64(2**4)` (= 16) | score points per leak-free epoch | ## Containers @@ -147,55 +145,50 @@ This patch updates a few configuration values to move penalty parameters closer ```python class BeaconBlockBody(Container): randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations + eth1_data: Eth1Data + graffiti: Bytes32 proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] attestations: List[Attestation, MAX_ATTESTATIONS] deposits: List[Deposit, MAX_DEPOSITS] voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate # [New in Altair] + # [New in Altair] + sync_aggregate: SyncAggregate ``` #### `BeaconState` ```python class BeaconState(Container): - # Versioning genesis_time: uint64 genesis_validators_root: Root slot: Slot fork: Fork - # History latest_block_header: BeaconBlockHeader block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] - # Eth1 eth1_data: Eth1Data eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] eth1_deposit_index: uint64 - # Registry validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] - # Randomness randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] - # Slashings - slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances - # Participation - previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair] - current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair] - # Finality - justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + # [Modified in Altair] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + # [Modified in Altair] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] previous_justified_checkpoint: Checkpoint current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair] - # Sync - current_sync_committee: SyncCommittee # [New in Altair] - next_sync_committee: SyncCommittee # [New in Altair] + # [New in Altair] + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + # [New in Altair] + current_sync_committee: SyncCommittee + # [New in Altair] + next_sync_committee: SyncCommittee ``` ### New containers @@ -220,9 +213,11 @@ class SyncCommittee(Container): ### Crypto -Refer to the definitions in the [phase 0 document regarding BLS signatures](../phase0/beacon-chain.md#bls-signatures) -and the extensions defined in the [Altair BLS document](./bls.md). This specification assumes knowledge of -the functionality described in those documents. +Refer to the definitions in the +[phase 0 document regarding BLS signatures](../phase0/beacon-chain.md#bls-signatures) +and the extensions defined in the [Altair BLS document](./bls.md). This +specification assumes knowledge of the functionality described in those +documents. ### Misc @@ -248,6 +243,23 @@ def has_flag(flags: ParticipationFlags, flag_index: int) -> bool: return flags & flag == flag ``` +#### `get_index_for_new_validator` + +```python +def get_index_for_new_validator(state: BeaconState) -> ValidatorIndex: + return ValidatorIndex(len(state.validators)) +``` + +#### `set_or_append_list` + +```python +def set_or_append_list(list: List, index: ValidatorIndex, value: Any) -> None: + if index == len(list): + list.append(value) + else: + list[index] = value +``` + ### Beacon state accessors #### `get_next_sync_committee_indices` @@ -266,7 +278,9 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd i = 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) + 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] effective_balance = state.validators[candidate_index].effective_balance @@ -278,7 +292,9 @@ def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorInd #### `get_next_sync_committee` -*Note*: The function `get_next_sync_committee` should only be called at sync committee period boundaries and when [upgrading state to Altair](./fork.md#upgrading-the-state). +*Note*: The function `get_next_sync_committee` should only be called at sync +committee period boundaries and when +[upgrading state to Altair](./fork.md#upgrading-the-state). ```python def get_next_sync_committee(state: BeaconState) -> SyncCommittee: @@ -295,14 +311,20 @@ def get_next_sync_committee(state: BeaconState) -> SyncCommittee: ```python def get_base_reward_per_increment(state: BeaconState) -> Gwei: - return Gwei(EFFECTIVE_BALANCE_INCREMENT * BASE_REWARD_FACTOR // integer_squareroot(get_total_active_balance(state))) + return Gwei( + EFFECTIVE_BALANCE_INCREMENT + * BASE_REWARD_FACTOR + // integer_squareroot(get_total_active_balance(state)) + ) ``` #### `get_base_reward` -*Note*: The function `get_base_reward` is modified with the removal of `BASE_REWARDS_PER_EPOCH` and the use of increment based accounting. +*Note*: The function `get_base_reward` is modified with the removal of +`BASE_REWARDS_PER_EPOCH` and the use of increment based accounting. -*Note*: On average an optimally performing validator earns one base reward per epoch. +*Note*: On average an optimally performing validator earns one base reward per +epoch. ```python def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: @@ -316,7 +338,9 @@ def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: #### `get_unslashed_participating_indices` ```python -def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epoch: Epoch) -> Set[ValidatorIndex]: +def get_unslashed_participating_indices( + state: BeaconState, flag_index: int, epoch: Epoch +) -> Set[ValidatorIndex]: """ Return the set of validator indices that are both active and unslashed for the given ``flag_index`` and ``epoch``. """ @@ -326,16 +350,18 @@ def get_unslashed_participating_indices(state: BeaconState, flag_index: int, epo else: epoch_participation = state.previous_epoch_participation active_validator_indices = get_active_validator_indices(state, epoch) - participating_indices = [i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index)] + participating_indices = [ + i for i in active_validator_indices if has_flag(epoch_participation[i], flag_index) + ] return set(filter(lambda index: not state.validators[index].slashed, participating_indices)) ``` #### `get_attestation_participation_flag_indices` ```python -def get_attestation_participation_flag_indices(state: BeaconState, - data: AttestationData, - inclusion_delay: uint64) -> Sequence[int]: +def get_attestation_participation_flag_indices( + state: BeaconState, data: AttestationData, inclusion_delay: uint64 +) -> Sequence[int]: """ Return the flag indices that are satisfied by an attestation. """ @@ -346,8 +372,12 @@ def get_attestation_participation_flag_indices(state: BeaconState, # Matching roots is_matching_source = data.source == justified_checkpoint - is_matching_target = is_matching_source and data.target.root == get_block_root(state, data.target.epoch) - is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot(state, data.slot) + is_matching_target = is_matching_source and data.target.root == get_block_root( + state, data.target.epoch + ) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot( + state, data.slot + ) assert is_matching_source participation_flag_indices = [] @@ -364,17 +394,23 @@ def get_attestation_participation_flag_indices(state: BeaconState, #### `get_flag_index_deltas` ```python -def get_flag_index_deltas(state: BeaconState, flag_index: int) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_flag_index_deltas( + state: BeaconState, flag_index: int +) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ Return the deltas for a given ``flag_index`` by scanning through the participation flags. """ rewards = [Gwei(0)] * len(state.validators) penalties = [Gwei(0)] * len(state.validators) previous_epoch = get_previous_epoch(state) - unslashed_participating_indices = get_unslashed_participating_indices(state, flag_index, previous_epoch) + unslashed_participating_indices = get_unslashed_participating_indices( + state, flag_index, previous_epoch + ) weight = PARTICIPATION_FLAG_WEIGHTS[flag_index] unslashed_participating_balance = get_total_balance(state, unslashed_participating_indices) - unslashed_participating_increments = unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + unslashed_participating_increments = ( + unslashed_participating_balance // EFFECTIVE_BALANCE_INCREMENT + ) active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT for index in get_eligible_validator_indices(state): base_reward = get_base_reward(state, index) @@ -397,10 +433,14 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S rewards = [Gwei(0) for _ in range(len(state.validators))] penalties = [Gwei(0) for _ in range(len(state.validators))] previous_epoch = get_previous_epoch(state) - matching_target_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, previous_epoch) + matching_target_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, previous_epoch + ) for index in get_eligible_validator_indices(state): if index not in matching_target_indices: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] + penalty_numerator = ( + state.validators[index].effective_balance * state.inactivity_scores[index] + ) penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR penalties[index] += Gwei(penalty_numerator // penalty_denominator) return rewards, penalties @@ -410,13 +450,14 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S #### Modified `slash_validator` -*Note*: The function `slash_validator` is modified to use `MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR` -and use `PROPOSER_WEIGHT` when calculating the proposer reward. +*Note*: The function `slash_validator` is modified to use +`MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR` and use `PROPOSER_WEIGHT` when +calculating the proposer reward. ```python -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: +def slash_validator( + state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex = None +) -> None: """ Slash the validator with index ``slashed_index``. """ @@ -424,9 +465,13 @@ def slash_validator(state: BeaconState, initiate_validator_exit(state, slashed_index) validator = state.validators[slashed_index] validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + validator.withdrawable_epoch = max( + validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR) + ) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR) + decrease_balance( + state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR + ) # Apply proposer and whistleblower rewards proposer_index = get_beacon_proposer_index(state) @@ -451,7 +496,8 @@ def process_block(state: BeaconState, block: BeaconBlock) -> None: #### Modified `process_attestation` -*Note*: The function `process_attestation` is modified to do incentive accounting with epoch participation flags. +*Note*: The function `process_attestation` is modified to do incentive +accounting with epoch participation flags. ```python def process_attestation(state: BeaconState, attestation: Attestation) -> None: @@ -465,7 +511,9 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: assert len(attestation.aggregation_bits) == len(committee) # Participation flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, state.slot - data.slot) + participation_flag_indices = get_attestation_participation_flag_indices( + state, data, state.slot - data.slot + ) # Verify signature assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) @@ -477,59 +525,40 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: epoch_participation = state.previous_epoch_participation proposer_reward_numerator = 0 - for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for index in get_attesting_indices(state, attestation): for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): - if flag_index in participation_flag_indices and not has_flag(epoch_participation[index], flag_index): + if flag_index in participation_flag_indices and not has_flag( + epoch_participation[index], flag_index + ): epoch_participation[index] = add_flag(epoch_participation[index], flag_index) proposer_reward_numerator += get_base_reward(state, index) * weight # Reward proposer - proposer_reward_denominator = (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + proposer_reward_denominator = ( + (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + ) proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) increase_balance(state, get_beacon_proposer_index(state), proposer_reward) ``` -#### Modified `process_deposit` +#### Modified `add_validator_to_registry` -*Note*: The function `process_deposit` is modified to initialize `inactivity_scores`, `previous_epoch_participation`, and `current_epoch_participation`. +*Note*: The function `add_validator_to_registry` is modified to initialize +`inactivity_scores`, `previous_epoch_participation`, and +`current_epoch_participation`. ```python -def process_deposit(state: BeaconState, deposit: Deposit) -> None: - # Verify the Merkle branch - assert is_valid_merkle_branch( - leaf=hash_tree_root(deposit.data), - branch=deposit.proof, - depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in - index=state.eth1_deposit_index, - root=state.eth1_data.deposit_root, - ) - - # Deposits must be processed in order - state.eth1_deposit_index += 1 - - pubkey = deposit.data.pubkey - amount = deposit.data.amount - validator_pubkeys = [validator.pubkey for validator in state.validators] - if pubkey not in validator_pubkeys: - # Verify the deposit signature (proof of possession) which is not checked by the deposit contract - deposit_message = DepositMessage( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - amount=deposit.data.amount, - ) - domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks - signing_root = compute_signing_root(deposit_message, domain) - # Initialize validator if the deposit signature is valid - if bls.Verify(pubkey, signing_root, deposit.data.signature): - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - state.previous_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.current_epoch_participation.append(ParticipationFlags(0b0000_0000)) - state.inactivity_scores.append(uint64(0)) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) +def add_validator_to_registry( + state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> None: + index = get_index_for_new_validator(state) + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + # [New in Altair] + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) ``` #### Sync aggregate processing @@ -540,23 +569,35 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: def process_sync_aggregate(state: BeaconState, sync_aggregate: SyncAggregate) -> None: # Verify sync committee aggregate signature signing over the previous slot block root committee_pubkeys = state.current_sync_committee.pubkeys - participant_pubkeys = [pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit] + participant_pubkeys = [ + pubkey for pubkey, bit in zip(committee_pubkeys, sync_aggregate.sync_committee_bits) if bit + ] previous_slot = max(state.slot, Slot(1)) - Slot(1) domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, compute_epoch_at_slot(previous_slot)) signing_root = compute_signing_root(get_block_root_at_slot(state, previous_slot), domain) - assert eth_fast_aggregate_verify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature) + assert eth_fast_aggregate_verify( + participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature + ) # Compute participant and proposer rewards total_active_increments = get_total_active_balance(state) // EFFECTIVE_BALANCE_INCREMENT total_base_rewards = Gwei(get_base_reward_per_increment(state) * total_active_increments) - max_participant_rewards = Gwei(total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH) + max_participant_rewards = Gwei( + total_base_rewards * SYNC_REWARD_WEIGHT // WEIGHT_DENOMINATOR // SLOTS_PER_EPOCH + ) participant_reward = Gwei(max_participant_rewards // SYNC_COMMITTEE_SIZE) - proposer_reward = Gwei(participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT)) + proposer_reward = Gwei( + participant_reward * PROPOSER_WEIGHT // (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) + ) # Apply participant and proposer rewards all_pubkeys = [v.pubkey for v in state.validators] - committee_indices = [ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys] - for participant_index, participation_bit in zip(committee_indices, sync_aggregate.sync_committee_bits): + committee_indices = [ + ValidatorIndex(all_pubkeys.index(pubkey)) for pubkey in state.current_sync_committee.pubkeys + ] + for participant_index, participation_bit in zip( + committee_indices, sync_aggregate.sync_committee_bits + ): if participation_bit: increase_balance(state, participant_index, participant_reward) increase_balance(state, get_beacon_proposer_index(state), proposer_reward) @@ -584,7 +625,8 @@ def process_epoch(state: BeaconState) -> None: #### Justification and finalization -*Note*: The function `process_justification_and_finalization` is modified to adapt to the new participation records. +*Note*: The function `process_justification_and_finalization` is modified to +adapt to the new participation records. ```python def process_justification_and_finalization(state: BeaconState) -> None: @@ -592,12 +634,18 @@ def process_justification_and_finalization(state: BeaconState) -> None: # Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. if get_current_epoch(state) <= GENESIS_EPOCH + 1: return - previous_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)) - current_indices = get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state)) + previous_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) + ) + current_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, get_current_epoch(state) + ) total_active_balance = get_total_active_balance(state) previous_target_balance = get_total_balance(state, previous_indices) current_target_balance = get_total_balance(state, current_indices) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + weigh_justification_and_finalization( + state, total_active_balance, previous_target_balance, current_target_balance + ) ``` #### Inactivity scores @@ -612,18 +660,23 @@ def process_inactivity_updates(state: BeaconState) -> None: for index in get_eligible_validator_indices(state): # Increase the inactivity score of inactive validators - if index in get_unslashed_participating_indices(state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state)): + if index in get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, get_previous_epoch(state) + ): state.inactivity_scores[index] -= min(1, state.inactivity_scores[index]) else: state.inactivity_scores[index] += INACTIVITY_SCORE_BIAS # Decrease the inactivity score of all eligible validators during a leak-free epoch if not is_in_inactivity_leak(state): - state.inactivity_scores[index] -= min(INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index]) + state.inactivity_scores[index] -= min( + INACTIVITY_SCORE_RECOVERY_RATE, state.inactivity_scores[index] + ) ``` #### Rewards and penalties -*Note*: The function `process_rewards_and_penalties` is modified to support the incentive accounting reforms. +*Note*: The function `process_rewards_and_penalties` is modified to support the +incentive accounting reforms. ```python def process_rewards_and_penalties(state: BeaconState) -> None: @@ -631,9 +684,12 @@ def process_rewards_and_penalties(state: BeaconState) -> None: if get_current_epoch(state) == GENESIS_EPOCH: return - flag_deltas = [get_flag_index_deltas(state, flag_index) for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS))] + flag_deltas = [ + get_flag_index_deltas(state, flag_index) + for flag_index in range(len(PARTICIPATION_FLAG_WEIGHTS)) + ] deltas = flag_deltas + [get_inactivity_penalty_deltas(state)] - for (rewards, penalties) in deltas: + for rewards, penalties in deltas: for index in range(len(state.validators)): increase_balance(state, ValidatorIndex(index), rewards[index]) decrease_balance(state, ValidatorIndex(index), penalties[index]) @@ -641,17 +697,25 @@ def process_rewards_and_penalties(state: BeaconState) -> None: #### Slashings -*Note*: The function `process_slashings` is modified to use `PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR`. +*Note*: The function `process_slashings` is modified to use +`PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR`. ```python def process_slashings(state: BeaconState) -> None: epoch = get_current_epoch(state) total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, total_balance) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR, total_balance + ) for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + if ( + validator.slashed + and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch + ): increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty_numerator = ( + validator.effective_balance // increment * adjusted_total_slashing_balance + ) penalty = penalty_numerator // total_balance * increment decrease_balance(state, ValidatorIndex(index), penalty) ``` @@ -663,7 +727,9 @@ def process_slashings(state: BeaconState) -> None: ```python def process_participation_flag_updates(state: BeaconState) -> None: state.previous_epoch_participation = state.current_epoch_participation - state.current_epoch_participation = [ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + state.current_epoch_participation = [ + ParticipationFlags(0b0000_0000) for _ in range(len(state.validators)) + ] ``` #### Sync committee updates @@ -677,52 +743,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 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: Bytes32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit]) -> BeaconState: - fork = Fork( - previous_version=GENESIS_FORK_VERSION, - 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/bls.md b/specs/altair/bls.md index 8492e95c30..45bbd71a16 100644 --- a/specs/altair/bls.md +++ b/specs/altair/bls.md @@ -1,10 +1,6 @@ # Altair -- BLS extensions -## Table of contents - - - - + - [Introduction](#introduction) - [Constants](#constants) @@ -12,19 +8,20 @@ - [`eth_aggregate_pubkeys`](#eth_aggregate_pubkeys) - [`eth_fast_aggregate_verify`](#eth_fast_aggregate_verify) - - + ## Introduction -A number of extensions are defined to handle BLS signatures in the Altair upgrade. +A number of extensions are defined to handle BLS signatures in the Altair +upgrade. -Knowledge of the [phase 0 specification](../phase0/beacon-chain.md) is assumed, including type definitions. +Knowledge of the [phase 0 specification](../phase0/beacon-chain.md) is assumed, +including type definitions. ## Constants -| Name | Value | -| - | - | +| Name | Value | +| ---------------------- | -------------------------------------- | | `G2_POINT_AT_INFINITY` | `BLSSignature(b'\xc0' + b'\x00' * 95)` | ## Extensions @@ -40,7 +37,7 @@ def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: """ Return the aggregate public key for the public keys in ``pubkeys``. - NOTE: the ``+`` operation should be interpreted as elliptic curve point addition, which takes as input + Note: the ``+`` operation should be interpreted as elliptic curve point addition, which takes as input elliptic curve points that must be decoded from the input ``BLSPubkey``s. This implementation is for demonstrative purposes only and ignores encoding/decoding concerns. Refer to the BLS signature draft standard for more information. @@ -58,7 +55,9 @@ def eth_aggregate_pubkeys(pubkeys: Sequence[BLSPubkey]) -> BLSPubkey: ### `eth_fast_aggregate_verify` ```python -def eth_fast_aggregate_verify(pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature) -> bool: +def eth_fast_aggregate_verify( + pubkeys: Sequence[BLSPubkey], message: Bytes32, signature: BLSSignature +) -> bool: """ Wrapper to ``bls.FastAggregateVerify`` accepting the ``G2_POINT_AT_INFINITY`` signature when ``pubkeys`` is empty. """ diff --git a/specs/altair/fork.md b/specs/altair/fork.md index f80064a830..1fe6ac3927 100644 --- a/specs/altair/fork.md +++ b/specs/altair/fork.md @@ -1,60 +1,86 @@ # Altair -- Fork Logic -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - + - [Introduction](#introduction) - [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [`compute_fork_version`](#compute_fork_version) - [Fork to Altair](#fork-to-altair) - [Fork trigger](#fork-trigger) - [Upgrading the state](#upgrading-the-state) - + ## Introduction -This document describes the process of the first upgrade of the beacon chain: the Altair hard fork, introducing light client support and other improvements. +This document describes the process of the first upgrade of the beacon chain: +the Altair hard fork, introducing light client support and other improvements. ## Configuration -Warning: this configuration is not definitive. +| Name | Value | +| --------------------- | --------------------------------------------- | +| `ALTAIR_FORK_VERSION` | `Version('0x01000000')` | +| `ALTAIR_FORK_EPOCH` | `Epoch(74240)` (Oct 27, 2021, 10:56:23am UTC) | + +## Helper functions + +### Misc -| Name | Value | -| - | - | -| `ALTAIR_FORK_VERSION` | `Version('0x01000000')` | -| `ALTAIR_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | +#### `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` ## Fork to Altair ### Fork trigger -TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `ALTAIR_FORK_EPOCH`. +The fork is triggered at epoch `ALTAIR_FORK_EPOCH`. -Note that for the pure Altair networks, we don't apply `upgrade_to_altair` since it starts with Altair version logic. +Note that for the pure Altair networks, we don't apply `upgrade_to_altair` since +it starts with Altair version logic. ### Upgrading the state -If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == ALTAIR_FORK_EPOCH`, an irregular state change is made to upgrade to Altair. +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == ALTAIR_FORK_EPOCH`, an irregular state +change is made to upgrade to Altair. -The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `ALTAIR_FORK_EPOCH * SLOTS_PER_EPOCH`. -Care must be taken when transitioning through the fork boundary as implementations will need a modified [state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) that deviates from the Phase 0 document. -In particular, the outer `state_transition` function defined in the Phase 0 document will not expose the precise fork slot to execute the upgrade in the presence of skipped slots at the fork boundary. Instead the logic must be within `process_slots`. +The upgrade occurs after the completion of the inner loop of `process_slots` +that sets `state.slot` equal to `ALTAIR_FORK_EPOCH * SLOTS_PER_EPOCH`. Care must +be taken when transitioning through the fork boundary as implementations will +need a modified +[state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) +that deviates from the Phase 0 document. In particular, the outer +`state_transition` function defined in the Phase 0 document will not expose the +precise fork slot to execute the upgrade in the presence of skipped slots at the +fork boundary. Instead the logic must be within `process_slots`. ```python -def translate_participation(state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation]) -> None: +def translate_participation( + state: BeaconState, pending_attestations: Sequence[phase0.PendingAttestation] +) -> None: for attestation in pending_attestations: data = attestation.data inclusion_delay = attestation.inclusion_delay # Translate attestation inclusion info to flag indices - participation_flag_indices = get_attestation_participation_flag_indices(state, data, inclusion_delay) + participation_flag_indices = get_attestation_participation_flag_indices( + state, data, inclusion_delay + ) # Apply flags to all attesting validators epoch_participation = state.previous_epoch_participation - for index in get_attesting_indices(state, data, attestation.aggregation_bits): + for index in get_attesting_indices(state, attestation): for flag_index in participation_flag_indices: epoch_participation[index] = add_flag(epoch_participation[index], flag_index) @@ -62,7 +88,6 @@ def translate_participation(state: BeaconState, pending_attestations: Sequence[p def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: epoch = phase0.get_current_epoch(pre) post = BeaconState( - # Versioning genesis_time=pre.genesis_time, genesis_validators_root=pre.genesis_validators_root, slot=pre.slot, @@ -71,31 +96,27 @@ def upgrade_to_altair(pre: phase0.BeaconState) -> BeaconState: current_version=ALTAIR_FORK_VERSION, epoch=epoch, ), - # History latest_block_header=pre.latest_block_header, block_roots=pre.block_roots, state_roots=pre.state_roots, historical_roots=pre.historical_roots, - # Eth1 eth1_data=pre.eth1_data, eth1_data_votes=pre.eth1_data_votes, eth1_deposit_index=pre.eth1_deposit_index, - # Registry validators=pre.validators, balances=pre.balances, - # Randomness randao_mixes=pre.randao_mixes, - # Slashings slashings=pre.slashings, - # Participation - previous_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - current_epoch_participation=[ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators))], - # Finality + previous_epoch_participation=[ + ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators)) + ], + current_epoch_participation=[ + ParticipationFlags(0b0000_0000) for _ in range(len(pre.validators)) + ], justification_bits=pre.justification_bits, previous_justified_checkpoint=pre.previous_justified_checkpoint, current_justified_checkpoint=pre.current_justified_checkpoint, finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity inactivity_scores=[uint64(0) for _ in range(len(pre.validators))], ) # Fill in previous epoch participation from the pre state's pending attestations diff --git a/specs/altair/light-client/full-node.md b/specs/altair/light-client/full-node.md new file mode 100644 index 0000000000..a88b92b6b0 --- /dev/null +++ b/specs/altair/light-client/full-node.md @@ -0,0 +1,221 @@ +# Altair Light Client -- Full Node + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [`compute_merkle_proof`](#compute_merkle_proof) + - [`block_to_light_client_header`](#block_to_light_client_header) +- [Deriving light client data](#deriving-light-client-data) + - [`create_light_client_bootstrap`](#create_light_client_bootstrap) + - [`create_light_client_update`](#create_light_client_update) + - [`create_light_client_finality_update`](#create_light_client_finality_update) + - [`create_light_client_optimistic_update`](#create_light_client_optimistic_update) + + + +## Introduction + +This document provides helper functions to enable full nodes to serve light +client data. Full nodes SHOULD implement the described functionality to enable +light clients to sync with the network. + +## Helper functions + +### `compute_merkle_proof` + +This function return the Merkle proof of the given SSZ object `object` at +generalized index `index`. + +```python +def compute_merkle_proof(object: SSZObject, index: GeneralizedIndex) -> Sequence[Bytes32]: ... +``` + +### `block_to_light_client_header` + +```python +def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: + return LightClientHeader( + beacon=BeaconBlockHeader( + slot=block.message.slot, + proposer_index=block.message.proposer_index, + parent_root=block.message.parent_root, + state_root=block.message.state_root, + body_root=hash_tree_root(block.message.body), + ), + ) +``` + +## Deriving light client data + +Full nodes are expected to derive light client data from historic blocks and +states and provide it to other clients. + +### `create_light_client_bootstrap` + +To form a `LightClientBootstrap`, the following objects are needed: + +- `state`: the post state of any post-Altair block +- `block`: the corresponding block + +```python +def create_light_client_bootstrap( + state: BeaconState, block: SignedBeaconBlock +) -> LightClientBootstrap: + assert compute_epoch_at_slot(state.slot) >= ALTAIR_FORK_EPOCH + + assert state.slot == state.latest_block_header.slot + header = state.latest_block_header.copy() + header.state_root = hash_tree_root(state) + assert hash_tree_root(header) == hash_tree_root(block.message) + + return LightClientBootstrap( + header=block_to_light_client_header(block), + current_sync_committee=state.current_sync_committee, + current_sync_committee_branch=CurrentSyncCommitteeBranch( + compute_merkle_proof(state, current_sync_committee_gindex_at_slot(state.slot)) + ), + ) +``` + +Full nodes SHOULD provide `LightClientBootstrap` for all finalized epoch +boundary blocks in the epoch range +`[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` +where `current_epoch` is defined by the current wall-clock time. Full nodes MAY +also provide `LightClientBootstrap` for other blocks. + +Blocks are considered to be epoch boundary blocks if their block root can occur +as part of a valid `Checkpoint`, i.e., if their slot is the initial slot of an +epoch, or if all following slots through the initial slot of the next epoch are +empty (no block proposed / orphaned). + +`LightClientBootstrap` is computed from the block's immediate post state +(without applying empty slots). + +### `create_light_client_update` + +To form a `LightClientUpdate`, the following historical states and blocks are +needed: + +- `state`: the post state of any block with a post-Altair parent block +- `block`: the corresponding block +- `attested_state`: the post state of `attested_block` +- `attested_block`: the block referred to by `block.parent_root` +- `finalized_block`: the block referred to by + `attested_state.finalized_checkpoint.root`, if locally available (may be + unavailable, e.g., when using checkpoint sync, or if it was pruned locally) + +```python +def create_light_client_update( + state: BeaconState, + block: SignedBeaconBlock, + attested_state: BeaconState, + attested_block: SignedBeaconBlock, + finalized_block: Optional[SignedBeaconBlock], +) -> LightClientUpdate: + assert compute_epoch_at_slot(attested_state.slot) >= ALTAIR_FORK_EPOCH + assert ( + sum(block.message.body.sync_aggregate.sync_committee_bits) + >= MIN_SYNC_COMMITTEE_PARTICIPANTS + ) + + assert state.slot == state.latest_block_header.slot + header = state.latest_block_header.copy() + header.state_root = hash_tree_root(state) + assert hash_tree_root(header) == hash_tree_root(block.message) + update_signature_period = compute_sync_committee_period_at_slot(block.message.slot) + + assert attested_state.slot == attested_state.latest_block_header.slot + attested_header = attested_state.latest_block_header.copy() + attested_header.state_root = hash_tree_root(attested_state) + assert ( + hash_tree_root(attested_header) + == hash_tree_root(attested_block.message) + == block.message.parent_root + ) + update_attested_period = compute_sync_committee_period_at_slot(attested_block.message.slot) + + update = LightClientUpdate() + + update.attested_header = block_to_light_client_header(attested_block) + + # `next_sync_committee` is only useful if the message is signed by the current sync committee + if update_attested_period == update_signature_period: + update.next_sync_committee = attested_state.next_sync_committee + update.next_sync_committee_branch = NextSyncCommitteeBranch( + compute_merkle_proof( + attested_state, next_sync_committee_gindex_at_slot(attested_state.slot) + ) + ) + + # Indicate finality whenever possible + if finalized_block is not None: + if finalized_block.message.slot != GENESIS_SLOT: + update.finalized_header = block_to_light_client_header(finalized_block) + assert ( + hash_tree_root(update.finalized_header.beacon) + == attested_state.finalized_checkpoint.root + ) + else: + assert attested_state.finalized_checkpoint.root == Bytes32() + update.finality_branch = FinalityBranch( + compute_merkle_proof(attested_state, finalized_root_gindex_at_slot(attested_state.slot)) + ) + + update.sync_aggregate = block.message.body.sync_aggregate + update.signature_slot = block.message.slot + + return update +``` + +Full nodes SHOULD provide the best derivable `LightClientUpdate` (according to +`is_better_update`) for each sync committee period covering any epochs in range +`[max(ALTAIR_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` +where `current_epoch` is defined by the current wall-clock time. Full nodes MAY +also provide `LightClientUpdate` for other sync committee periods. + +- `LightClientUpdate` are assigned to sync committee periods based on their + `attested_header.beacon.slot` +- `LightClientUpdate` are only considered if + `compute_sync_committee_period_at_slot(update.attested_header.beacon.slot) == compute_sync_committee_period_at_slot(update.signature_slot)` +- Only `LightClientUpdate` with `sync_aggregate` from blocks on the canonical + chain as selected by fork choice are considered, regardless of ranking by + `is_better_update`. `LightClientUpdate` referring to orphaned blocks SHOULD + NOT be provided. + +### `create_light_client_finality_update` + +```python +def create_light_client_finality_update(update: LightClientUpdate) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=update.attested_header, + finalized_header=update.finalized_header, + finality_branch=update.finality_branch, + sync_aggregate=update.sync_aggregate, + signature_slot=update.signature_slot, + ) +``` + +Full nodes SHOULD provide the `LightClientFinalityUpdate` with the highest +`attested_header.beacon.slot` (if multiple, highest `signature_slot`) as +selected by fork choice, and SHOULD support a push mechanism to deliver new +`LightClientFinalityUpdate` whenever `finalized_header` changes. If that +`LightClientFinalityUpdate` does not have supermajority (> 2/3) sync committee +participation, a second `LightClientFinalityUpdate` SHOULD be delivered for the +same `finalized_header` once supermajority participation is obtained. + +### `create_light_client_optimistic_update` + +```python +def create_light_client_optimistic_update(update: LightClientUpdate) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=update.attested_header, + sync_aggregate=update.sync_aggregate, + signature_slot=update.signature_slot, + ) +``` + +Full nodes SHOULD provide the `LightClientOptimisticUpdate` with the highest +`attested_header.beacon.slot` (if multiple, highest `signature_slot`) as +selected by fork choice, and SHOULD support a push mechanism to deliver new +`LightClientOptimisticUpdate` whenever `attested_header` changes. diff --git a/specs/altair/light-client/light-client.md b/specs/altair/light-client/light-client.md new file mode 100644 index 0000000000..9db58c7594 --- /dev/null +++ b/specs/altair/light-client/light-client.md @@ -0,0 +1,57 @@ +# Altair Light Client -- Light Client + + + +- [Introduction](#introduction) +- [Light client sync process](#light-client-sync-process) + + + +## Introduction + +This document explains how light clients MAY obtain light client data to sync +with the network. + +## Light client sync process + +1. The light client MUST be configured out-of-band with a spec/preset (including + fork schedule), with `genesis_state` (including `genesis_time` and + `genesis_validators_root`), and with a trusted block root. The trusted block + SHOULD be within the weak subjectivity period, and its root SHOULD be from a + finalized `Checkpoint`. +2. The local clock is initialized based on the configured `genesis_time`, and + the current fork digest is determined to browse for and connect to relevant + light client data providers. +3. The light client fetches a + [`LightClientBootstrap`](./sync-protocol.md#lightclientbootstrap) object for + the configured trusted block root. The `bootstrap` object is passed to + [`initialize_light_client_store`](./sync-protocol.md#initialize_light_client_store) + to obtain a local [`LightClientStore`](./sync-protocol.md#lightclientstore). +4. The light client tracks the sync committee periods `finalized_period` from + `store.finalized_header.beacon.slot`, `optimistic_period` from + `store.optimistic_header.beacon.slot`, and `current_period` from + `current_slot` based on the local clock. + 1. When `finalized_period == optimistic_period` and + [`is_next_sync_committee_known`](./sync-protocol.md#is_next_sync_committee_known) + indicates `False`, the light client fetches a + [`LightClientUpdate`](./sync-protocol.md#lightclientupdate) for + `finalized_period`. If `finalized_period == current_period`, this fetch + SHOULD be scheduled at a random time before `current_period` advances. + 2. When `finalized_period + 1 < current_period`, the light client fetches a + `LightClientUpdate` for each sync committee period in range + `[finalized_period + 1, current_period)` (current period excluded) + 3. When `finalized_period + 1 >= current_period`, the light client keeps + observing + [`LightClientFinalityUpdate`](./sync-protocol.md#lightclientfinalityupdate) + and + [`LightClientOptimisticUpdate`](./sync-protocol.md#lightclientoptimisticupdate). + Received objects are passed to + [`process_light_client_finality_update`](./sync-protocol.md#process_light_client_finality_update) + and + [`process_light_client_optimistic_update`](./sync-protocol.md#process_light_client_optimistic_update). + This ensures that `finalized_header` and `optimistic_header` reflect the + latest blocks. +5. [`process_light_client_store_force_update`](./sync-protocol.md#process_light_client_store_force_update) + MAY be called based on use case dependent heuristics if light client sync + appears stuck. If available, falling back to an alternative syncing mechanism + to cover the affected sync committee period is preferred. diff --git a/specs/altair/light-client/p2p-interface.md b/specs/altair/light-client/p2p-interface.md new file mode 100644 index 0000000000..1ce7e5d358 --- /dev/null +++ b/specs/altair/light-client/p2p-interface.md @@ -0,0 +1,379 @@ +# Altair Light Client -- Networking + + + +- [Networking](#networking) + - [Configuration](#configuration) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`light_client_finality_update`](#light_client_finality_update) + - [`light_client_optimistic_update`](#light_client_optimistic_update) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [GetLightClientBootstrap](#getlightclientbootstrap) + - [LightClientUpdatesByRange](#lightclientupdatesbyrange) + - [GetLightClientFinalityUpdate](#getlightclientfinalityupdate) + - [GetLightClientOptimisticUpdate](#getlightclientoptimisticupdate) +- [Light clients](#light-clients) +- [Validator assignments](#validator-assignments) + - [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Sync committee](#sync-committee) + + + +## Networking + +This section extends the +[networking specification for Altair](../p2p-interface.md) with additional +messages, topics and data to the Req-Resp and Gossip domains. + +### Configuration + +| Name | Value | Description | +| ---------------------------------- | -------------- | ------------------------------------------------------------------- | +| `MAX_REQUEST_LIGHT_CLIENT_UPDATES` | `2**7` (= 128) | Maximum number of `LightClientUpdate` instances in a single request | + +### The gossip domain: gossipsub + +Gossip meshes are added to allow light clients to stay in sync with the network. + +#### Topics and messages + +New global topics are added to provide light clients with the latest updates. + +| name | Message Type | +| -------------------------------- | ----------------------------- | +| `light_client_finality_update` | `LightClientFinalityUpdate` | +| `light_client_optimistic_update` | `LightClientOptimisticUpdate` | + +##### Global topics + +###### `light_client_finality_update` + +This topic is used to propagate the latest `LightClientFinalityUpdate` to light +clients, allowing them to keep track of the latest `finalized_header`. + +The following validations MUST pass before forwarding the `finality_update` on +the network. + +- _[IGNORE]_ The `finalized_header.beacon.slot` is greater than that of all + previously forwarded `finality_update`s, or it matches the highest previously + forwarded slot and also has a `sync_aggregate` indicating supermajority (> + 2/3) sync committee participation while the previously forwarded + `finality_update` for that slot did not indicate supermajority +- _[IGNORE]_ The `finality_update` is received after the block at + `signature_slot` was given enough time to propagate through the network -- + i.e. validate that one-third of `finality_update.signature_slot` has + transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of + the slot, with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) + +For full nodes, the following validations MUST additionally pass before +forwarding the `finality_update` on the network. + +- _[IGNORE]_ The received `finality_update` matches the locally computed one + exactly (as defined in + [`create_light_client_finality_update`](./full-node.md#create_light_client_finality_update)) + +For light clients, the following validations MUST additionally pass before +forwarding the `finality_update` on the network. + +- _[REJECT]_ The `finality_update` is valid -- i.e. validate that + `process_light_client_finality_update` does not indicate errors +- _[IGNORE]_ The `finality_update` advances the `finalized_header` of the local + `LightClientStore` -- i.e. validate that processing `finality_update` + increases `store.finalized_header.beacon.slot` + +Light clients SHOULD call `process_light_client_finality_update` even if the +message is ignored. + +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)`: + + + +| `fork_version` | Message SSZ type | +| ------------------------------- | ---------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientFinalityUpdate` | + +###### `light_client_optimistic_update` + +This topic is used to propagate the latest `LightClientOptimisticUpdate` to +light clients, allowing them to keep track of the latest `optimistic_header`. + +The following validations MUST pass before forwarding the `optimistic_update` on +the network. + +- _[IGNORE]_ The `attested_header.beacon.slot` is greater than that of all + previously forwarded `optimistic_update`s +- _[IGNORE]_ The `optimistic_update` is received after the block at + `signature_slot` was given enough time to propagate through the network -- + i.e. validate that one-third of `optimistic_update.signature_slot` has + transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of + the slot, with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) + +For full nodes, the following validations MUST additionally pass before +forwarding the `optimistic_update` on the network. + +- _[IGNORE]_ The received `optimistic_update` matches the locally computed one + exactly (as defined in + [`create_light_client_optimistic_update`](./full-node.md#create_light_client_optimistic_update)) + +For light clients, the following validations MUST additionally pass before +forwarding the `optimistic_update` on the network. + +- _[REJECT]_ The `optimistic_update` is valid -- i.e. validate that + `process_light_client_optimistic_update` does not indicate errors +- _[IGNORE]_ The `optimistic_update` either matches corresponding fields of the + most recently forwarded `LightClientFinalityUpdate` (if any), or it advances + the `optimistic_header` of the local `LightClientStore` -- i.e. validate that + processing `optimistic_update` increases `store.optimistic_header.beacon.slot` + +Light clients SHOULD call `process_light_client_optimistic_update` even if the +message is ignored. + +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)`: + + + +| `fork_version` | Message SSZ type | +| ------------------------------- | ------------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientOptimisticUpdate` | + +### The Req/Resp domain + +#### Messages + +##### GetLightClientBootstrap + +**Protocol ID:** `/eth2/beacon_chain/req/light_client_bootstrap/1/` + +Request Content: + +``` +( + Root +) +``` + +Response Content: + +``` +( + LightClientBootstrap +) +``` + +Requests the `LightClientBootstrap` structure corresponding to a given +post-Altair beacon block root. + +The request MUST be encoded as an SSZ-field. + +Peers SHOULD provide results as defined in +[`create_light_client_bootstrap`](./full-node.md#create_light_client_bootstrap). +To fulfill a request, the requested block and its post state need to be known. + +When a `LightClientBootstrap` instance cannot be produced for a given block +root, peers SHOULD respond with error code `3: ResourceUnavailable`. + +A `ForkDigest`-context based on +`compute_fork_version(compute_epoch_at_slot(bootstrap.header.beacon.slot))` is +used to select the fork namespace of the Response type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Response SSZ type | +| ------------------------------- | ----------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientBootstrap` | + +##### LightClientUpdatesByRange + +**Protocol ID:** `/eth2/beacon_chain/req/light_client_updates_by_range/1/` + +Request Content: + +``` +( + start_period: uint64 + count: uint64 +) +``` + +Response Content: + +``` +( + List[LightClientUpdate, MAX_REQUEST_LIGHT_CLIENT_UPDATES] +) +``` + +Requests the `LightClientUpdate` instances in the sync committee period range +`[start_period, start_period + count)`, leading up to the current head sync +committee period as selected by fork choice. + +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 `LightClientUpdate` payload. + +Peers SHOULD provide results as defined in +[`create_light_client_update`](./full-node.md#create_light_client_update). They +MUST respond with at least the earliest known result within the requested range, +and MUST send results in consecutive order (by period). The response MUST NOT +contain more than `min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count)` results. + +For each `response_chunk`, a `ForkDigest`-context based on +`compute_fork_version(compute_epoch_at_slot(update.attested_header.beacon.slot))` +is used to select the fork namespace of the Response type. Note that this +`fork_version` may be different from the one used to verify the +`update.sync_aggregate`, which is based on `update.signature_slot`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Response chunk SSZ type | +| ------------------------------- | -------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientUpdate` | + +##### GetLightClientFinalityUpdate + +**Protocol ID:** `/eth2/beacon_chain/req/light_client_finality_update/1/` + +No Request Content. + +Response Content: + +``` +( + LightClientFinalityUpdate +) +``` + +Requests the latest `LightClientFinalityUpdate` known by a peer. + +Peers SHOULD provide results as defined in +[`create_light_client_finality_update`](./full-node.md#create_light_client_finality_update). + +When no `LightClientFinalityUpdate` is available, peers SHOULD respond with +error code `3: ResourceUnavailable`. + +A `ForkDigest`-context based on +`compute_fork_version(compute_epoch_at_slot(finality_update.attested_header.beacon.slot))` +is used to select the fork namespace of the Response type. Note that this +`fork_version` may be different from the one used to verify the +`finality_update.sync_aggregate`, which is based on +`finality_update.signature_slot`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Response SSZ type | +| ------------------------------- | ---------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientFinalityUpdate` | + +##### GetLightClientOptimisticUpdate + +**Protocol ID:** `/eth2/beacon_chain/req/light_client_optimistic_update/1/` + +No Request Content. + +Response Content: + +``` +( + LightClientOptimisticUpdate +) +``` + +Requests the latest `LightClientOptimisticUpdate` known by a peer. + +Peers SHOULD provide results as defined in +[`create_light_client_optimistic_update`](./full-node.md#create_light_client_optimistic_update). + +When no `LightClientOptimisticUpdate` is available, peers SHOULD respond with +error code `3: ResourceUnavailable`. + +A `ForkDigest`-context based on +`compute_fork_version(compute_epoch_at_slot(optimistic_update.attested_header.beacon.slot))` +is used to select the fork namespace of the Response type. Note that this +`fork_version` may be different from the one used to verify the +`optimistic_update.sync_aggregate`, which is based on +`optimistic_update.signature_slot`. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Response SSZ type | +| ------------------------------- | ------------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` and later | `altair.LightClientOptimisticUpdate` | + +## Light clients + +Light clients using libp2p to stay in sync with the network SHOULD subscribe to +the [`light_client_finality_update`](#light_client_finality_update) and +[`light_client_optimistic_update`](#light_client_optimistic_update) pubsub +topics and validate all received messages while the +[light client sync process](./light-client.md#light-client-sync-process) +supports processing `LightClientFinalityUpdate` and +`LightClientOptimisticUpdate` structures. + +Light clients MAY also collect historic light client data and make it available +to other peers. If they do, they SHOULD advertise supported message endpoints in +[the Req/Resp domain](#the-reqresp-domain), and MAY also update the contents of +their [`Status`](../../phase0/p2p-interface.md#status) message to reflect the +locally available light client data. + +If only limited light client data is locally available, the light client SHOULD +use data based on `genesis_block` and `GENESIS_SLOT` in its `Status` message. +Hybrid peers that also implement full node functionality MUST only incorporate +data based on their full node sync progress into their `Status` message. + +## Validator assignments + +This section extends the [honest validator specification](../validator.md) with +additional responsibilities to enable light clients to sync with the network. + +### Beacon chain responsibilities + +All full nodes SHOULD subscribe to and provide stability on the +[`light_client_finality_update`](#light_client_finality_update) and +[`light_client_optimistic_update`](#light_client_optimistic_update) pubsub +topics by validating all received messages. + +### Sync committee + +Whenever fork choice selects a new head block with a sync aggregate +participation `>= MIN_SYNC_COMMITTEE_PARTICIPANTS` and a post-Altair parent +block, full nodes with at least one validator assigned to the current sync +committee at the block's `slot` SHOULD broadcast derived light client data as +follows: + +- If `finalized_header.beacon.slot` increased, a `LightClientFinalityUpdate` + SHOULD be broadcasted to the pubsub topic `light_client_finality_update` if no + matching message has not yet been forwarded as part of gossip validation. +- If `attested_header.beacon.slot` increased, a `LightClientOptimisticUpdate` + SHOULD be broadcasted to the pubsub topic `light_client_optimistic_update` if + no matching message has not yet been forwarded as part of gossip validation. + +These messages SHOULD be broadcasted after one-third of `slot` has transpired +(`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of the slot). +To ensure that the corresponding block was given enough time to propagate +through the network, they SHOULD NOT be sent earlier. Note that this is +different from how other messages are handled, e.g., attestations, which may be +sent early. diff --git a/specs/altair/light-client/sync-protocol.md b/specs/altair/light-client/sync-protocol.md new file mode 100644 index 0000000000..8a0d8a899e --- /dev/null +++ b/specs/altair/light-client/sync-protocol.md @@ -0,0 +1,590 @@ +# Altair Light Client -- Sync Protocol + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) +- [Preset](#preset) + - [Misc](#misc) +- [Containers](#containers) + - [`LightClientHeader`](#lightclientheader) + - [`LightClientBootstrap`](#lightclientbootstrap) + - [`LightClientUpdate`](#lightclientupdate) + - [`LightClientFinalityUpdate`](#lightclientfinalityupdate) + - [`LightClientOptimisticUpdate`](#lightclientoptimisticupdate) + - [`LightClientStore`](#lightclientstore) +- [Helper functions](#helper-functions) + - [`finalized_root_gindex_at_slot`](#finalized_root_gindex_at_slot) + - [`current_sync_committee_gindex_at_slot`](#current_sync_committee_gindex_at_slot) + - [`next_sync_committee_gindex_at_slot`](#next_sync_committee_gindex_at_slot) + - [`is_valid_light_client_header`](#is_valid_light_client_header) + - [`is_sync_committee_update`](#is_sync_committee_update) + - [`is_finality_update`](#is_finality_update) + - [`is_better_update`](#is_better_update) + - [`is_next_sync_committee_known`](#is_next_sync_committee_known) + - [`get_safety_threshold`](#get_safety_threshold) + - [`get_subtree_index`](#get_subtree_index) + - [`is_valid_normalized_merkle_branch`](#is_valid_normalized_merkle_branch) + - [`compute_sync_committee_period_at_slot`](#compute_sync_committee_period_at_slot) +- [Light client initialization](#light-client-initialization) + - [`initialize_light_client_store`](#initialize_light_client_store) +- [Light client state updates](#light-client-state-updates) + - [`validate_light_client_update`](#validate_light_client_update) + - [`apply_light_client_update`](#apply_light_client_update) + - [`process_light_client_store_force_update`](#process_light_client_store_force_update) + - [`process_light_client_update`](#process_light_client_update) + - [`process_light_client_finality_update`](#process_light_client_finality_update) + - [`process_light_client_optimistic_update`](#process_light_client_optimistic_update) + + + +## Introduction + +The beacon chain is designed to be light client friendly for constrained +environments to access Ethereum with reasonable safety and liveness. Such +environments include resource-constrained devices (e.g. phones for +trust-minimized wallets) and metered VMs (e.g. blockchain VMs for cross-chain +bridges). + +This document suggests a minimal light client design for the beacon chain that +uses sync committees introduced in +[this beacon chain extension](../beacon-chain.md). + +Additional documents describe how the light client sync protocol can be used: + +- [Full node](./full-node.md) +- [Light client](./light-client.md) +- [Networking](./p2p-interface.md) + +## Custom types + +| Name | SSZ equivalent | Description | +| ---------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------- | +| `FinalityBranch` | `Vector[Bytes32, floorlog2(FINALIZED_ROOT_GINDEX)]` | Merkle branch of `finalized_checkpoint.root` within `BeaconState` | +| `CurrentSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_GINDEX)]` | Merkle branch of `current_sync_committee` within `BeaconState` | +| `NextSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_GINDEX)]` | Merkle branch of `next_sync_committee` within `BeaconState` | + +## Constants + +| Name | Value | +| ------------------------------- | ---------------------------------------------------------------------------- | +| `FINALIZED_ROOT_GINDEX` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 105) | +| `CURRENT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(BeaconState, 'current_sync_committee')` (= 54) | +| `NEXT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 55) | + +## Preset + +### Misc + +| Name | Value | Unit | Duration | +| --------------------------------- | ---------------------------------------------------- | ---------- | ----------- | +| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | validators | | +| `UPDATE_TIMEOUT` | `SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD` | slots | ~27.3 hours | + +## Containers + +### `LightClientHeader` + +```python +class LightClientHeader(Container): + beacon: BeaconBlockHeader +``` + +Future upgrades may introduce additional fields to this structure, and validate +them by extending +[`is_valid_light_client_header`](#is_valid_light_client_header). + +### `LightClientBootstrap` + +```python +class LightClientBootstrap(Container): + # Header matching the requested beacon block root + header: LightClientHeader + # Current sync committee corresponding to `header.beacon.state_root` + current_sync_committee: SyncCommittee + current_sync_committee_branch: CurrentSyncCommitteeBranch +``` + +### `LightClientUpdate` + +```python +class LightClientUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader + # Next sync committee corresponding to `attested_header.beacon.state_root` + next_sync_committee: SyncCommittee + next_sync_committee_branch: NextSyncCommitteeBranch + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header: LightClientHeader + finality_branch: FinalityBranch + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### `LightClientFinalityUpdate` + +```python +class LightClientFinalityUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader + # Finalized header corresponding to `attested_header.beacon.state_root` + finalized_header: LightClientHeader + finality_branch: FinalityBranch + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### `LightClientOptimisticUpdate` + +```python +class LightClientOptimisticUpdate(Container): + # Header attested to by the sync committee + attested_header: LightClientHeader + # Sync committee aggregate signature + sync_aggregate: SyncAggregate + # Slot at which the aggregate signature was created (untrusted) + signature_slot: Slot +``` + +### `LightClientStore` + +```python +@dataclass +class LightClientStore(object): + # Header that is finalized + finalized_header: LightClientHeader + # Sync committees corresponding to the finalized header + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # Best available header to switch finalized head to if we see nothing else + best_valid_update: Optional[LightClientUpdate] + # Most recent available reasonably-safe header + optimistic_header: LightClientHeader + # Max number of active participants in a sync committee (used to calculate safety threshold) + previous_max_active_participants: uint64 + current_max_active_participants: uint64 +``` + +## Helper functions + +### `finalized_root_gindex_at_slot` + +```python +def finalized_root_gindex_at_slot(_slot: Slot) -> GeneralizedIndex: + return FINALIZED_ROOT_GINDEX +``` + +### `current_sync_committee_gindex_at_slot` + +```python +def current_sync_committee_gindex_at_slot(_slot: Slot) -> GeneralizedIndex: + return CURRENT_SYNC_COMMITTEE_GINDEX +``` + +### `next_sync_committee_gindex_at_slot` + +```python +def next_sync_committee_gindex_at_slot(_slot: Slot) -> GeneralizedIndex: + return NEXT_SYNC_COMMITTEE_GINDEX +``` + +### `is_valid_light_client_header` + +```python +def is_valid_light_client_header(_header: LightClientHeader) -> bool: + return True +``` + +### `is_sync_committee_update` + +```python +def is_sync_committee_update(update: LightClientUpdate) -> bool: + return update.next_sync_committee_branch != NextSyncCommitteeBranch() +``` + +### `is_finality_update` + +```python +def is_finality_update(update: LightClientUpdate) -> bool: + return update.finality_branch != FinalityBranch() +``` + +### `is_better_update` + +```python +def is_better_update(new_update: LightClientUpdate, old_update: LightClientUpdate) -> bool: + # Compare supermajority (> 2/3) sync committee participation + max_active_participants = len(new_update.sync_aggregate.sync_committee_bits) + new_num_active_participants = sum(new_update.sync_aggregate.sync_committee_bits) + old_num_active_participants = sum(old_update.sync_aggregate.sync_committee_bits) + new_has_supermajority = new_num_active_participants * 3 >= max_active_participants * 2 + old_has_supermajority = old_num_active_participants * 3 >= max_active_participants * 2 + if new_has_supermajority != old_has_supermajority: + return new_has_supermajority + if not new_has_supermajority and new_num_active_participants != old_num_active_participants: + return new_num_active_participants > old_num_active_participants + + # Compare presence of relevant sync committee + new_has_relevant_sync_committee = is_sync_committee_update(new_update) and ( + compute_sync_committee_period_at_slot(new_update.attested_header.beacon.slot) + == compute_sync_committee_period_at_slot(new_update.signature_slot) + ) + old_has_relevant_sync_committee = is_sync_committee_update(old_update) and ( + compute_sync_committee_period_at_slot(old_update.attested_header.beacon.slot) + == compute_sync_committee_period_at_slot(old_update.signature_slot) + ) + if new_has_relevant_sync_committee != old_has_relevant_sync_committee: + return new_has_relevant_sync_committee + + # Compare indication of any finality + new_has_finality = is_finality_update(new_update) + old_has_finality = is_finality_update(old_update) + if new_has_finality != old_has_finality: + return new_has_finality + + # Compare sync committee finality + if new_has_finality: + new_has_sync_committee_finality = compute_sync_committee_period_at_slot( + new_update.finalized_header.beacon.slot + ) == compute_sync_committee_period_at_slot(new_update.attested_header.beacon.slot) + old_has_sync_committee_finality = compute_sync_committee_period_at_slot( + old_update.finalized_header.beacon.slot + ) == compute_sync_committee_period_at_slot(old_update.attested_header.beacon.slot) + if new_has_sync_committee_finality != old_has_sync_committee_finality: + return new_has_sync_committee_finality + + # Tiebreaker 1: Sync committee participation beyond supermajority + if new_num_active_participants != old_num_active_participants: + return new_num_active_participants > old_num_active_participants + + # Tiebreaker 2: Prefer older data (fewer changes to best) + if new_update.attested_header.beacon.slot != old_update.attested_header.beacon.slot: + return new_update.attested_header.beacon.slot < old_update.attested_header.beacon.slot + + # Tiebreaker 3: Prefer updates with earlier signature slots + return new_update.signature_slot < old_update.signature_slot +``` + +### `is_next_sync_committee_known` + +```python +def is_next_sync_committee_known(store: LightClientStore) -> bool: + return store.next_sync_committee != SyncCommittee() +``` + +### `get_safety_threshold` + +```python +def get_safety_threshold(store: LightClientStore) -> uint64: + return ( + max( + store.previous_max_active_participants, + store.current_max_active_participants, + ) + // 2 + ) +``` + +### `get_subtree_index` + +```python +def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: + return uint64(generalized_index % 2 ** (floorlog2(generalized_index))) +``` + +### `is_valid_normalized_merkle_branch` + +```python +def is_valid_normalized_merkle_branch( + leaf: Bytes32, branch: Sequence[Bytes32], gindex: GeneralizedIndex, root: Root +) -> bool: + depth = floorlog2(gindex) + index = get_subtree_index(gindex) + num_extra = len(branch) - depth + for i in range(num_extra): + if branch[i] != Bytes32(): + return False + return is_valid_merkle_branch(leaf, branch[num_extra:], depth, index, root) +``` + +### `compute_sync_committee_period_at_slot` + +```python +def compute_sync_committee_period_at_slot(slot: Slot) -> uint64: + return compute_sync_committee_period(compute_epoch_at_slot(slot)) +``` + +## Light client initialization + +A light client maintains its state in a `store` object of type +`LightClientStore`. `initialize_light_client_store` initializes a new `store` +with a received `LightClientBootstrap` derived from a given +`trusted_block_root`. + +### `initialize_light_client_store` + +```python +def initialize_light_client_store( + trusted_block_root: Root, bootstrap: LightClientBootstrap +) -> LightClientStore: + assert is_valid_light_client_header(bootstrap.header) + assert hash_tree_root(bootstrap.header.beacon) == trusted_block_root + + assert is_valid_normalized_merkle_branch( + leaf=hash_tree_root(bootstrap.current_sync_committee), + branch=bootstrap.current_sync_committee_branch, + gindex=current_sync_committee_gindex_at_slot(bootstrap.header.beacon.slot), + root=bootstrap.header.beacon.state_root, + ) + + return LightClientStore( + finalized_header=bootstrap.header, + current_sync_committee=bootstrap.current_sync_committee, + next_sync_committee=SyncCommittee(), + best_valid_update=None, + optimistic_header=bootstrap.header, + previous_max_active_participants=0, + current_max_active_participants=0, + ) +``` + +## Light client state updates + +- A light client receives objects of type `LightClientUpdate`, + `LightClientFinalityUpdate` and `LightClientOptimisticUpdate`: + - **`update: LightClientUpdate`**: Every `update` triggers + `process_light_client_update(store, update, current_slot, genesis_validators_root)` + where `current_slot` is the current slot based on a local clock. + - **`finality_update: LightClientFinalityUpdate`**: Every `finality_update` + triggers + `process_light_client_finality_update(store, finality_update, current_slot, genesis_validators_root)`. + - **`optimistic_update: LightClientOptimisticUpdate`**: Every + `optimistic_update` triggers + `process_light_client_optimistic_update(store, optimistic_update, current_slot, genesis_validators_root)`. +- `process_light_client_store_force_update` MAY be called based on use case + dependent heuristics if light client sync appears stuck. + +### `validate_light_client_update` + +```python +def validate_light_client_update( + store: LightClientStore, + update: LightClientUpdate, + current_slot: Slot, + genesis_validators_root: Root, +) -> None: + # Verify sync committee has sufficient participants + sync_aggregate = update.sync_aggregate + assert sum(sync_aggregate.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS + + # Verify update does not skip a sync committee period + assert is_valid_light_client_header(update.attested_header) + update_attested_slot = update.attested_header.beacon.slot + update_finalized_slot = update.finalized_header.beacon.slot + assert current_slot >= update.signature_slot > update_attested_slot >= update_finalized_slot + store_period = compute_sync_committee_period_at_slot(store.finalized_header.beacon.slot) + update_signature_period = compute_sync_committee_period_at_slot(update.signature_slot) + if is_next_sync_committee_known(store): + assert update_signature_period in (store_period, store_period + 1) + else: + assert update_signature_period == store_period + + # Verify update is relevant + update_attested_period = compute_sync_committee_period_at_slot(update_attested_slot) + update_has_next_sync_committee = not is_next_sync_committee_known(store) and ( + is_sync_committee_update(update) and update_attested_period == store_period + ) + assert ( + update_attested_slot > store.finalized_header.beacon.slot or update_has_next_sync_committee + ) + + # Verify that the `finality_branch`, if present, confirms `finalized_header` + # to match the finalized checkpoint root saved in the state of `attested_header`. + # Note that the genesis finalized checkpoint root is represented as a zero hash. + if not is_finality_update(update): + assert update.finalized_header == LightClientHeader() + else: + if update_finalized_slot == GENESIS_SLOT: + assert update.finalized_header == LightClientHeader() + finalized_root = Bytes32() + else: + assert is_valid_light_client_header(update.finalized_header) + finalized_root = hash_tree_root(update.finalized_header.beacon) + assert is_valid_normalized_merkle_branch( + leaf=finalized_root, + branch=update.finality_branch, + gindex=finalized_root_gindex_at_slot(update.attested_header.beacon.slot), + root=update.attested_header.beacon.state_root, + ) + + # Verify that the `next_sync_committee`, if present, actually is the next sync committee saved in the + # state of the `attested_header` + if not is_sync_committee_update(update): + assert update.next_sync_committee == SyncCommittee() + else: + if update_attested_period == store_period and is_next_sync_committee_known(store): + assert update.next_sync_committee == store.next_sync_committee + assert is_valid_normalized_merkle_branch( + leaf=hash_tree_root(update.next_sync_committee), + branch=update.next_sync_committee_branch, + gindex=next_sync_committee_gindex_at_slot(update.attested_header.beacon.slot), + root=update.attested_header.beacon.state_root, + ) + + # Verify sync committee aggregate signature + if update_signature_period == store_period: + sync_committee = store.current_sync_committee + else: + sync_committee = store.next_sync_committee + participant_pubkeys = [ + pubkey + for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee.pubkeys) + if bit + ] + fork_version_slot = max(update.signature_slot, Slot(1)) - Slot(1) + fork_version = compute_fork_version(compute_epoch_at_slot(fork_version_slot)) + domain = compute_domain(DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root) + signing_root = compute_signing_root(update.attested_header.beacon, domain) + assert bls.FastAggregateVerify( + participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature + ) +``` + +### `apply_light_client_update` + +```python +def apply_light_client_update(store: LightClientStore, update: LightClientUpdate) -> None: + store_period = compute_sync_committee_period_at_slot(store.finalized_header.beacon.slot) + update_finalized_period = compute_sync_committee_period_at_slot( + update.finalized_header.beacon.slot + ) + if not is_next_sync_committee_known(store): + assert update_finalized_period == store_period + store.next_sync_committee = update.next_sync_committee + elif update_finalized_period == store_period + 1: + store.current_sync_committee = store.next_sync_committee + store.next_sync_committee = update.next_sync_committee + store.previous_max_active_participants = store.current_max_active_participants + store.current_max_active_participants = 0 + if update.finalized_header.beacon.slot > store.finalized_header.beacon.slot: + store.finalized_header = update.finalized_header + if store.finalized_header.beacon.slot > store.optimistic_header.beacon.slot: + store.optimistic_header = store.finalized_header +``` + +### `process_light_client_store_force_update` + +```python +def process_light_client_store_force_update(store: LightClientStore, current_slot: Slot) -> None: + if ( + current_slot > store.finalized_header.beacon.slot + UPDATE_TIMEOUT + and store.best_valid_update is not None + ): + # Forced best update when the update timeout has elapsed. + # Because the apply logic waits for `finalized_header.beacon.slot` to indicate sync committee finality, + # the `attested_header` may be treated as `finalized_header` in extended periods of non-finality + # to guarantee progression into later sync committee periods according to `is_better_update`. + if ( + store.best_valid_update.finalized_header.beacon.slot + <= store.finalized_header.beacon.slot + ): + store.best_valid_update.finalized_header = store.best_valid_update.attested_header + apply_light_client_update(store, store.best_valid_update) + store.best_valid_update = None +``` + +### `process_light_client_update` + +```python +def process_light_client_update( + store: LightClientStore, + update: LightClientUpdate, + current_slot: Slot, + genesis_validators_root: Root, +) -> None: + validate_light_client_update(store, update, current_slot, genesis_validators_root) + + sync_committee_bits = update.sync_aggregate.sync_committee_bits + + # Update the best update in case we have to force-update to it if the timeout elapses + if store.best_valid_update is None or is_better_update(update, store.best_valid_update): + store.best_valid_update = update + + # Track the maximum number of active participants in the committee signatures + store.current_max_active_participants = max( + store.current_max_active_participants, + sum(sync_committee_bits), + ) + + # Update the optimistic header + if ( + sum(sync_committee_bits) > get_safety_threshold(store) + and update.attested_header.beacon.slot > store.optimistic_header.beacon.slot + ): + store.optimistic_header = update.attested_header + + # Update finalized header + update_has_finalized_next_sync_committee = ( + not is_next_sync_committee_known(store) + and is_sync_committee_update(update) + and is_finality_update(update) + and ( + compute_sync_committee_period_at_slot(update.finalized_header.beacon.slot) + == compute_sync_committee_period_at_slot(update.attested_header.beacon.slot) + ) + ) + if sum(sync_committee_bits) * 3 >= len(sync_committee_bits) * 2 and ( + update.finalized_header.beacon.slot > store.finalized_header.beacon.slot + or update_has_finalized_next_sync_committee + ): + # Normal update through 2/3 threshold + apply_light_client_update(store, update) + store.best_valid_update = None +``` + +### `process_light_client_finality_update` + +```python +def process_light_client_finality_update( + store: LightClientStore, + finality_update: LightClientFinalityUpdate, + current_slot: Slot, + genesis_validators_root: Root, +) -> None: + update = LightClientUpdate( + attested_header=finality_update.attested_header, + next_sync_committee=SyncCommittee(), + next_sync_committee_branch=NextSyncCommitteeBranch(), + finalized_header=finality_update.finalized_header, + finality_branch=finality_update.finality_branch, + sync_aggregate=finality_update.sync_aggregate, + signature_slot=finality_update.signature_slot, + ) + process_light_client_update(store, update, current_slot, genesis_validators_root) +``` + +### `process_light_client_optimistic_update` + +```python +def process_light_client_optimistic_update( + store: LightClientStore, + optimistic_update: LightClientOptimisticUpdate, + current_slot: Slot, + genesis_validators_root: Root, +) -> None: + update = LightClientUpdate( + attested_header=optimistic_update.attested_header, + next_sync_committee=SyncCommittee(), + next_sync_committee_branch=NextSyncCommitteeBranch(), + finalized_header=LightClientHeader(), + finality_branch=FinalityBranch(), + sync_aggregate=optimistic_update.sync_aggregate, + signature_slot=optimistic_update.signature_slot, + ) + process_light_client_update(store, update, current_slot, genesis_validators_root) +``` diff --git a/specs/altair/p2p-interface.md b/specs/altair/p2p-interface.md index f2700b3115..ce67ab43d4 100644 --- a/specs/altair/p2p-interface.md +++ b/specs/altair/p2p-interface.md @@ -1,19 +1,8 @@ # 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 - - - - - - - [Warning](#warning) +- [Introduction](#introduction) - [Modifications in Altair](#modifications-in-altair) - [MetaData](#metadata) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) @@ -34,20 +23,28 @@ Altair adds new messages, topics and data to the Req-Resp, Gossip and Discovery - [GetMetaData v2](#getmetadata-v2) - [Transitioning from v1 to v2](#transitioning-from-v1-to-v2) - [The discovery domain: discv5](#the-discovery-domain-discv5) + - [ENR structure](#enr-structure) + - [Sync committee bitfield](#sync-committee-bitfield) + + - - +## Introduction -## Warning +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. -This document is currently illustrative for early Altair testnets and some parts are subject to change. -Refer to the note in the [validator guide](./validator.md) for further details. +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 +## Modifications in Altair -## MetaData +### MetaData -The `MetaData` stored locally by clients is updated with an additional field to communicate the sync committee subnet subscriptions: +The `MetaData` stored locally by clients is updated with an additional field to +communicate the sync committee subnet subscriptions: ``` ( @@ -59,74 +56,104 @@ The `MetaData` stored locally by clients is updated with an additional field to Where -- `seq_number` and `attnets` have the same meaning defined in the Phase 0 document. -- `syncnets` is a `Bitvector` representing the node's sync committee subnet subscriptions. This field should mirror the data in the node's ENR as outlined in the [validator guide](./validator.md#sync-committee-subnet-stability). +- `seq_number` and `attnets` have the same meaning defined in the Phase 0 + document. +- `syncnets` is a `Bitvector` representing the node's sync committee subnet + subscriptions. This field should mirror the data in the node's ENR as outlined + in the [validator guide](./validator.md#sync-committee-subnet-stability). + +### The gossip domain: gossipsub -## The gossip domain: gossipsub +Gossip meshes are added in Altair to support the consensus activities of the +sync committees. Validators use an aggregation scheme to balance the processing +and networking load across all of the relevant actors. -Gossip meshes are added in Altair to support the consensus activities of the sync committees. -Validators use an aggregation scheme to balance the processing and networking load across all of the relevant actors. +#### Topics and messages -### Topics and messages +Topics follow the same specification as in the Phase 0 document. New topics are +added in Altair to support the sync committees and the beacon block topic is +updated with the modified type. -Topics follow the same specification as in the Phase 0 document. -New topics are added in Altair to support the sync committees and the beacon block topic is updated with the modified type. +The specification around the creation, validation, and dissemination of messages +has not changed from the Phase 0 document. -The specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 document. +The derivation of the `message-id` has changed starting with Altair to +incorporate the message `topic` along with the message `data`. These are fields +of the `Message` Protobuf, and interpreted as empty byte strings if missing. The +`message-id` MUST be the following 20 byte value computed from the message: -The derivation of the `message-id` has changed starting with Altair to incorporate the message `topic` along with the message `data`. These are fields of the `Message` Protobuf, and interpreted as empty byte strings if missing. -The `message-id` MUST be the following 20 byte value computed from the message: -* If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of - the concatenation of the following data: `MESSAGE_DOMAIN_VALID_SNAPPY`, the length of the topic byte string (encoded as little-endian `uint64`), - the topic byte string, and the snappy decompressed message data: - i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + uint_to_bytes(uint64(len(message.topic))) + message.topic + snappy_decompress(message.data))[:20]`. -* Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of - the concatenation of the following data: `MESSAGE_DOMAIN_INVALID_SNAPPY`, the length of the topic byte string (encoded as little-endian `uint64`), - the topic byte string, and the raw message data: - i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + uint_to_bytes(uint64(len(message.topic))) + message.topic + message.data)[:20]`. +- If `message.data` has a valid snappy decompression, set `message-id` to the + first 20 bytes of the `SHA256` hash of the concatenation of the following + data: `MESSAGE_DOMAIN_VALID_SNAPPY`, the length of the topic byte string + (encoded as little-endian `uint64`), the topic byte string, and the snappy + decompressed message data: i.e. + `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + uint_to_bytes(uint64(len(message.topic))) + message.topic + snappy_decompress(message.data))[:20]`. +- Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of the + concatenation of the following data: `MESSAGE_DOMAIN_INVALID_SNAPPY`, the + length of the topic byte string (encoded as little-endian `uint64`), the topic + byte string, and the raw message data: i.e. + `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + uint_to_bytes(uint64(len(message.topic))) + message.topic + message.data)[:20]`. -Implementations may need to carefully handle the function that computes the `message-id`. In particular, messages on topics with the Phase 0 -fork digest should use the `message-id` procedure specified in the Phase 0 document. -Messages on topics with the Altair fork digest should use the `message-id` procedure defined here. -If an implementation only supports a single `message-id` function, it can define a switch inline; -for example, `if topic in phase0_topics: return phase0_msg_id_fn(message) else return altair_msg_id_fn(message)`. +Implementations may need to carefully handle the function that computes the +`message-id`. In particular, messages on topics with the Phase 0 fork digest +should use the `message-id` procedure specified in the Phase 0 document. +Messages on topics with the Altair fork digest should use the `message-id` +procedure defined here. If an implementation only supports a single `message-id` +function, it can define a switch inline; for example, +`if topic in phase0_topics: return phase0_msg_id_fn(message) else return altair_msg_id_fn(message)`. -The new topics along with the type of the `data` field of a gossipsub message are given in this table: +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: -| Name | Message Type | -| - | - | -| `beacon_block` | `SignedBeaconBlock` (modified) | -| `sync_committee_contribution_and_proof` | `SignedContributionAndProof` | -| `sync_committee_{subnet_id}` | `SyncCommitteeMessage` | +| Name | Message Type | +| --------------------------------------- | ------------------------------ | +| `beacon_block` | `SignedBeaconBlock` (modified) | +| `sync_committee_contribution_and_proof` | `SignedContributionAndProof` | +| `sync_committee_{subnet_id}` | `SyncCommitteeMessage` | -Definitions of these new types can be found in the [Altair validator guide](./validator.md#containers). +Definitions of these new types can be found in the +[Altair validator guide](./validator.md#containers). -Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics. +Note that the `ForkDigestValue` path segment of the topic separates the old and +the new `beacon_block` topics. -#### Global topics +##### Global topics -Altair changes the type of the global beacon block topic and adds one global topic to propagate partially aggregated sync committee messages to all potential proposers of beacon blocks. +Altair changes the type of the global beacon block topic and adds one global +topic to propagate partially aggregated sync committee messages to all potential +proposers of beacon blocks. -##### `beacon_block` +###### `beacon_block` -The existing specification for this topic does not change from the Phase 0 document, -but the type of the payload does change to the (modified) `SignedBeaconBlock`. -This type changes due to the inclusion of the inner `BeaconBlockBody` that is modified in Altair. +The existing specification for this topic does not change from the Phase 0 +document, but the type of the payload does change to the (modified) +`SignedBeaconBlock`. This type changes due to the inclusion of the inner +`BeaconBlockBody` that is modified in Altair. -See the [state transition document](./beacon-chain.md#beaconblockbody) for Altair for further details. +See the [state transition document](./beacon-chain.md#beaconblockbody) for +Altair for further details. -##### `sync_committee_contribution_and_proof` +###### `sync_committee_contribution_and_proof` -This topic is used to propagate partially aggregated sync committee messages to be included in future blocks. +This topic is used to propagate partially aggregated sync committee messages to +be included in future blocks. -The following validations MUST pass before forwarding the `signed_contribution_and_proof` on the network; define `contribution_and_proof = signed_contribution_and_proof.message`, `contribution = contribution_and_proof.contribution`, and the following function `get_sync_subcommittee_pubkeys` for convenience: +The following validations MUST pass before forwarding the +`signed_contribution_and_proof` on the network; define +`contribution_and_proof = signed_contribution_and_proof.message`, +`contribution = contribution_and_proof.contribution`, and the following function +`get_sync_subcommittee_pubkeys` for convenience: ```python -def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64) -> Sequence[BLSPubkey]: +def get_sync_subcommittee_pubkeys( + state: BeaconState, subcommittee_index: uint64 +) -> Sequence[BLSPubkey]: # Committees assigned to `slot` sign for `slot - 1` # This creates the exceptional logic below when transitioning between sync committee periods next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period( + next_slot_epoch + ): sync_committee = state.current_sync_committee else: sync_committee = state.next_sync_committee @@ -134,133 +161,186 @@ def get_sync_subcommittee_pubkeys(state: BeaconState, subcommittee_index: uint64 # Return pubkeys for the subcommittee index sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT i = subcommittee_index * sync_subcommittee_size - return sync_committee.pubkeys[i:i + sync_subcommittee_size] + return sync_committee.pubkeys[i : i + sync_subcommittee_size] ``` -- _[IGNORE]_ The contribution's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `contribution.slot == current_slot`. -- _[REJECT]_ The subcommittee index is in the allowed range, i.e. `contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT`. -- _[REJECT]_ The contribution has participants -- - that is, `any(contribution.aggregation_bits)`. -- _[REJECT]_ `contribution_and_proof.selection_proof` selects the validator as an aggregator for the slot -- i.e. `is_sync_committee_aggregator(contribution_and_proof.selection_proof)` returns `True`. -- _[REJECT]_ The aggregator's validator index is in the declared subcommittee of the current sync committee -- - i.e. `state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index)`. -- _[IGNORE]_ The sync committee contribution is the first valid contribution received for the aggregator with index `contribution_and_proof.aggregator_index` - for the slot `contribution.slot` and subcommittee index `contribution.subcommittee_index` - (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE` for this topic that can be flushed after each slot). -- _[REJECT]_ The `contribution_and_proof.selection_proof` is a valid signature of the `SyncAggregatorSelectionData` derived from the `contribution` by the validator with index `contribution_and_proof.aggregator_index`. -- _[REJECT]_ The aggregator signature, `signed_contribution_and_proof.signature`, is valid. -- _[REJECT]_ The aggregate signature is valid for the message `beacon_block_root` and aggregate pubkey derived from the participation info in `aggregation_bits` for the subcommittee specified by the `contribution.subcommittee_index`. - -#### Sync committee subnets - -Sync committee subnets are used to propagate unaggregated sync committee messages to subsections of the network. - -##### `sync_committee_{subnet_id}` - -The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync committee messages to the subnet `subnet_id` to be aggregated before being gossiped to the global `sync_committee_contribution_and_proof` topic. - -The following validations MUST pass before forwarding the `sync_committee_message` on the network: - -- _[IGNORE]_ The message's slot is for the current slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. `sync_committee_message.slot == current_slot`. -- _[REJECT]_ The `subnet_id` is valid for the given validator, i.e. `subnet_id in compute_subnets_for_sync_committee(state, sync_committee_message.validator_index)`. - Note this validation implies the validator is part of the broader current sync committee along with the correct subcommittee. -- _[IGNORE]_ There has been no other valid sync committee message for the declared `slot` for the validator referenced by `sync_committee_message.validator_index` - (this requires maintaining a cache of size `SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT` for each subnet that can be flushed after each slot). - Note this validation is _per topic_ so that for a given `slot`, multiple messages could be forwarded with the same `validator_index` as long as the `subnet_id`s are distinct. -- _[REJECT]_ The `signature` is valid for the message `beacon_block_root` for the validator referenced by `validator_index`. +- _[IGNORE]_ The contribution's slot is for the current slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. + `contribution.slot == current_slot`. +- _[REJECT]_ The subcommittee index is in the allowed range, i.e. + `contribution.subcommittee_index < SYNC_COMMITTEE_SUBNET_COUNT`. +- _[REJECT]_ The contribution has participants -- that is, + `any(contribution.aggregation_bits)`. +- _[REJECT]_ `contribution_and_proof.selection_proof` selects the validator as + an aggregator for the slot -- i.e. + `is_sync_committee_aggregator(contribution_and_proof.selection_proof)` returns + `True`. +- _[REJECT]_ The aggregator's validator index is in the declared subcommittee of + the current sync committee -- i.e. + `state.validators[contribution_and_proof.aggregator_index].pubkey in get_sync_subcommittee_pubkeys(state, contribution.subcommittee_index)`. +- _[IGNORE]_ A valid sync committee contribution with equal `slot`, + `beacon_block_root` and `subcommittee_index` whose `aggregation_bits` is + non-strict superset has _not_ already been seen. +- _[IGNORE]_ The sync committee contribution is the first valid contribution + received for the aggregator with index + `contribution_and_proof.aggregator_index` for the slot `contribution.slot` and + subcommittee index `contribution.subcommittee_index` (this requires + maintaining a cache of size `SYNC_COMMITTEE_SIZE` for this topic that can be + flushed after each slot). +- _[REJECT]_ The `contribution_and_proof.selection_proof` is a valid signature + of the `SyncAggregatorSelectionData` derived from the `contribution` by the + validator with index `contribution_and_proof.aggregator_index`. +- _[REJECT]_ The aggregator signature, + `signed_contribution_and_proof.signature`, is valid. +- _[REJECT]_ The aggregate signature is valid for the message + `beacon_block_root` and aggregate pubkey derived from the participation info + in `aggregation_bits` for the subcommittee specified by the + `contribution.subcommittee_index`. + +##### Sync committee subnets + +Sync committee subnets are used to propagate unaggregated sync committee +messages to subsections of the network. + +###### `sync_committee_{subnet_id}` + +The `sync_committee_{subnet_id}` topics are used to propagate unaggregated sync +committee messages to the subnet `subnet_id` to be aggregated before being +gossiped to the global `sync_committee_contribution_and_proof` topic. + +The following validations MUST pass before forwarding the +`sync_committee_message` on the network: + +- _[IGNORE]_ The message's slot is for the current slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance), i.e. + `sync_committee_message.slot == current_slot`. +- _[REJECT]_ The `subnet_id` is valid for the given validator, i.e. + `subnet_id in compute_subnets_for_sync_committee(state, sync_committee_message.validator_index)`. + Note this validation implies the validator is part of the broader current sync + committee along with the correct subcommittee. +- _[IGNORE]_ There has been no other valid sync committee message for the + declared `slot` for the validator referenced by + `sync_committee_message.validator_index` (this requires maintaining a cache of + size `SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT` for each subnet that + can be flushed after each slot). Note this validation is _per topic_ so that + for a given `slot`, multiple messages could be forwarded with the same + `validator_index` as long as the `subnet_id`s are distinct. +- _[REJECT]_ The `signature` is valid for the message `beacon_block_root` for + the validator referenced by `validator_index`. + +##### Sync committees and aggregation + +The aggregation scheme closely follows the design of the attestation aggregation +scheme. Sync committee messages are broadcast into "subnets" defined by a topic. +The number of subnets is defined by `SYNC_COMMITTEE_SUBNET_COUNT` in the +[Altair validator guide](./validator.md#constants). Sync committee members are +divided into "subcommittees" which are then assigned to a subnet for the +duration of tenure in the sync committee. Individual validators can be +duplicated in the broader sync committee such that they are included multiple +times in a given subcommittee or across multiple subcommittees. + +Unaggregated messages (along with metadata) are sent as `SyncCommitteeMessage`s +on the `sync_committee_{subnet_id}` topics. + +Aggregated sync committee messages are packaged into (signed) +`SyncCommitteeContribution` along with proofs and gossiped to the +`sync_committee_contribution_and_proof` topic. + +#### Transitioning the gossip + +With any fork, the fork version, and thus the `ForkDigestValue`, change. Message +types are unique per topic, and so for a smooth transition a node must +temporarily subscribe to both the old and new topics. + +The topics that are not removed in a fork are updated with a new +`ForkDigestValue`. In advance of the fork, a node SHOULD subscribe to the +post-fork variants of the topics. + +Subscriptions are expected to be well-received, all updated nodes should +subscribe as well. Topic-meshes can be grafted quickly as the nodes are already +connected and exchanging gossip control messages. + +Messages SHOULD NOT be re-broadcast from one fork to the other. A node's +behavior before the fork and after the fork are as follows: -#### Sync committees and aggregation - -The aggregation scheme closely follows the design of the attestation aggregation scheme. -Sync committee messages are broadcast into "subnets" defined by a topic. -The number of subnets is defined by `SYNC_COMMITTEE_SUBNET_COUNT` in the [Altair validator guide](./validator.md#constants). -Sync committee members are divided into "subcommittees" which are then assigned to a subnet for the duration of tenure in the sync committee. -Individual validators can be duplicated in the broader sync committee such that they are included multiple times in a given subcommittee or across multiple subcommittees. - -Unaggregated messages (along with metadata) are sent as `SyncCommitteeMessage`s on the `sync_committee_{subnet_id}` topics. - -Aggregated sync committee messages are packaged into (signed) `SyncCommitteeContribution` along with proofs and gossiped to the `sync_committee_contribution_and_proof` topic. - -### Transitioning the gossip - -With any fork, the fork version, and thus the `ForkDigestValue`, change. -Message types are unique per topic, and so for a smooth transition a node must temporarily subscribe to both the old and new topics. - -The topics that are not removed in a fork are updated with a new `ForkDigestValue`. In advance of the fork, a node SHOULD subscribe to the post-fork variants of the topics. - -Subscriptions are expected to be well-received, all updated nodes should subscribe as well. -Topic-meshes can be grafted quickly as the nodes are already connected and exchanging gossip control messages. - -Messages SHOULD NOT be re-broadcast from one fork to the other. -A node's behavior before the fork and after the fork are as follows: Pre-fork: -- Peers who propagate messages on the post-fork topics MAY be scored negatively proportionally to time till fork, - to account for clock discrepancy. -- Messages can be IGNORED on the post-fork topics, with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` margin. + +- Peers who propagate messages on the post-fork topics MAY be scored negatively + proportionally to time till fork, to account for clock discrepancy. +- Messages can be IGNORED on the post-fork topics, with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` margin. Post-fork: -- Peers who propagate messages on the pre-fork topics MUST NOT be scored negatively. Lagging IWANT may force them to. -- Messages on pre and post-fork variants of topics share application-level caches. - E.g. an attestation on the both the old and new topic is ignored like any duplicate. -- Two epochs after the fork, pre-fork topics SHOULD be unsubscribed from. This is well after the configured `seen_ttl`. -## The Req/Resp domain +- Peers who propagate messages on the pre-fork topics MUST NOT be scored + negatively. Lagging IWANT may force them to. +- Messages on pre and post-fork variants of topics share application-level + caches. E.g. an attestation on the both the old and new topic is ignored like + any duplicate. +- Two epochs after the fork, pre-fork topics SHOULD be unsubscribed from. This + is well after the configured `seen_ttl`. -### Req-Resp interaction +### The Req/Resp domain -An additional `` field is introduced to the `response_chunk` as defined in the Phase 0 document: +#### Req-Resp interaction + +An additional `` field is introduced to the `response_chunk` as +defined in the Phase 0 document: ``` response_chunk ::= | | | ``` -All Phase 0 methods are compatible: `` is empty by default. -On a non-zero `` with `ErrorMessage` payload, the `` is also empty. +All Phase 0 methods are compatible: `` is empty by default. On a +non-zero `` with `ErrorMessage` payload, the `` is also +empty. In Altair and later forks, `` functions as a short meta-data, defined per req-resp method, and can parametrize the payload decoder. -#### `ForkDigest`-context +##### `ForkDigest`-context -Starting with Altair, and in future forks, SSZ type definitions may change. -For this common case, we define the `ForkDigest`-context: +Starting with Altair, and in future forks, SSZ type definitions may change. For +this common case, we define the `ForkDigest`-context: -A fixed-width 4 byte ``, set to the `ForkDigest` matching the chunk: - `compute_fork_digest(fork_version, genesis_validators_root)`. +A fixed-width 4 byte ``, set to the `ForkDigest` matching the +chunk: `compute_fork_digest(fork_version, genesis_validators_root)`. -### Messages +#### Messages -#### BeaconBlocksByRange v2 +##### BeaconBlocksByRange v2 **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` -Request and Response remain unchanged. A `ForkDigest`-context is used to select the fork namespace of the Response type. +Request and Response remain unchanged. A `ForkDigest`-context 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 | -| ------------------------ | -------------------------- | -| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | -| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `fork_version` | Chunk SSZ type | +| ---------------------- | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -#### BeaconBlocksByRoot v2 +##### BeaconBlocksByRoot v2 **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` -Request and Response remain unchanged. A `ForkDigest`-context is used to select the fork namespace of the Response type. +Request and Response remain unchanged. A `ForkDigest`-context is used to select +the fork namespace of the Response type. Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: -[1]: # (eth2spec: skip) + -| `fork_version` | Chunk SSZ type | -| ------------------------ | -------------------------- | -| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | -| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `fork_version` | Chunk SSZ type | +| ---------------------- | -------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -#### GetMetaData v2 +##### GetMetaData v2 **Protocol ID:** `/eth2/beacon_chain/req/metadata/2/` @@ -278,24 +358,35 @@ Requests the MetaData of a peer, using the new `MetaData` definition given above that is extended from phase 0 in Altair. Other conditions for the `GetMetaData` protocol are unchanged from the phase 0 p2p networking document. -### Transitioning from v1 to v2 +#### Transitioning from v1 to v2 + +In advance of the fork, implementations can opt in to both run the v1 and v2 for +a smooth transition. This is non-breaking, and is recommended as soon as the +fork specification is stable. + +The v1 variants will be deprecated, and implementations should use v2 when +available (as negotiated with peers via LibP2P multistream-select). -In advance of the fork, implementations can opt in to both run the v1 and v2 for a smooth transition. -This is non-breaking, and is recommended as soon as the fork specification is stable. +The v1 method MAY be unregistered at the fork boundary. In the event of a +request on v1 for an Altair specific payload, the responder MUST return the +**InvalidRequest** response code. -The v1 variants will be deprecated, and implementations should use v2 when available -(as negotiated with peers via LibP2P multistream-select). +### The discovery domain: discv5 -The v1 method MAY be unregistered at the fork boundary. -In the event of a request on v1 for an Altair specific payload, -the responder MUST return the **InvalidRequest** response code. +#### ENR structure -## The discovery domain: discv5 +##### Sync committee bitfield -The `attnets` key of the ENR is used as defined in the Phase 0 document. +An additional bitfield is added to the ENR under the key `syncnets` to +facilitate sync committee subnet discovery. The length of this bitfield is +`SYNC_COMMITTEE_SUBNET_COUNT` where each bit corresponds to a distinct +`subnet_id` for a specific sync committee subnet. The `i`th bit is set in this +bitfield if the validator is currently subscribed to the `sync_committee_{i}` +topic. -An additional bitfield is added to the ENR under the key `syncnets` to facilitate sync committee subnet discovery. -The length of this bitfield is `SYNC_COMMITTEE_SUBNET_COUNT` where each bit corresponds to a distinct `subnet_id` for a specific sync committee subnet. -The `i`th bit is set in this bitfield if the validator is currently subscribed to the `sync_committee_{i}` topic. +| Key | Value | +| :--------- | :------------------------------------------- | +| `syncnets` | SSZ `Bitvector[SYNC_COMMITTEE_SUBNET_COUNT]` | -See the [validator document](./validator.md#sync-committee-subnet-stability) for further details on how the new bits are used. +See the [validator document](./validator.md#sync-committee-subnet-stability) for +further details on how the new bits are used. diff --git a/specs/altair/sync-protocol.md b/specs/altair/sync-protocol.md deleted file mode 100644 index 24c35f8912..0000000000 --- a/specs/altair/sync-protocol.md +++ /dev/null @@ -1,195 +0,0 @@ -# Altair -- Minimal Light Client - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Constants](#constants) -- [Preset](#preset) - - [Misc](#misc) -- [Containers](#containers) - - [`LightClientSnapshot`](#lightclientsnapshot) - - [`LightClientUpdate`](#lightclientupdate) - - [`LightClientStore`](#lightclientstore) -- [Helper functions](#helper-functions) - - [`get_subtree_index`](#get_subtree_index) -- [Light client state updates](#light-client-state-updates) - - [`validate_light_client_update`](#validate_light_client_update) - - [`apply_light_client_update`](#apply_light_client_update) - - [`process_light_client_update`](#process_light_client_update) - - - - -## Introduction - -The beacon chain is designed to be light client friendly for constrained environments to -access Ethereum with reasonable safety and liveness. -Such environments include resource-constrained devices (e.g. phones for trust-minimised wallets) -and metered VMs (e.g. blockchain VMs for cross-chain bridges). - -This document suggests a minimal light client design for the beacon chain that -uses sync committees introduced in [this beacon chain extension](./beacon-chain.md). - -## Constants - -| Name | Value | -| - | - | -| `FINALIZED_ROOT_INDEX` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` | -| `NEXT_SYNC_COMMITTEE_INDEX` | `get_generalized_index(BeaconState, 'next_sync_committee')` | - -## Preset - -### Misc - -| Name | Value | -| - | - | -| `MIN_SYNC_COMMITTEE_PARTICIPANTS` | `1` | - -## Containers - -### `LightClientSnapshot` - -```python -class LightClientSnapshot(Container): - # Beacon block header - header: BeaconBlockHeader - # Sync committees corresponding to the header - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee -``` - -### `LightClientUpdate` - -```python -class LightClientUpdate(Container): - # Update beacon block header - header: BeaconBlockHeader - # Next sync committee corresponding to the header - next_sync_committee: SyncCommittee - next_sync_committee_branch: Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)] - # Finality proof for the update header - finality_header: BeaconBlockHeader - finality_branch: Vector[Bytes32, floorlog2(FINALIZED_ROOT_INDEX)] - # Sync committee aggregate signature - sync_committee_bits: Bitvector[SYNC_COMMITTEE_SIZE] - sync_committee_signature: BLSSignature - # Fork version for the aggregate signature - fork_version: Version -``` - -### `LightClientStore` - -```python -@dataclass -class LightClientStore(object): - snapshot: LightClientSnapshot - valid_updates: Set[LightClientUpdate] -``` - -## Helper functions - -### `get_subtree_index` - -```python -def get_subtree_index(generalized_index: GeneralizedIndex) -> uint64: - return uint64(generalized_index % 2**(floorlog2(generalized_index))) -``` - -## Light client state updates - -A light client maintains its state in a `store` object of type `LightClientStore` and receives `update` objects of type `LightClientUpdate`. Every `update` triggers `process_light_client_update(store, update, current_slot)` where `current_slot` is the current slot based on some local clock. - -#### `validate_light_client_update` - -```python -def validate_light_client_update(snapshot: LightClientSnapshot, - update: LightClientUpdate, - genesis_validators_root: Root) -> None: - # Verify update slot is larger than snapshot slot - assert update.header.slot > snapshot.header.slot - - # Verify update does not skip a sync committee period - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert update_period in (snapshot_period, snapshot_period + 1) - - # Verify update header root is the finalized root of the finality header, if specified - if update.finality_header == BeaconBlockHeader(): - signed_header = update.header - assert update.finality_branch == [Bytes32() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))] - else: - signed_header = update.finality_header - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.header), - branch=update.finality_branch, - depth=floorlog2(FINALIZED_ROOT_INDEX), - index=get_subtree_index(FINALIZED_ROOT_INDEX), - root=update.finality_header.state_root, - ) - - # Verify update next sync committee if the update period incremented - if update_period == snapshot_period: - sync_committee = snapshot.current_sync_committee - assert update.next_sync_committee_branch == [Bytes32() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))] - else: - sync_committee = snapshot.next_sync_committee - assert is_valid_merkle_branch( - leaf=hash_tree_root(update.next_sync_committee), - branch=update.next_sync_committee_branch, - depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX), - index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX), - root=update.header.state_root, - ) - - # Verify sync committee has sufficient participants - assert sum(update.sync_committee_bits) >= MIN_SYNC_COMMITTEE_PARTICIPANTS - - # Verify sync committee aggregate signature - participant_pubkeys = [pubkey for (bit, pubkey) in zip(update.sync_committee_bits, sync_committee.pubkeys) if bit] - domain = compute_domain(DOMAIN_SYNC_COMMITTEE, update.fork_version, genesis_validators_root) - signing_root = compute_signing_root(signed_header, domain) - assert bls.FastAggregateVerify(participant_pubkeys, signing_root, update.sync_committee_signature) -``` - -#### `apply_light_client_update` - -```python -def apply_light_client_update(snapshot: LightClientSnapshot, update: LightClientUpdate) -> None: - snapshot_period = compute_epoch_at_slot(snapshot.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = compute_epoch_at_slot(update.header.slot) // EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if update_period == snapshot_period + 1: - snapshot.current_sync_committee = snapshot.next_sync_committee - snapshot.next_sync_committee = update.next_sync_committee - snapshot.header = update.header -``` - -#### `process_light_client_update` - -```python -def process_light_client_update(store: LightClientStore, update: LightClientUpdate, current_slot: Slot, - genesis_validators_root: Root) -> None: - validate_light_client_update(store.snapshot, update, genesis_validators_root) - store.valid_updates.add(update) - - update_timeout = SLOTS_PER_EPOCH * EPOCHS_PER_SYNC_COMMITTEE_PERIOD - if ( - sum(update.sync_committee_bits) * 3 >= len(update.sync_committee_bits) * 2 - and update.finality_header != BeaconBlockHeader() - ): - # Apply update if (1) 2/3 quorum is reached and (2) we have a finality proof. - # Note that (2) means that the current light client design needs finality. - # It may be changed to re-organizable light client design. See the on-going issue consensus-specs#2182. - apply_light_client_update(store.snapshot, update) - store.valid_updates = set() - elif current_slot > store.snapshot.header.slot + update_timeout: - # Forced best update when the update timeout has elapsed - apply_light_client_update(store.snapshot, - max(store.valid_updates, key=lambda update: sum(update.sync_committee_bits))) - store.valid_updates = set() -``` diff --git a/specs/altair/validator.md b/specs/altair/validator.md index 461c9a70db..0520abca27 100644 --- a/specs/altair/validator.md +++ b/specs/altair/validator.md @@ -1,16 +1,13 @@ # Altair -- Honest Validator -This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum proof-of-stake protocol. +This is an accompanying document to +[Altair -- The Beacon Chain](./beacon-chain.md), which describes the expected +actions of a "validator" participating in the Ethereum proof-of-stake protocol. -## Table of contents - - - - + - [Introduction](#introduction) - [Prerequisites](#prerequisites) -- [Warning](#warning) - [Constants](#constants) - [Misc](#misc) - [Containers](#containers) @@ -44,37 +41,41 @@ This is an accompanying document to [Altair -- The Beacon Chain](./beacon-chain. - [Broadcast sync committee contribution](#broadcast-sync-committee-contribution) - [Sync committee subnet stability](#sync-committee-subnet-stability) - - + ## Introduction -This document represents the expected behavior of an "honest validator" with respect to the Altair upgrade of the Ethereum proof-of-stake protocol. -It builds on the [previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) of the Ethereum proof-of-stake protocol. -This previous document is referred to below as the "Phase 0 document". - -Altair introduces a new type of committee: the sync committee. Sync committees are responsible for signing each block of the canonical chain and there exists an efficient algorithm for light clients to sync the chain using the output of the sync committees. -See the [sync protocol](./sync-protocol.md) for further details on the light client sync. -Under this network upgrade, validators track their participation in this new committee type and produce the relevant signatures as required. -Block proposers incorporate the (aggregated) sync committee signatures into each block they produce. +This document represents the expected behavior of an "honest validator" with +respect to the Altair upgrade of the Ethereum proof-of-stake protocol. It builds +on the +[previous document for the behavior of an "honest validator" from Phase 0](../phase0/validator.md) +of the Ethereum proof-of-stake protocol. This previous document is referred to +below as the "Phase 0 document". + +Altair introduces a new type of committee: the sync committee. Sync committees +are responsible for signing each block of the canonical chain and there exists +an efficient algorithm for light clients to sync the chain using the output of +the sync committees. See the [sync protocol](./light-client/sync-protocol.md) +for further details on the light client sync. Under this network upgrade, +validators track their participation in this new committee type and produce the +relevant signatures as required. Block proposers incorporate the (aggregated) +sync committee signatures into each block they produce. ## Prerequisites -All terminology, constants, functions, and protocol mechanics defined in the [Altair -- The Beacon Chain](./beacon-chain.md) doc are requisite for this document and used throughout. -Please see this document before continuing and use as a reference throughout. - -## Warning - -This document is currently illustrative for early Altair testnets and some parts are subject to change, especially pending implementation and profiling of Altair testnets. +All terminology, constants, functions, and protocol mechanics defined in the +[Altair -- The Beacon Chain](./beacon-chain.md) doc are requisite for this +document and used throughout. Please see this document before continuing and use +as a reference throughout. ## Constants ### Misc -| Name | Value | Unit | -| - | - | :-: | -| `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE` | `2**4` (= 16) | validators | -| `SYNC_COMMITTEE_SUBNET_COUNT` | `4` | The number of sync committee subnets used in the gossipsub aggregation protocol. | +| Name | Value | Unit | +| ------------------------------------------ | ------------- | :------------------------------------------------------------------------------: | +| `TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE` | `2**4` (= 16) | validators | +| `SYNC_COMMITTEE_SUBNET_COUNT` | `4` | The number of sync committee subnets used in the gossipsub aggregation protocol. | ## Containers @@ -82,13 +83,9 @@ This document is currently illustrative for early Altair testnets and some parts ```python class SyncCommitteeMessage(Container): - # Slot to which this contribution pertains slot: Slot - # Block root for this signature beacon_block_root: Root - # Index of the validator that produced this signature validator_index: ValidatorIndex - # Signature by the validator over the block root of `slot` signature: BLSSignature ``` @@ -96,16 +93,10 @@ class SyncCommitteeMessage(Container): ```python class SyncCommitteeContribution(Container): - # Slot to which this contribution pertains slot: Slot - # Block root for this contribution beacon_block_root: Root - # The subcommittee this contribution pertains to out of the broader sync committee subcommittee_index: uint64 - # A bit is set if a signature from the validator at the corresponding - # index in the subcommittee is present in the aggregate `signature`. aggregation_bits: Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT] - # Signature by the validator(s) over the block root of `slot` signature: BLSSignature ``` @@ -136,17 +127,26 @@ class SyncAggregatorSelectionData(Container): ## Validator assignments -A validator determines beacon committee assignments and beacon block proposal duties as defined in the Phase 0 document. +A validator determines beacon committee assignments and beacon block proposal +duties as defined in the Phase 0 document. ### Sync Committee -To determine sync committee assignments, a validator can run the following function: `is_assigned_to_sync_committee(state, epoch, validator_index)` where `epoch` is an epoch number within the current or next sync committee period. -This function is a predicate indicating the presence or absence of the validator in the corresponding sync committee for the queried sync committee period. - -*Note*: Being assigned to a sync committee for a given `slot` means that the validator produces and broadcasts signatures for `slot - 1` for inclusion in `slot`. -This means that when assigned to an `epoch` sync committee signatures must be produced and broadcast for slots on range `[compute_start_slot_at_epoch(epoch) - 1, compute_start_slot_at_epoch(epoch) + SLOTS_PER_EPOCH - 1)` -rather than for the range `[compute_start_slot_at_epoch(epoch), compute_start_slot_at_epoch(epoch) + SLOTS_PER_EPOCH)`. -To reduce complexity during the Altair fork, sync committees are not expected to produce signatures for `compute_epoch_at_slot(ALTAIR_FORK_EPOCH) - 1`. +To determine sync committee assignments, a validator can run the following +function: `is_assigned_to_sync_committee(state, epoch, validator_index)` where +`epoch` is an epoch number within the current or next sync committee period. +This function is a predicate indicating the presence or absence of the validator +in the corresponding sync committee for the queried sync committee period. + +*Note*: Being assigned to a sync committee for a given `slot` means that the +validator produces and broadcasts signatures for `slot - 1` for inclusion in +`slot`. This means that when assigned to an `epoch` sync committee signatures +must be produced and broadcast for slots on range +`[compute_start_slot_at_epoch(epoch) - 1, compute_start_slot_at_epoch(epoch) + SLOTS_PER_EPOCH - 1)` +rather than for the range +`[compute_start_slot_at_epoch(epoch), compute_start_slot_at_epoch(epoch) + SLOTS_PER_EPOCH)`. +To reduce complexity during the Altair fork, sync committees are not expected to +produce signatures for `compute_start_slot_at_epoch(ALTAIR_FORK_EPOCH) - 1`. ```python def compute_sync_committee_period(epoch: Epoch) -> uint64: @@ -154,9 +154,9 @@ def compute_sync_committee_period(epoch: Epoch) -> uint64: ``` ```python -def is_assigned_to_sync_committee(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex) -> bool: +def is_assigned_to_sync_committee( + state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex +) -> bool: sync_committee_period = compute_sync_committee_period(epoch) current_epoch = get_current_epoch(state) current_sync_committee_period = compute_sync_committee_period(current_epoch) @@ -172,60 +172,94 @@ def is_assigned_to_sync_committee(state: BeaconState, ### Lookahead -The sync committee shufflings give validators 1 sync committee period of lookahead which amounts to `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs. -At any given `epoch`, the `BeaconState` contains the current `SyncCommittee` and the next `SyncCommittee`. -Once every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs, the next `SyncCommittee` becomes the current `SyncCommittee` and the next committee is computed and stored. - -*Note*: The data required to compute a given committee is not cached in the `BeaconState` after committees are calculated at the period boundaries. -For this reason, *always* get committee assignments via the fields of the `BeaconState` (`current_sync_committee` and `next_sync_committee`) or use the above reference code. - -A validator should plan for future sync committee assignments by noting which sync committee periods they are selected for participation. -Specifically, a validator should: -* Upon (re)syncing the chain and upon sync committee period boundaries, check for assignments in the current and next sync committee periods. -* If the validator is in the current sync committee period, then they perform the responsibilities below for sync committee rewards. -* If the validator is in the next sync committee period, they should wait until the next `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` boundary and then perform the responsibilities throughout that period. +The sync committee shufflings give validators 1 sync committee period of +lookahead which amounts to `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs. At any +given `epoch`, the `BeaconState` contains the current `SyncCommittee` and the +next `SyncCommittee`. Once every `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs, the +next `SyncCommittee` becomes the current `SyncCommittee` and the next committee +is computed and stored. + +*Note*: The data required to compute a given committee is not cached in the +`BeaconState` after committees are calculated at the period boundaries. For this +reason, *always* get committee assignments via the fields of the `BeaconState` +(`current_sync_committee` and `next_sync_committee`) or use the above reference +code. + +A validator should plan for future sync committee assignments by noting which +sync committee periods they are selected for participation. Specifically, a +validator should: + +- Upon (re)syncing the chain and upon sync committee period boundaries, check + for assignments in the current and next sync committee periods. +- If the validator is in the current sync committee period, then they perform + the responsibilities below for sync committee rewards. +- If the validator is in the next sync committee period, they should wait until + the next `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` boundary and then perform the + responsibilities throughout that period. ## Beacon chain responsibilities A validator maintains the responsibilities given in the Phase 0 document. -Block proposals are modified to incorporate the sync committee signatures as detailed below. +Block proposals are modified to incorporate the sync committee signatures as +detailed below. -When assigned to a sync committee, validators have a new responsibility to sign and broadcast beacon block roots during each slot of the sync committee period. -These signatures are aggregated and routed to the proposer over gossip for inclusion into a beacon block. -Assignments to a particular sync committee are infrequent at normal validator counts; however, an action every slot is required when in the current active sync committee. +When assigned to a sync committee, validators have a new responsibility to sign +and broadcast beacon block roots during each slot of the sync committee period. +These signatures are aggregated and routed to the proposer over gossip for +inclusion into a beacon block. Assignments to a particular sync committee are +infrequent at normal validator counts; however, an action every slot is required +when in the current active sync committee. ### Block proposal -Refer to the phase 0 document for the majority of the [block proposal responsibility](../phase0/validator.md#block-proposal). -The validator should follow those instructions to prepare a `SignedBeaconBlock` for inclusion into the chain. All changes are additive to phase 0 and noted below. +Refer to the phase 0 document for the majority of the +[block proposal responsibility](../phase0/validator.md#block-proposal). The +validator should follow those instructions to prepare a `SignedBeaconBlock` for +inclusion into the chain. All changes are additive to phase 0 and noted below. #### Preparing a `BeaconBlock` -No change to [Preparing for a `BeaconBlock`](../phase0/validator.md#preparing-for-a-beaconblock). +No change to +[Preparing for a `BeaconBlock`](../phase0/validator.md#preparing-for-a-beaconblock). #### Constructing the `BeaconBlockBody` -Each section of [Constructing the `BeaconBlockBody`](../phase0/validator.md#constructing-the-beaconblockbody) should be followed. -After constructing the `BeaconBlockBody` as per that section, the proposer has an additional task to include the sync committee signatures: +Each section of +[Constructing the `BeaconBlockBody`](../phase0/validator.md#constructing-the-beaconblockbody) +should be followed. After constructing the `BeaconBlockBody` as per that +section, the proposer has an additional task to include the sync committee +signatures: ##### Sync committee -The proposer receives a number of `SyncCommitteeContribution`s (wrapped in `SignedContributionAndProof`s on the wire) from validators in the sync committee who are selected to partially aggregate signatures from independent subcommittees formed by breaking the full sync committee into `SYNC_COMMITTEE_SUBNET_COUNT` pieces (see below for details). - -The proposer collects the contributions that match their local view of the chain (i.e. `contribution.beacon_block_root == block.parent_root`) for further aggregation when preparing a block. -Of these contributions, proposers should select the best contribution seen across all aggregators for each subnet/subcommittee. -A contribution with more valid signatures is better than a contribution with fewer signatures. - -Recall `block.body.sync_aggregate.sync_committee_bits` is a `Bitvector` where the `i`th bit is `True` if the corresponding validator in the sync committee has produced a valid signature, -and that `block.body.sync_aggregate.sync_committee_signature` is the aggregate BLS signature combining all of the valid signatures. - -Given a collection of the best seen `contributions` (with no repeating `subcommittee_index` values) and the `BeaconBlock` under construction, -the proposer processes them as follows: +The proposer receives a number of `SyncCommitteeContribution`s (wrapped in +`SignedContributionAndProof`s on the wire) from validators in the sync committee +who are selected to partially aggregate signatures from independent +subcommittees formed by breaking the full sync committee into +`SYNC_COMMITTEE_SUBNET_COUNT` pieces (see below for details). + +The proposer collects the contributions that match their local view of the chain +(i.e. `contribution.beacon_block_root == block.parent_root`) for further +aggregation when preparing a block. Of these contributions, proposers should +select the best contribution seen across all aggregators for each +subnet/subcommittee. A contribution with more valid signatures is better than a +contribution with fewer signatures. + +Recall `block.body.sync_aggregate.sync_committee_bits` is a `Bitvector` where +the `i`th bit is `True` if the corresponding validator in the sync committee has +produced a valid signature, and that +`block.body.sync_aggregate.sync_committee_signature` is the aggregate BLS +signature combining all of the valid signatures. + +Given a collection of the best seen `contributions` (with no repeating +`subcommittee_index` values) and the `BeaconBlock` under construction, the +proposer processes them as follows: ```python -def process_sync_committee_contributions(block: BeaconBlock, - contributions: Set[SyncCommitteeContribution]) -> None: +def process_sync_committee_contributions( + block: BeaconBlock, contributions: Set[SyncCommitteeContribution] +) -> None: sync_aggregate = SyncAggregate() signatures = [] sync_subcommittee_size = SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT @@ -243,40 +277,65 @@ def process_sync_committee_contributions(block: BeaconBlock, block.body.sync_aggregate = sync_aggregate ``` -*Note*: The resulting block must pass the validations for the `SyncAggregate` defined in `process_sync_aggregate` defined in the [state transition document](./beacon-chain.md#sync-aggregate-processing). -In particular, this means `SyncCommitteeContribution`s received from gossip must have a `beacon_block_root` that matches the proposer's local view of the chain. +*Note*: The resulting block must pass the validations for the `SyncAggregate` +defined in `process_sync_aggregate` defined in the +[state transition document](./beacon-chain.md#sync-aggregate-processing). In +particular, this means `SyncCommitteeContribution`s received from gossip must +have a `beacon_block_root` that matches the proposer's local view of the chain. #### Packaging into a `SignedBeaconBlock` -No change to [Packaging into a `SignedBeaconBlock`](../phase0/validator.md#packaging-into-a-signedbeaconblock). +No change to +[Packaging into a `SignedBeaconBlock`](../phase0/validator.md#packaging-into-a-signedbeaconblock). ### Attesting and attestation aggregation -Refer to the phase 0 document for the [attesting](../phase0/validator.md#attesting) and [attestation aggregation](../phase0/validator.md#attestation-aggregation) responsibilities. -There is no change compared to the phase 0 document. +Refer to the phase 0 document for the +[attesting](../phase0/validator.md#attesting) and +[attestation aggregation](../phase0/validator.md#attestation-aggregation) +responsibilities. There is no change compared to the phase 0 document. ### Sync committees -Sync committee members employ an aggregation scheme to reduce load on the global proposer channel that is monitored by all potential proposers to be able to include the full output of the sync committee every slot. -Sync committee members produce individual signatures on subnets (similar to the attestation subnets) via `SyncCommitteeMessage`s which are then collected by aggregators sampled from the sync subcommittees to produce a `SyncCommitteeContribution` which is gossiped to proposers. -This process occurs each slot. +Sync committee members employ an aggregation scheme to reduce load on the global +proposer channel that is monitored by all potential proposers to be able to +include the full output of the sync committee every slot. Sync committee members +produce individual signatures on subnets (similar to the attestation subnets) +via `SyncCommitteeMessage`s which are then collected by aggregators sampled from +the sync subcommittees to produce a `SyncCommitteeContribution` which is +gossiped to proposers. This process occurs each slot. #### Sync committee messages ##### Prepare sync committee message -If a validator is in the current sync committee (i.e. `is_assigned_to_sync_committee()` above returns `True`), then for every `slot` in the current sync committee period, the validator should prepare a `SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic in `get_sync_committee_message` as soon as they have determined the head block of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` is prepared and broadcast in `slot-1 ` instead of `slot`. - -This logic is triggered upon the same conditions as when producing an attestation. -Meaning, a sync committee member should produce and broadcast a `SyncCommitteeMessage` either when (a) the validator has received a valid block from the expected block proposer for the current `slot` or (b) one-third of the slot has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of the slot) -- whichever comes first. - -`get_sync_committee_message(state, block_root, validator_index, privkey)` assumes the parameter `state` is the head state corresponding to processing the block up to the current slot as determined by the fork choice (including any empty slots up to the current slot processed with `process_slots` on top of the latest block), `block_root` is the root of the head block, `validator_index` is the index of the validator in the registry `state.validators` controlled by `privkey`, and `privkey` is the BLS private key for the validator. +If a validator is in the current sync committee (i.e. +`is_assigned_to_sync_committee()` above returns `True`), then for every `slot` +in the current sync committee period, the validator should prepare a +`SyncCommitteeMessage` for the previous slot (`slot - 1`) according to the logic +in `get_sync_committee_message` as soon as they have determined the head block +of `slot - 1`. This means that when assigned to `slot` a `SyncCommitteeMessage` +is prepared and broadcast in `slot-1 ` instead of `slot`. + +This logic is triggered upon the same conditions as when producing an +attestation. Meaning, a sync committee member should produce and broadcast a +`SyncCommitteeMessage` either when (a) the validator has received a valid block +from the expected block proposer for the current `slot` or (b) one-third of the +slot has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the +start of the slot) -- whichever comes first. + +`get_sync_committee_message(state, block_root, validator_index, privkey)` +assumes the parameter `state` is the head state corresponding to processing the +block up to the current slot as determined by the fork choice (including any +empty slots up to the current slot processed with `process_slots` on top of the +latest block), `block_root` is the root of the head block, `validator_index` is +the index of the validator in the registry `state.validators` controlled by +`privkey`, and `privkey` is the BLS private key for the validator. ```python -def get_sync_committee_message(state: BeaconState, - block_root: Root, - validator_index: ValidatorIndex, - privkey: int) -> SyncCommitteeMessage: +def get_sync_committee_message( + state: BeaconState, block_root: Root, validator_index: ValidatorIndex, privkey: int +) -> SyncCommitteeMessage: epoch = get_current_epoch(state) domain = get_domain(state, DOMAIN_SYNC_COMMITTEE, epoch) signing_root = compute_signing_root(block_root, domain) @@ -292,47 +351,69 @@ def get_sync_committee_message(state: BeaconState, ##### Broadcast sync committee message -The validator broadcasts the assembled signature to the assigned subnet, the `sync_committee_{subnet_id}` pubsub topic. +The validator broadcasts the assembled signature to the assigned subnet, the +`sync_committee_{subnet_id}` pubsub topic. -The `subnet_id` is derived from the position in the sync committee such that the sync committee is divided into "subcommittees". -`subnet_id` can be computed via `compute_subnets_for_sync_committee(state, validator_index)` where `state` is a `BeaconState` during the matching sync committee period. +The `subnet_id` is derived from the position in the sync committee such that the +sync committee is divided into "subcommittees". `subnet_id` can be computed via +`compute_subnets_for_sync_committee(state, validator_index)` where `state` is a +`BeaconState` during the matching sync committee period. -*Note*: This function returns multiple deduplicated subnets if a given validator index is included multiple times in a given sync committee across multiple subcommittees. +*Note*: This function returns multiple deduplicated subnets if a given validator +index is included multiple times in a given sync committee across multiple +subcommittees. ```python -def compute_subnets_for_sync_committee(state: BeaconState, validator_index: ValidatorIndex) -> Set[uint64]: +def compute_subnets_for_sync_committee( + state: BeaconState, validator_index: ValidatorIndex +) -> Set[SubnetID]: next_slot_epoch = compute_epoch_at_slot(Slot(state.slot + 1)) - if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period(next_slot_epoch): + if compute_sync_committee_period(get_current_epoch(state)) == compute_sync_committee_period( + next_slot_epoch + ): sync_committee = state.current_sync_committee else: sync_committee = state.next_sync_committee target_pubkey = state.validators[validator_index].pubkey - sync_committee_indices = [index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey] - return set([ - uint64(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) - for index in sync_committee_indices - ]) + sync_committee_indices = [ + index for index, pubkey in enumerate(sync_committee.pubkeys) if pubkey == target_pubkey + ] + return set( + [ + SubnetID(index // (SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT)) + for index in sync_committee_indices + ] + ) ``` -*Note*: Subnet assignment does not change during the duration of a validator's assignment to a given sync committee. +*Note*: Subnet assignment does not change during the duration of a validator's +assignment to a given sync committee. -*Note*: If a validator has multiple `subnet_id` results from `compute_subnets_for_sync_committee`, the validator should broadcast a copy of the `sync_committee_message` on each of the distinct subnets. +*Note*: If a validator has multiple `subnet_id` results from +`compute_subnets_for_sync_committee`, the validator should broadcast a copy of +the `sync_committee_message` on each of the distinct subnets. #### Sync committee contributions -Each slot, some sync committee members in each subcommittee are selected to aggregate the `SyncCommitteeMessage`s into a `SyncCommitteeContribution` which is broadcast on a global channel for inclusion into the next block. +Each slot, some sync committee members in each subcommittee are selected to +aggregate the `SyncCommitteeMessage`s into a `SyncCommitteeContribution` which +is broadcast on a global channel for inclusion into the next block. ##### Aggregation selection -A validator is selected to aggregate based on the value returned by `is_sync_committee_aggregator()` where `signature` is the BLS signature returned by `get_sync_committee_selection_proof()`. -The signature function takes a `BeaconState` with the relevant sync committees for the queried `slot` (i.e. `state.slot` is within the span covered by the current or next sync committee period), the `subcommittee_index` equal to the `subnet_id`, and the `privkey` is the BLS private key associated with the validator. +A validator is selected to aggregate based on the value returned by +`is_sync_committee_aggregator()` where `signature` is the BLS signature returned +by `get_sync_committee_selection_proof()`. The signature function takes a +`BeaconState` with the relevant sync committees for the queried `slot` (i.e. +`state.slot` is within the span covered by the current or next sync committee +period), the `subcommittee_index` equal to the `subnet_id`, and the `privkey` is +the BLS private key associated with the validator. ```python -def get_sync_committee_selection_proof(state: BeaconState, - slot: Slot, - subcommittee_index: uint64, - privkey: int) -> BLSSignature: +def get_sync_committee_selection_proof( + state: BeaconState, slot: Slot, subcommittee_index: uint64, privkey: int +) -> BLSSignature: domain = get_domain(state, DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, compute_epoch_at_slot(slot)) signing_data = SyncAggregatorSelectionData( slot=slot, @@ -344,60 +425,102 @@ def get_sync_committee_selection_proof(state: BeaconState, ```python def is_sync_committee_aggregator(signature: BLSSignature) -> bool: - modulo = max(1, SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE) + modulo = max( + 1, + SYNC_COMMITTEE_SIZE + // SYNC_COMMITTEE_SUBNET_COUNT + // TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE, + ) return bytes_to_uint64(hash(signature)[0:8]) % modulo == 0 ``` -*NOTE*: The set of aggregators generally changes every slot; however, the assignments can be computed ahead of time as soon as the committee is known. +*Note*: The set of aggregators generally changes every slot; however, the +assignments can be computed ahead of time as soon as the committee is known. ##### Construct sync committee contribution -If a validator is selected to aggregate the `SyncCommitteeMessage`s produced on a subnet during a given `slot`, they construct an aggregated `SyncCommitteeContribution`. +If a validator is selected to aggregate the `SyncCommitteeMessage`s produced on +a subnet during a given `slot`, they construct an aggregated +`SyncCommitteeContribution`. -Collect all of the (valid) `sync_committee_messages: Set[SyncCommitteeMessage]` from the `sync_committee_{subnet_id}` gossip during the selected `slot` with an equivalent `beacon_block_root` to that of the aggregator. If `len(sync_committee_messages) > 0`, the aggregator creates a `contribution: SyncCommitteeContribution` with the following fields: +Collect all of the (valid) `sync_committee_messages: Set[SyncCommitteeMessage]` +from the `sync_committee_{subnet_id}` gossip during the selected `slot` with an +equivalent `beacon_block_root` to that of the aggregator. If +`len(sync_committee_messages) > 0`, the aggregator creates a +`contribution: SyncCommitteeContribution` with the following fields: ###### Slot -Set `contribution.slot = state.slot` where `state` is the `BeaconState` for the slot in question. +Set `contribution.slot = state.slot` where `state` is the `BeaconState` for the +slot in question. ###### Beacon block root -Set `contribution.beacon_block_root = beacon_block_root` from the `beacon_block_root` found in the `sync_committee_messages`. +Set `contribution.beacon_block_root = beacon_block_root` from the +`beacon_block_root` found in the `sync_committee_messages`. ###### Subcommittee index -Set `contribution.subcommittee_index` to the index for the subcommittee index corresponding to the subcommittee assigned to this subnet. This index matches the `subnet_id` used to derive the topic name. +Set `contribution.subcommittee_index` to the index for the subcommittee index +corresponding to the subcommittee assigned to this subnet. This index matches +the `subnet_id` used to derive the topic name. ###### Aggregation bits -Let `contribution.aggregation_bits` be a `Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT]`, where the `index`th bit is set in the `Bitvector` for each corresponding validator included in this aggregate from the corresponding subcommittee. -An aggregator finds the index in the sync committee (as determined by a reverse pubkey lookup on `state.current_sync_committee.pubkeys`) for a given validator referenced by `sync_committee_message.validator_index` and maps the sync committee index to an index in the subcommittee (along with the prior `subcommittee_index`). This index within the subcommittee is set in `contribution.aggegration_bits`. +Let `contribution.aggregation_bits` be a +`Bitvector[SYNC_COMMITTEE_SIZE // SYNC_COMMITTEE_SUBNET_COUNT]`, where the +`index`th bit is set in the `Bitvector` for each corresponding validator +included in this aggregate from the corresponding subcommittee. An aggregator +finds the index in the sync committee (as determined by a reverse pubkey lookup +on `state.current_sync_committee.pubkeys`) for a given validator referenced by +`sync_committee_message.validator_index` and maps the sync committee index to an +index in the subcommittee (along with the prior `subcommittee_index`). This +index within the subcommittee is set in `contribution.aggregation_bits`. -For example, if a validator with index `2044` is pseudo-randomly sampled to sync committee index `135`. This sync committee index maps to `subcommittee_index` `1` with position `7` in the `Bitvector` for the contribution. +For example, if a validator with index `2044` is pseudo-randomly sampled to sync +committee index `135`. This sync committee index maps to `subcommittee_index` +`1` with position `7` in the `Bitvector` for the contribution. -*Note*: A validator **could be included multiple times** in a given subcommittee such that multiple bits are set for a single `SyncCommitteeMessage`. +*Note*: A validator **could be included multiple times** in a given subcommittee +such that multiple bits are set for a single `SyncCommitteeMessage`. ###### Signature -Set `contribution.signature = aggregate_signature` where `aggregate_signature` is obtained by assembling the appropriate collection of `BLSSignature`s from the set of `sync_committee_messages` and using the `bls.Aggregate()` function to produce an aggregate `BLSSignature`. +Set `contribution.signature = aggregate_signature` where `aggregate_signature` +is obtained by assembling the appropriate collection of `BLSSignature`s from the +set of `sync_committee_messages` and using the `bls.Aggregate()` function to +produce an aggregate `BLSSignature`. -The collection of input signatures should include one signature per validator who had a bit set in the `aggregation_bits` bitfield, with repeated signatures if one validator maps to multiple indices within the subcommittee. +The collection of input signatures should include one signature per validator +who had a bit set in the `aggregation_bits` bitfield, with repeated signatures +if one validator maps to multiple indices within the subcommittee. ##### Broadcast sync committee contribution -If the validator is selected to aggregate (`is_sync_committee_aggregator()`), then they broadcast their best aggregate as a `SignedContributionAndProof` to the global aggregate channel (`sync_committee_contribution_and_proof` topic) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`. +If the validator is selected to aggregate (`is_sync_committee_aggregator()`), +then they broadcast their best aggregate as a `SignedContributionAndProof` to +the global aggregate channel (`sync_committee_contribution_and_proof` topic) +two-thirds of the way through the `slot`-that is, +`SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` seconds after the start of `slot`. -Selection proofs are provided in `ContributionAndProof` to prove to the gossip channel that the validator has been selected as an aggregator. +Selection proofs are provided in `ContributionAndProof` to prove to the gossip +channel that the validator has been selected as an aggregator. -`ContributionAndProof` messages are signed by the aggregator and broadcast inside of `SignedContributionAndProof` objects to prevent a class of DoS attacks and message forgeries. +`ContributionAndProof` messages are signed by the aggregator and broadcast +inside of `SignedContributionAndProof` objects to prevent a class of DoS attacks +and message forgeries. -First, `contribution_and_proof = get_contribution_and_proof(state, validator_index, contribution, privkey)` is constructed. +First, +`contribution_and_proof = get_contribution_and_proof(state, validator_index, contribution, privkey)` +is constructed. ```python -def get_contribution_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - contribution: SyncCommitteeContribution, - privkey: int) -> ContributionAndProof: +def get_contribution_and_proof( + state: BeaconState, + aggregator_index: ValidatorIndex, + contribution: SyncCommitteeContribution, + privkey: int, +) -> ContributionAndProof: selection_proof = get_sync_committee_selection_proof( state, contribution.slot, @@ -411,33 +534,51 @@ def get_contribution_and_proof(state: BeaconState, ) ``` -Then `signed_contribution_and_proof = SignedContributionAndProof(message=contribution_and_proof, signature=signature)` is constructed and broadcast. Where `signature` is obtained from: +Then +`signed_contribution_and_proof = SignedContributionAndProof(message=contribution_and_proof, signature=signature)` +is constructed and broadcast. Where `signature` is obtained from: ```python -def get_contribution_and_proof_signature(state: BeaconState, - contribution_and_proof: ContributionAndProof, - privkey: int) -> BLSSignature: +def get_contribution_and_proof_signature( + state: BeaconState, contribution_and_proof: ContributionAndProof, privkey: int +) -> BLSSignature: contribution = contribution_and_proof.contribution - domain = get_domain(state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot)) + domain = get_domain( + state, DOMAIN_CONTRIBUTION_AND_PROOF, compute_epoch_at_slot(contribution.slot) + ) signing_root = compute_signing_root(contribution_and_proof, domain) return bls.Sign(privkey, signing_root) ``` ## Sync committee subnet stability -The sync committee subnets need special care to ensure stability given the relatively low number of validators involved in the sync committee at any particular time. -To provide this stability, a validator must do the following: - -* Maintain advertisement of the subnet the validator in the sync committee is assigned to in their node's ENR as soon as they have joined the subnet. -Subnet assignments are known `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs in advance and can be computed with `compute_subnets_for_sync_committee` defined above. -ENR advertisement is indicated by setting the appropriate bit(s) of the bitfield found under the `syncnets` key in the ENR corresponding to the derived `subnet_id`(s). -Any bits modified for the sync committee responsibilities are unset in the ENR once the node no longer has any validators in the subcommittee. - - *Note*: The first sync committee from phase 0 to the Altair fork will not be known until the fork happens, which implies subnet assignments are not known until then. -Early sync committee members should listen for topic subscriptions from peers and employ discovery via the ENR advertisements near the fork boundary to form initial subnets. -Some early sync committee rewards may be missed while the initial subnets form. - -* To join a sync committee subnet, select a random number of epochs before the end of the current sync committee period between 1 and `SYNC_COMMITTEE_SUBNET_COUNT`, inclusive. -Validators should join their member subnet at the beginning of the epoch they have randomly selected. -For example, if the next sync committee period starts at epoch `853,248` and the validator randomly selects an offset of `3`, they should join the subnet at the beginning of epoch `853,245`. -Validators should leverage the lookahead period on sync committee assignments so that they can join the appropriate subnets ahead of their assigned sync committee period. +The sync committee subnets need special care to ensure stability given the +relatively low number of validators involved in the sync committee at any +particular time. To provide this stability, a validator must do the following: + +- Maintain advertisement of the subnet the validator in the sync committee is + assigned to in their node's ENR as soon as they have joined the subnet. Subnet + assignments are known `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` epochs in advance and + can be computed with `compute_subnets_for_sync_committee` defined above. ENR + advertisement is indicated by setting the appropriate bit(s) of the bitfield + found under the `syncnets` key in the ENR corresponding to the derived + `subnet_id`(s). Any bits modified for the sync committee responsibilities are + unset in the ENR once the node no longer has any validators in the + subcommittee. + + *Note*: The first sync committee from phase 0 to the Altair fork will not be + known until the fork happens, which implies subnet assignments are not known + until then. Early sync committee members should listen for topic subscriptions + from peers and employ discovery via the ENR advertisements near the fork + boundary to form initial subnets. Some early sync committee rewards may be + missed while the initial subnets form. + +- To join a sync committee subnet, select a random number of epochs before the + end of the current sync committee period between 1 and + `SYNC_COMMITTEE_SUBNET_COUNT`, inclusive. Validators should join their member + subnet at the beginning of the epoch they have randomly selected. For example, + if the next sync committee period starts at epoch `853,248` and the validator + randomly selects an offset of `3`, they should join the subnet at the + beginning of epoch `853,245`. Validators should leverage the lookahead period + on sync committee assignments so that they can join the appropriate subnets + ahead of their assigned sync committee period. diff --git a/specs/bellatrix/beacon-chain.md b/specs/bellatrix/beacon-chain.md new file mode 100644 index 0000000000..31aa1caf03 --- /dev/null +++ b/specs/bellatrix/beacon-chain.md @@ -0,0 +1,446 @@ +# Bellatrix -- The Beacon Chain + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Preset](#preset) + - [Rewards and penalties](#rewards-and-penalties) + - [Execution](#execution) +- [Configuration](#configuration) + - [Transition settings](#transition-settings) +- [Containers](#containers) + - [Modified containers](#modified-containers) + - [`BeaconBlockBody`](#beaconblockbody) + - [`BeaconState`](#beaconstate) + - [New containers](#new-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) +- [Helper functions](#helper-functions) + - [Predicates](#predicates) + - [`is_merge_transition_complete`](#is_merge_transition_complete) + - [`is_merge_transition_block`](#is_merge_transition_block) + - [`is_execution_enabled`](#is_execution_enabled) + - [Beacon state accessors](#beacon-state-accessors) + - [Modified `get_inactivity_penalty_deltas`](#modified-get_inactivity_penalty_deltas) + - [Beacon state mutators](#beacon-state-mutators) + - [Modified `slash_validator`](#modified-slash_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution engine](#execution-engine) + - [Request data](#request-data) + - [`NewPayloadRequest`](#newpayloadrequest) + - [Engine APIs](#engine-apis) + - [`notify_new_payload`](#notify_new_payload) + - [`is_valid_block_hash`](#is_valid_block_hash) + - [`verify_and_notify_new_payload`](#verify_and_notify_new_payload) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [`process_execution_payload`](#process_execution_payload) + - [Epoch processing](#epoch-processing) + - [Slashings](#slashings) + + + +## Introduction + +This upgrade adds transaction execution to the beacon chain as part of Bellatrix +upgrade. + +Additionally, this upgrade introduces the following minor changes: + +- Penalty parameter updates to their planned maximally punitive values + +## Custom types + +*Note*: The `Transaction` type is a stub which is not final. + +| Name | SSZ equivalent | Description | +| ------------------ | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Transaction` | `ByteList[MAX_BYTES_PER_TRANSACTION]` | either a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) or a legacy transaction | +| `ExecutionAddress` | `Bytes20` | Address of account on the execution layer | + +## Preset + +### Rewards and penalties + +Bellatrix updates a few configuration values to move penalty parameters to their +final, maximum security values. + +*Note*: The spec does *not* override previous configuration values but instead +creates new values and replaces usage throughout. + +| Name | Value | +| -------------------------------------------- | ------------------------------ | +| `INACTIVITY_PENALTY_QUOTIENT_BELLATRIX` | `uint64(2**24)` (= 16,777,216) | +| `MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX` | `uint64(2**5)` (= 32) | +| `PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX` | `uint64(3)` | + +### Execution + +| Name | Value | +| ------------------------------ | --------------------------------- | +| `MAX_BYTES_PER_TRANSACTION` | `uint64(2**30)` (= 1,073,741,824) | +| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**20)` (= 1,048,576) | +| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | +| `MAX_EXTRA_DATA_BYTES` | `2**5` (= 32) | + +## Configuration + +### Transition settings + +| Name | Value | +| -------------------------------------- | ---------------------------------------------------- | +| `TERMINAL_TOTAL_DIFFICULTY` | `58750000000000000000000` (Estimated: Sept 15, 2022) | +| `TERMINAL_BLOCK_HASH` | `Hash32()` | +| `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` | `FAR_FUTURE_EPOCH` | + +## Containers + +### Modified containers + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # [New in Bellatrix] + execution_payload: ExecutionPayload +``` + +#### `BeaconState` + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # [New in Bellatrix] + latest_execution_payload_header: ExecutionPayloadHeader +``` + +### New containers + +#### `ExecutionPayload` + +*Note*: `fee_recipient`, `prev_randao`, and `block_number` correspond to +`beneficiary`, `difficulty`, and `number` in +[the yellow paper](https://ethereum.github.io/yellowpaper/paper.pdf), +respectively. + +```python +class ExecutionPayload(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] +``` + +#### `ExecutionPayloadHeader` + +*Note*: `block_hash` is the hash of the execution block. + +```python +class ExecutionPayloadHeader(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions_root: Root +``` + +## Helper functions + +### Predicates + +#### `is_merge_transition_complete` + +```python +def is_merge_transition_complete(state: BeaconState) -> bool: + return state.latest_execution_payload_header != ExecutionPayloadHeader() +``` + +#### `is_merge_transition_block` + +```python +def is_merge_transition_block(state: BeaconState, body: BeaconBlockBody) -> bool: + return not is_merge_transition_complete(state) and body.execution_payload != ExecutionPayload() +``` + +#### `is_execution_enabled` + +```python +def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: + return is_merge_transition_block(state, body) or is_merge_transition_complete(state) +``` + +### Beacon state accessors + +#### Modified `get_inactivity_penalty_deltas` + +*Note*: The function `get_inactivity_penalty_deltas` is modified to use +`INACTIVITY_PENALTY_QUOTIENT_BELLATRIX`. + +```python +def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: + """ + Return the inactivity penalty deltas by considering timely target participation flags and inactivity scores. + """ + rewards = [Gwei(0) for _ in range(len(state.validators))] + penalties = [Gwei(0) for _ in range(len(state.validators))] + previous_epoch = get_previous_epoch(state) + matching_target_indices = get_unslashed_participating_indices( + state, TIMELY_TARGET_FLAG_INDEX, previous_epoch + ) + for index in get_eligible_validator_indices(state): + if index not in matching_target_indices: + penalty_numerator = ( + state.validators[index].effective_balance * state.inactivity_scores[index] + ) + # [Modified in Bellatrix] + penalty_denominator = INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_BELLATRIX + penalties[index] += Gwei(penalty_numerator // penalty_denominator) + return rewards, penalties +``` + +### Beacon state mutators + +#### Modified `slash_validator` + +*Note*: The function `slash_validator` is modified to use +`MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX`. + +```python +def slash_validator( + state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex = None +) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max( + validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR) + ) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + # [Modified in Bellatrix] + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + whistleblower_reward = Gwei(validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) +``` + +## Beacon chain state transition function + +### Execution engine + +#### Request data + +##### `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload +``` + +#### Engine APIs + +The implementation-dependent `ExecutionEngine` protocol encapsulates the +execution sub-system logic via: + +- a state object `self.execution_state` of type `ExecutionState` +- a notification function `self.notify_new_payload` which may apply changes to + the `self.execution_state` + +The body of these functions are implementation dependent. The Engine API may be +used to implement this and similarly defined functions via an external execution +engine. + +#### `notify_new_payload` + +`notify_new_payload` is a function accessed through the `EXECUTION_ENGINE` +module which instantiates the `ExecutionEngine` protocol. + +```python +def notify_new_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... +``` + +#### `is_valid_block_hash` + +```python +def is_valid_block_hash(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: + """ + Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly. + """ + ... +``` + +#### `verify_and_notify_new_payload` + +```python +def verify_and_notify_new_payload( + self: ExecutionEngine, new_payload_request: NewPayloadRequest +) -> bool: + """ + Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``. + """ + execution_payload = new_payload_request.execution_payload + + if b"" in execution_payload.transactions: + return False + + if not self.is_valid_block_hash(execution_payload): + return False + + if not self.notify_new_payload(execution_payload): + return False + + return True +``` + +### Block processing + +*Note*: The call to the `process_execution_payload` must happen before the call +to the `process_randao` as the former depends on the `randao_mix` computed with +the reveal of the previous block. + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + if is_execution_enabled(state, block.body): + process_execution_payload(state, block.body, EXECUTION_ENGINE) # [New in Bellatrix] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### Execution payload + +##### `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 + if is_merge_transition_complete(state): + 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_time_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.verify_and_notify_new_payload( + NewPayloadRequest(execution_payload=payload) + ) + # 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), + ) +``` + +### Epoch processing + +#### Slashings + +*Note*: The function `process_slashings` is modified to use +`PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX`. + +```python +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) + * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, # [Modified in Bellatrix] + total_balance, + ) + for index, validator in enumerate(state.validators): + if ( + validator.slashed + and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch + ): + increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow + penalty_numerator = ( + validator.effective_balance // increment * adjusted_total_slashing_balance + ) + penalty = penalty_numerator // total_balance * increment + decrease_balance(state, ValidatorIndex(index), penalty) +``` diff --git a/specs/bellatrix/fork-choice.md b/specs/bellatrix/fork-choice.md new file mode 100644 index 0000000000..29caab82f6 --- /dev/null +++ b/specs/bellatrix/fork-choice.md @@ -0,0 +1,324 @@ +# Bellatrix -- Fork Choice + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`notify_forkchoice_updated`](#notify_forkchoice_updated) + - [`safe_block_hash`](#safe_block_hash) + - [`should_override_forkchoice_update`](#should_override_forkchoice_update) +- [Helpers](#helpers) + - [`PayloadAttributes`](#payloadattributes) + - [`PowBlock`](#powblock) + - [`get_pow_block`](#get_pow_block) + - [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block) + - [`validate_merge_block`](#validate_merge_block) +- [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [`on_block`](#on_block) + + + +## Introduction + +This is the modification of the fork choice according to the executable beacon +chain proposal. + +*Note*: It introduces the process of transition from the last PoW block to the +first PoS block. + +## Custom types + +| Name | SSZ equivalent | Description | +| ----------- | -------------- | ---------------------------------------- | +| `PayloadId` | `Bytes8` | Identifier of a payload building process | + +## Protocols + +### `ExecutionEngine` + +*Note*: The `notify_forkchoice_updated` function is added to the +`ExecutionEngine` protocol to signal the fork choice updates. + +The body of this function is implementation dependent. The Engine API may be +used to implement it with an external execution engine. + +#### `notify_forkchoice_updated` + +This function performs three actions *atomically*: + +- Re-organizes the execution payload chain and corresponding state to make + `head_block_hash` the head. +- Updates safe block hash with the value provided by `safe_block_hash` + parameter. +- Applies finality to the execution state: it irreversibly persists the chain of + all execution payloads and corresponding state, up to and including + `finalized_block_hash`. + +Additionally, if `payload_attributes` is provided, this function sets in motion +a payload build process on top of `head_block_hash` and returns an identifier of +initiated process. + +```python +def notify_forkchoice_updated( + self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes], +) -> Optional[PayloadId]: ... +``` + +*Note*: The `(head_block_hash, finalized_block_hash)` values of the +`notify_forkchoice_updated` function call maps on the `POS_FORKCHOICE_UPDATED` +event defined in the +[EIP-3675](https://eips.ethereum.org/EIPS/eip-3675#definitions). As per +EIP-3675, before a post-transition block is finalized, +`notify_forkchoice_updated` MUST be called with +`finalized_block_hash = Hash32()`. + +*Note*: Client software MUST NOT call this function until the transition +conditions are met on the PoW network, i.e. there exists a block for which +`is_valid_terminal_pow_block` function returns `True`. + +*Note*: Client software MUST call this function to initiate the payload build +process to produce the merge transition block; the `head_block_hash` parameter +MUST be set to the hash of a terminal PoW block in this case. + +##### `safe_block_hash` + +The `safe_block_hash` parameter MUST be set to return value of +[`get_safe_execution_block_hash(store: Store)`](../../fork_choice/safe-block.md#get_safe_execution_block_hash) +function. + +##### `should_override_forkchoice_update` + +If proposer boost re-orgs are implemented and enabled (see `get_proposer_head`) +then additional care must be taken to ensure that the proposer is able to build +an execution payload. + +If a beacon node knows it will propose the next block then it SHOULD NOT call +`notify_forkchoice_updated` if it detects the current head to be weak and +potentially capable of being re-orged. Complete information for evaluating +`get_proposer_head` _will not_ be available immediately after the receipt of a +new block, so an approximation of those conditions should be used when deciding +whether to send or suppress a fork choice notification. The exact conditions +used may be implementation-specific, a suggested implementation is below. + +Let `validator_is_connected(validator_index: ValidatorIndex) -> bool` be a +function that indicates whether the validator with `validator_index` is +connected to the node (e.g. has sent an unexpired proposer preparation message). + +```python +def should_override_forkchoice_update(store: Store, head_root: Root) -> bool: + head_block = store.blocks[head_root] + parent_root = head_block.parent_root + parent_block = store.blocks[parent_root] + current_slot = get_current_slot(store) + proposal_slot = head_block.slot + Slot(1) + + # Only re-org the head_block block if it arrived later than the attestation deadline. + head_late = is_head_late(store, head_root) + + # Shuffling stable. + shuffling_stable = is_shuffling_stable(proposal_slot) + + # FFG information of the new head_block will be competitive with the current head. + ffg_competitive = is_ffg_competitive(store, head_root, parent_root) + + # Do not re-org if the chain is not finalizing with acceptable frequency. + finalization_ok = is_finalization_ok(store, proposal_slot) + + # Only suppress the fork choice update if we are confident that we will propose the next block. + parent_state_advanced = store.block_states[parent_root].copy() + process_slots(parent_state_advanced, proposal_slot) + proposer_index = get_beacon_proposer_index(parent_state_advanced) + proposing_reorg_slot = validator_is_connected(proposer_index) + + # Single slot re-org. + parent_slot_ok = parent_block.slot + 1 == head_block.slot + proposing_on_time = is_proposing_on_time(store) + + # Note that this condition is different from `get_proposer_head` + current_time_ok = head_block.slot == current_slot or ( + proposal_slot == current_slot and proposing_on_time + ) + single_slot_reorg = parent_slot_ok and current_time_ok + + # Check the head weight only if the attestations from the head slot have already been applied. + # Implementations may want to do this in different ways, e.g. by advancing + # `store.time` early, or by counting queued attestations during the head block's slot. + if current_slot > head_block.slot: + head_weak = is_head_weak(store, head_root) + parent_strong = is_parent_strong(store, parent_root) + else: + head_weak = True + parent_strong = True + + return all( + [ + head_late, + shuffling_stable, + ffg_competitive, + finalization_ok, + proposing_reorg_slot, + single_slot_reorg, + head_weak, + parent_strong, + ] + ) +``` + +*Note*: The ordering of conditions is a suggestion only. Implementations are +free to optimize by re-ordering the conditions from least to most expensive and +by returning early if any of the early conditions are `False`. + +In case `should_override_forkchoice_update` returns `True`, a node SHOULD +instead call `notify_forkchoice_updated` with parameters appropriate for +building upon the parent block. Care must be taken to compute the correct +`payload_attributes`, as they may change depending on the slot of the block to +be proposed (due to withdrawals). + +If `should_override_forkchoice_update` returns `True` but `get_proposer_head` +later chooses the canonical head rather than its parent, then this is a +misprediction that will cause the node to construct a payload with less notice. +The result of `get_proposer_head` MUST be preferred over the result of +`should_override_forkchoice_update` (when proposer reorgs are enabled). + +## Helpers + +### `PayloadAttributes` + +Used to signal to initiate the payload build process via +`notify_forkchoice_updated`. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress +``` + +### `PowBlock` + +```python +class PowBlock(Container): + block_hash: Hash32 + parent_hash: Hash32 + total_difficulty: uint256 +``` + +### `get_pow_block` + +Let `get_pow_block(block_hash: Hash32) -> Optional[PowBlock]` be the function +that given the hash of the PoW block returns its data. It may result in `None` +if the requested block is not yet available. + +*Note*: The `eth_getBlockByHash` JSON-RPC method may be used to pull this +information from an execution client. + +### `is_valid_terminal_pow_block` + +Used by fork-choice handler, `on_block`. + +```python +def is_valid_terminal_pow_block(block: PowBlock, parent: PowBlock) -> bool: + is_total_difficulty_reached = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY + is_parent_total_difficulty_valid = parent.total_difficulty < TERMINAL_TOTAL_DIFFICULTY + return is_total_difficulty_reached and is_parent_total_difficulty_valid +``` + +### `validate_merge_block` + +```python +def validate_merge_block(block: BeaconBlock) -> None: + """ + Check the parent PoW block of execution payload is a valid terminal PoW block. + + Note: Unavailable PoW block(s) may later become available, + and a client software MAY delay a call to ``validate_merge_block`` + until the PoW block(s) become available. + """ + if TERMINAL_BLOCK_HASH != Hash32(): + # If `TERMINAL_BLOCK_HASH` is used as an override, the activation epoch must be reached. + assert compute_epoch_at_slot(block.slot) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + assert block.body.execution_payload.parent_hash == TERMINAL_BLOCK_HASH + return + + pow_block = get_pow_block(block.body.execution_payload.parent_hash) + # Check if `pow_block` is available + assert pow_block is not None + pow_parent = get_pow_block(pow_block.parent_hash) + # Check if `pow_parent` is available + assert pow_parent is not None + # Check if `pow_block` is a valid terminal PoW block + assert is_valid_terminal_pow_block(pow_block, pow_parent) +``` + +## Updated fork-choice handlers + +### `on_block` + +*Note*: The only modification is the addition of the verification of transition +block conditions. + +```python +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + + A block that is asserted as invalid due to unavailable PoW block may be valid at a later time, + consider scheduling it for later processing in such case. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + pre_state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block + + # Check the block is valid and compute the post-state + state = pre_state.copy() + block_root = hash_tree_root(block) + state_transition(state, signed_block, True) + + # [New in Bellatrix] + if is_merge_transition_block(pre_state, block.body): + validate_merge_block(block) + + # Add new block to the store + store.blocks[block_root] = block + # Add new state for this block to the store + store.block_states[block_root] = state + + # Add block timeliness to the store + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely + + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) + + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) + + # Eagerly compute unrealized justification and finality. + compute_pulled_up_tip(store, block_root) +``` diff --git a/specs/bellatrix/fork.md b/specs/bellatrix/fork.md new file mode 100644 index 0000000000..d42ded2ae5 --- /dev/null +++ b/specs/bellatrix/fork.md @@ -0,0 +1,112 @@ +# Bellatrix -- Fork Logic + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to Bellatrix](#fork-to-bellatrix) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of Bellatrix upgrade. + +## Configuration + +| Name | Value | +| ------------------------ | ---------------------------------------------- | +| `BELLATRIX_FORK_VERSION` | `Version('0x02000000')` | +| `BELLATRIX_FORK_EPOCH` | `Epoch(144896)` (Sept 6, 2022, 11:34:47am UTC) | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to Bellatrix + +### Fork trigger + +TBD. Social consensus, along with state conditions such as epoch boundary, +finality, deposits, active validator count, etc. may be part of the decision +process to trigger the fork. For now we assume the condition will be triggered +at epoch `BELLATRIX_FORK_EPOCH`. + +Note that for the pure Bellatrix networks, we don't apply `upgrade_to_bellatrix` +since it starts with Bellatrix version logic. + +### Upgrading the state + +As with the Phase0-to-Altair upgrade, the `state_transition` is modified to +upgrade the `BeaconState`. The `BeaconState` upgrade runs as part of +`process_slots`, slots with missing block proposals do not affect the upgrade +time. + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == BELLATRIX_FORK_EPOCH`, an irregular state +change is made to upgrade to Bellatrix. The upgrade occurs after the completion +of the inner loop of `process_slots` that sets `state.slot` equal to +`BELLATRIX_FORK_EPOCH * SLOTS_PER_EPOCH`. + +When multiple upgrades are scheduled for the same epoch (common for +test-networks), all the upgrades run in sequence before resuming the regular +state transition. + +```python +def upgrade_to_bellatrix(pre: altair.BeaconState) -> BeaconState: + epoch = altair.get_current_epoch(pre) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [New in Bellatrix] + current_version=BELLATRIX_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # [New in Bellatrix] + latest_execution_payload_header=ExecutionPayloadHeader(), + ) + + return post +``` diff --git a/specs/bellatrix/p2p-interface.md b/specs/bellatrix/p2p-interface.md new file mode 100644 index 0000000000..daf6e02e97 --- /dev/null +++ b/specs/bellatrix/p2p-interface.md @@ -0,0 +1,219 @@ +# Bellatrix -- Networking + + + +- [Introduction](#introduction) +- [Modifications in Bellatrix](#modifications-in-bellatrix) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [Gossipsub](#gossipsub) + - [Why was the max gossip message size increased at Bellatrix?](#why-was-the-max-gossip-message-size-increased-at-bellatrix) + - [Req/Resp](#reqresp) + - [Why was the max chunk response size increased at Bellatrix?](#why-was-the-max-chunk-response-size-increased-at-bellatrix) + - [Why allow invalid payloads on the P2P network?](#why-allow-invalid-payloads-on-the-p2p-network) + + + +## 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 + +Some gossip meshes are upgraded in Bellatrix to support upgraded types. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. All topics remain +stable except the beacon block topic which is updated with the modified type. + +The specification around the creation, validation, and dissemination of messages +has not changed from the Phase 0 and Altair documents unless explicitly noted +here. + +The derivation of the `message-id` remains stable. + +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: + +| Name | Message Type | +| -------------- | ------------------------------ | +| `beacon_block` | `SignedBeaconBlock` (modified) | + +Note that the `ForkDigestValue` path segment of the topic separates the old and +the new `beacon_block` topics. + +##### Global topics + +Bellatrix changes the type of the global beacon block topic. + +###### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) +`SignedBeaconBlock` found in Bellatrix. Specifically, this type changes with the +addition of `execution_payload` to the inner `BeaconBlockBody`. See Bellatrix +[state transition document](./beacon-chain.md#beaconblockbody) for further +details. + +Blocks with execution enabled will be permitted to propagate regardless of the +validity of the execution payload. This prevents network segregation between +[optimistic](/sync/optimistic.md) and non-optimistic nodes. + +In addition to the gossip validations for this topic from prior specifications, +the following validations MUST pass before forwarding the `signed_beacon_block` +on the network. Alias `block = signed_beacon_block.message`, +`execution_payload = block.body.execution_payload`. + +If the execution is enabled for the block -- i.e. +`is_execution_enabled(state, block.body)` then validate the following: + +- _[REJECT]_ The block's execution payload timestamp is correct with respect to + the slot -- i.e. + `execution_payload.timestamp == compute_time_at_slot(state, block.slot)`. +- If `execution_payload` verification of block's parent by an execution node is + *not* complete: + - _[REJECT]_ The block's parent (defined by `block.parent_root`) passes all + validation (excluding execution node verification of the + `block.body.execution_payload`). +- Otherwise: + - _[IGNORE]_ The block's parent (defined by `block.parent_root`) passes all + validation (including execution node verification of the + `block.body.execution_payload`). + +The following gossip validation from prior specifications MUST NOT be applied if +the execution is enabled for the block -- i.e. +`is_execution_enabled(state, block.body)`: + +- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes + validation. + +#### Transitioning the gossip + +See gossip transition details found in the +[Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics. + +### The Req/Resp domain + +Non-faulty, [optimistic](/sync/optimistic.md) nodes may send blocks which result +in an INVALID response from an execution engine. To prevent network segregation +between optimistic and non-optimistic nodes, transmission of an INVALID +execution payload via the Req/Resp domain SHOULD NOT cause a node to be +down-scored or disconnected. Transmission of a block which is invalid due to any +consensus layer rules (i.e., *not* execution layer rules) MAY result in +down-scoring or disconnection. + +#### Messages + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +Request and Response remain unchanged unless explicitly noted here. + +Bellatrix fork-digest is introduced to the `context` enum to specify Bellatrix +block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Request and Response remain unchanged. Bellatrix fork-digest is introduced to +the `context` enum to specify Bellatrix block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | + +# Design decision rationale + +### Gossipsub + +#### Why was the max gossip message size increased at Bellatrix? + +With the addition of `ExecutionPayload` to `BeaconBlock`s, there is a dynamic +field -- `transactions` -- which can validly exceed the `MAX_PAYLOAD_SIZE` limit +(1 MiB) put in place at Phase 0, so MAX_PAYLOAD_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 current mainnet conditions. + +Note, that due to additional size induced by the `BeaconBlock` contents (e.g. +proposer signature, operations lists, etc) this does reduce the theoretical max +valid `ExecutionPayload` (and `transactions` list) size as slightly lower than +10 MiB. Considering that `BeaconBlock` max size is on the order of 128 KiB in +the worst case and the current gas limit (~30M) bounds max blocksize to less +than 2 MiB today, this marginal difference in theoretical bounds will have zero +impact on network functionality and security. + +### Req/Resp + +#### Why was the max chunk response size increased at Bellatrix? + +Similar to the discussion about the maximum gossip size increase, the +`ExecutionPayload` type can cause `BeaconBlock`s to exceed the 1 MiB bounds put +in place during Phase 0. + +As with the gossip limit, 10 MiB is selected because this is firmly above any +valid block sizes in the range of gas limits expected in the medium term. + +As with both gossip and req/rsp maximum values, type-specific limits should +always by simultaneously respected. + +#### Why allow invalid payloads on the P2P network? + +The specification allows blocks with invalid execution payloads to propagate +across gossip and via RPC calls. The reasoning for this is as follows: + +1. Optimistic nodes must listen to block gossip to obtain a view of the head of + the chain. +2. Therefore, optimistic nodes must propagate gossip blocks. Otherwise, they'd + be censoring. +3. If optimistic nodes will propagate blocks via gossip, then they must respond + to requests for the parent via RPC. +4. Therefore, optimistic nodes must send optimistic blocks via RPC. + +So, to prevent network segregation from optimistic nodes inadvertently sending +invalid execution payloads, nodes should never downscore/disconnect nodes due to +such invalid payloads. This does open the network to some DoS attacks from +invalid execution payloads, but the scope of actors is limited to validators who +can put those payloads in valid (and slashable) beacon blocks. Therefore, it is +argued that the DoS risk introduced in tolerable. + +More complicated schemes are possible that could restrict invalid payloads from +RPC. However, it's not clear that complexity is warranted. diff --git a/specs/bellatrix/validator.md b/specs/bellatrix/validator.md new file mode 100644 index 0000000000..73efc94670 --- /dev/null +++ b/specs/bellatrix/validator.md @@ -0,0 +1,204 @@ +# Bellatrix -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [`GetPayloadResponse`](#getpayloadresponse) + - [`get_pow_block_at_terminal_total_difficulty`](#get_pow_block_at_terminal_total_difficulty) + - [`get_terminal_pow_block`](#get_terminal_pow_block) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`get_payload`](#get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement executable beacon chain proposal. + +## Prerequisites + +This document is an extension of the +[Altair -- Honest Validator](../altair/validator.md) guide. All behaviors and +definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated Beacon Chain doc of [Bellatrix](./beacon-chain.md) are requisite for +this document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Helpers + +### `GetPayloadResponse` + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload +``` + +### `get_pow_block_at_terminal_total_difficulty` + +```python +def get_pow_block_at_terminal_total_difficulty( + pow_chain: Dict[Hash32, PowBlock], +) -> Optional[PowBlock]: + # `pow_chain` abstractly represents all blocks in the PoW chain + for block in pow_chain.values(): + block_reached_ttd = block.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY + if block_reached_ttd: + # If genesis block, no parent exists so reaching TTD alone qualifies as valid terminal block + if block.parent_hash == Hash32(): + return block + parent = pow_chain[block.parent_hash] + parent_reached_ttd = parent.total_difficulty >= TERMINAL_TOTAL_DIFFICULTY + if not parent_reached_ttd: + return block + + return None +``` + +### `get_terminal_pow_block` + +```python +def get_terminal_pow_block(pow_chain: Dict[Hash32, PowBlock]) -> Optional[PowBlock]: + if TERMINAL_BLOCK_HASH != Hash32(): + # Terminal block hash override takes precedence over terminal total difficulty + if TERMINAL_BLOCK_HASH in pow_chain: + return pow_chain[TERMINAL_BLOCK_HASH] + else: + return None + + return get_pow_block_at_terminal_total_difficulty(pow_chain) +``` + +*Note*: This function does *not* use simple serialize `hash_tree_root` as to +avoid requiring simple serialize hashing capabilities in the Execution Layer. + +## Protocols + +### `ExecutionEngine` + +*Note*: `get_payload` function is added to the `ExecutionEngine` protocol for +use as a validator. + +The body of this function is implementation dependent. The Engine API may be +used to implement it with an external execution engine. + +#### `get_payload` + +Given the `payload_id`, `get_payload` returns `GetPayloadResponse` with the most +recent version of the execution payload that has been built since the +corresponding call to `notify_forkchoice_updated` method. + +```python +def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + """ + Return ``GetPayloadResponse`` object. + """ + ... +``` + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. +Namely, the transition block handling and the addition of `ExecutionPayload`. + +*Note*: A validator must not propose on or attest to a block that isn't deemed +valid, i.e. hasn't yet passed the beacon chain state transition and execution +validations. In future upgrades, an "execution Proof-of-Custody" will be +integrated to prevent outsourcing of execution payload validations. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### ExecutionPayload + +To obtain an execution payload, a block proposer building a block on top of a +`state` must take the following actions: + +1. Set + `payload_id = prepare_execution_payload(state, pow_chain, safe_block_hash, finalized_block_hash, suggested_fee_recipient, execution_engine)`, + where: + - `state` is the state object after applying `process_slots(state, slot)` + transition to the resulting state of the parent block processing + - `pow_chain` is a `Dict[Hash32, PowBlock]` dictionary that abstractly + represents all blocks in the PoW chain with block hash as the dictionary + key + - `safe_block_hash` is the return value of the + `get_safe_execution_block_hash(store: Store)` function call + - `finalized_block_hash` is the block hash of the latest finalized execution + payload (`Hash32()` if none yet finalized) + - `suggested_fee_recipient` is the value suggested to be used for the + `fee_recipient` field of the execution payload + +```python +def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, + pow_chain: Optional[Dict[Hash32, PowBlock]] = None, +) -> Optional[PayloadId]: + if not is_merge_transition_complete(state): + assert pow_chain is not None + is_terminal_block_hash_set = TERMINAL_BLOCK_HASH != Hash32() + is_activation_epoch_reached = ( + get_current_epoch(state) >= TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH + ) + if is_terminal_block_hash_set and not is_activation_epoch_reached: + # Terminal block hash is set but activation epoch is not yet reached, no prepare payload call is needed + return None + + terminal_pow_block = get_terminal_pow_block(pow_chain) + if terminal_pow_block is None: + # Pre-merge, no prepare payload call is needed + return None + # Signify merge via producing on top of the terminal PoW block + parent_hash = terminal_pow_block.block_hash + else: + # Post-merge, normal payload + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) +``` + +2. Set + `block.body.execution_payload = get_execution_payload(payload_id, execution_engine)`, + where: + +```python +def get_execution_payload( + payload_id: Optional[PayloadId], execution_engine: ExecutionEngine +) -> ExecutionPayload: + if payload_id is None: + # Pre-merge, empty payload + return ExecutionPayload() + else: + return execution_engine.get_payload(payload_id).execution_payload +``` + +*Note*: It is recommended for a validator to call `prepare_execution_payload` as +soon as input parameters become known, and make subsequent calls to this +function when any of these parameters gets updated. diff --git a/specs/capella/beacon-chain.md b/specs/capella/beacon-chain.md new file mode 100644 index 0000000000..2559ae76b6 --- /dev/null +++ b/specs/capella/beacon-chain.md @@ -0,0 +1,494 @@ +# Capella -- The Beacon Chain + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) + - [Domain types](#domain-types) +- [Preset](#preset) + - [Max operations per block](#max-operations-per-block) + - [Execution](#execution) + - [Withdrawals processing](#withdrawals-processing) +- [Containers](#containers) + - [New containers](#new-containers) + - [`Withdrawal`](#withdrawal) + - [`BLSToExecutionChange`](#blstoexecutionchange) + - [`SignedBLSToExecutionChange`](#signedblstoexecutionchange) + - [`HistoricalSummary`](#historicalsummary) + - [Modified containers](#modified-containers) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`BeaconBlockBody`](#beaconblockbody) + - [`BeaconState`](#beaconstate) +- [Helpers](#helpers) + - [Predicates](#predicates) + - [`has_eth1_withdrawal_credential`](#has_eth1_withdrawal_credential) + - [`is_fully_withdrawable_validator`](#is_fully_withdrawable_validator) + - [`is_partially_withdrawable_validator`](#is_partially_withdrawable_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Epoch processing](#epoch-processing) + - [Historical summaries updates](#historical-summaries-updates) + - [Block processing](#block-processing) + - [New `get_expected_withdrawals`](#new-get_expected_withdrawals) + - [New `process_withdrawals`](#new-process_withdrawals) + - [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) + + + +## Introduction + +Capella is a consensus-layer upgrade containing a number of features related to +validator withdrawals. Including: + +- Automatic withdrawals of `withdrawable` validators. +- Partial withdrawals sweep for validators with 0x01 withdrawal credentials and + balances in excess of `MAX_EFFECTIVE_BALANCE`. +- Operation to change from `BLS_WITHDRAWAL_PREFIX` to + `ETH1_ADDRESS_WITHDRAWAL_PREFIX` versioned withdrawal credentials to enable + withdrawals for a validator. + +Another new feature is the new independent state and block historical +accumulators that replace the original singular historical roots. With these +accumulators, it becomes possible to validate the entire block history that led +up to that particular state without any additional information beyond the state +and the blocks. + +## Custom types + +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| ----------------- | -------------- | -------------------------- | +| `WithdrawalIndex` | `uint64` | an index of a `Withdrawal` | + +### Domain types + +| Name | Value | +| -------------------------------- | -------------------------- | +| `DOMAIN_BLS_TO_EXECUTION_CHANGE` | `DomainType('0x0A000000')` | + +## Preset + +### Max operations per block + +| Name | Value | +| ------------------------------ | ------------- | +| `MAX_BLS_TO_EXECUTION_CHANGES` | `2**4` (= 16) | + +### Execution + +| Name | Value | Description | +| ----------------------------- | --------------------- | ----------------------------------------------------- | +| `MAX_WITHDRAWALS_PER_PAYLOAD` | `uint64(2**4)` (= 16) | Maximum amount of withdrawals allowed in each payload | + +### Withdrawals processing + +| Name | Value | +| -------------------------------------- | ------------------ | +| `MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP` | `2**14` (= 16,384) | + +## Containers + +### New containers + +#### `Withdrawal` + +```python +class Withdrawal(Container): + index: WithdrawalIndex + validator_index: ValidatorIndex + address: ExecutionAddress + amount: Gwei +``` + +#### `BLSToExecutionChange` + +```python +class BLSToExecutionChange(Container): + validator_index: ValidatorIndex + from_bls_pubkey: BLSPubkey + to_execution_address: ExecutionAddress +``` + +#### `SignedBLSToExecutionChange` + +```python +class SignedBLSToExecutionChange(Container): + message: BLSToExecutionChange + signature: BLSSignature +``` + +#### `HistoricalSummary` + +*Note*: `HistoricalSummary` matches the components of the phase0 +`HistoricalBatch` making the two \*hash_tree_root-compatible. + +```python +class HistoricalSummary(Container): + block_summary_root: Root + state_summary_root: Root +``` + +### Modified containers + +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + # [New in Capella] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions_root: Root + # [New in Capella] + withdrawals_root: Root +``` + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + execution_payload: ExecutionPayload + # [New in Capella] + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] +``` + +#### `BeaconState` + +*Note*: `historical_roots` is frozen in Capella and is replaced by +`historical_summaries`. + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # [Modified in Capella] + latest_execution_payload_header: ExecutionPayloadHeader + # [New in Capella] + next_withdrawal_index: WithdrawalIndex + # [New in Capella] + next_withdrawal_validator_index: ValidatorIndex + # [New in Capella] + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] +``` + +## Helpers + +### Predicates + +#### `has_eth1_withdrawal_credential` + +```python +def has_eth1_withdrawal_credential(validator: Validator) -> bool: + """ + Check if ``validator`` has an 0x01 prefixed "eth1" withdrawal credential. + """ + return validator.withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX +``` + +#### `is_fully_withdrawable_validator` + +```python +def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool: + """ + Check if ``validator`` is fully withdrawable. + """ + return ( + has_eth1_withdrawal_credential(validator) + and validator.withdrawable_epoch <= epoch + and balance > 0 + ) +``` + +#### `is_partially_withdrawable_validator` + +```python +def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool: + """ + Check if ``validator`` is partially withdrawable. + """ + has_max_effective_balance = validator.effective_balance == MAX_EFFECTIVE_BALANCE + has_excess_balance = balance > MAX_EFFECTIVE_BALANCE + return ( + has_eth1_withdrawal_credential(validator) + and has_max_effective_balance + and has_excess_balance + ) +``` + +## Beacon chain state transition function + +### Epoch processing + +*Note*: The function `process_historical_summaries_update` replaces +`process_historical_roots_update` in Capella. + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_summaries_update(state) # [Modified in Capella] + process_participation_flag_updates(state) + process_sync_committee_updates(state) +``` + +#### Historical summaries updates + +```python +def process_historical_summaries_update(state: BeaconState) -> None: + # Set historical block root accumulator. + next_epoch = Epoch(get_current_epoch(state) + 1) + if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: + historical_summary = HistoricalSummary( + block_summary_root=hash_tree_root(state.block_roots), + state_summary_root=hash_tree_root(state.state_roots), + ) + state.historical_summaries.append(historical_summary) +``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + # [Modified in Capella] Removed `is_execution_enabled` check in Capella + process_withdrawals(state, block.body.execution_payload) # [New in Capella] + process_execution_payload(state, block.body, EXECUTION_ENGINE) # [Modified in Capella] + process_randao(state, block.body) + process_eth1_data(state, block.body) + process_operations(state, block.body) # [Modified in Capella] + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### New `get_expected_withdrawals` + +```python +def get_expected_withdrawals(state: BeaconState) -> Sequence[Withdrawal]: + epoch = get_current_epoch(state) + withdrawal_index = state.next_withdrawal_index + validator_index = state.next_withdrawal_validator_index + withdrawals: List[Withdrawal] = [] + bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) + for _ in range(bound): + validator = state.validators[validator_index] + balance = state.balances[validator_index] + if is_fully_withdrawable_validator(validator, balance, epoch): + withdrawals.append( + Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=balance, + ) + ) + withdrawal_index += WithdrawalIndex(1) + elif is_partially_withdrawable_validator(validator, balance): + withdrawals.append( + Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=balance - MAX_EFFECTIVE_BALANCE, + ) + ) + withdrawal_index += WithdrawalIndex(1) + if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + break + validator_index = ValidatorIndex((validator_index + 1) % len(state.validators)) + return withdrawals +``` + +#### New `process_withdrawals` + +```python +def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: + expected_withdrawals = get_expected_withdrawals(state) + assert payload.withdrawals == expected_withdrawals + + for withdrawal in expected_withdrawals: + decrease_balance(state, withdrawal.validator_index, withdrawal.amount) + + # Update the next withdrawal index if this block contained withdrawals + if len(expected_withdrawals) != 0: + latest_withdrawal = expected_withdrawals[-1] + state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) + + # Update the next validator index to start the next withdrawal sweep + if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + # Next sweep starts after the latest withdrawal's validator index + next_validator_index = ValidatorIndex( + (expected_withdrawals[-1].validator_index + 1) % len(state.validators) + ) + state.next_withdrawal_validator_index = next_validator_index + else: + # Advance sweep by the max length of the sweep if there was not a full set of withdrawals + next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + next_validator_index = ValidatorIndex(next_index % len(state.validators)) + state.next_withdrawal_validator_index = next_validator_index +``` + +#### Modified `process_execution_payload` + +*Note*: The function `process_execution_payload` is modified to use the new +`ExecutionPayloadHeader` type and removed the `is_merge_transition_complete` +check. + +```python +def process_execution_payload( + state: BeaconState, body: BeaconBlockBody, execution_engine: ExecutionEngine +) -> None: + payload = body.execution_payload + # [Modified in Capella] Removed `is_merge_transition_complete` check in Capella + # 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_time_at_slot(state, state.slot) + # Verify the execution payload is valid + assert execution_engine.verify_and_notify_new_payload( + NewPayloadRequest(execution_payload=payload) + ) + # 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), + # [New in Capella] + withdrawals_root=hash_tree_root(payload.withdrawals), + ) +``` + +#### Modified `process_operations` + +*Note*: The function `process_operations` is modified to process +`BLSToExecutionChange` operations included in the block. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # Verify that outstanding deposits are processed up to the maximum number of deposits + assert len(body.deposits) == min( + MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index + ) + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + for_ops(body.voluntary_exits, process_voluntary_exit) + # [New in Capella] + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) +``` + +#### New `process_bls_to_execution_change` + +```python +def process_bls_to_execution_change( + state: BeaconState, signed_address_change: SignedBLSToExecutionChange +) -> None: + address_change = signed_address_change.message + + assert address_change.validator_index < len(state.validators) + + validator = state.validators[address_change.validator_index] + + assert validator.withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:] == hash(address_change.from_bls_pubkey)[1:] + + # Fork-agnostic domain since address changes are valid across forks + domain = compute_domain( + DOMAIN_BLS_TO_EXECUTION_CHANGE, genesis_validators_root=state.genesis_validators_root + ) + signing_root = compute_signing_root(address_change, domain) + assert bls.Verify(address_change.from_bls_pubkey, signing_root, signed_address_change.signature) + + validator.withdrawal_credentials = ( + ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + address_change.to_execution_address + ) +``` diff --git a/specs/capella/fork-choice.md b/specs/capella/fork-choice.md new file mode 100644 index 0000000000..561bcb46f1 --- /dev/null +++ b/specs/capella/fork-choice.md @@ -0,0 +1,120 @@ +# Capella -- Fork Choice + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [`notify_forkchoice_updated`](#notify_forkchoice_updated) +- [Helpers](#helpers) + - [Modified `PayloadAttributes`](#modified-payloadattributes) +- [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [`on_block`](#on_block) + + + +## Introduction + +This is the modification of the fork choice according to the Capella upgrade. + +Unless stated explicitly, all prior functionality from +[Bellatrix](../bellatrix/fork-choice.md) is inherited. + +## Custom types + +## Protocols + +### `ExecutionEngine` + +*Note*: The `notify_forkchoice_updated` function is modified in the +`ExecutionEngine` protocol at the Capella upgrade. + +#### `notify_forkchoice_updated` + +The only change made is to the `PayloadAttributes` container through the +addition of `withdrawals`. Otherwise, `notify_forkchoice_updated` inherits all +prior functionality. + +```python +def notify_forkchoice_updated( + self: ExecutionEngine, + head_block_hash: Hash32, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + payload_attributes: Optional[PayloadAttributes], +) -> Optional[PayloadId]: ... +``` + +## Helpers + +### Modified `PayloadAttributes` + +`PayloadAttributes` is extended with the `withdrawals` field. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress + withdrawals: Sequence[Withdrawal] # [New in Capella] +``` + +## Updated fork-choice handlers + +### `on_block` + +*Note*: The only modification is the deletion of the verification of merge +transition block conditions. + +```python +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block + + # Check the block is valid and compute the post-state + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[block.parent_root]) + block_root = hash_tree_root(block) + state_transition(state, signed_block, True) + + # Add new block to the store + store.blocks[block_root] = block + # Add new state for this block to the store + store.block_states[block_root] = state + + # Add block timeliness to the store + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely + + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) + + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) + + # Eagerly compute unrealized justification and finality. + compute_pulled_up_tip(store, block_root) +``` diff --git a/specs/capella/fork.md b/specs/capella/fork.md new file mode 100644 index 0000000000..5d8f04bae2 --- /dev/null +++ b/specs/capella/fork.md @@ -0,0 +1,132 @@ +# Capella -- Fork Logic + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to Capella](#fork-to-capella) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the Capella upgrade. + +## Configuration + +| Name | Value | +| ---------------------- | ------------------------------------------------ | +| `CAPELLA_FORK_VERSION` | `Version('0x03000000')` | +| `CAPELLA_FORK_EPOCH` | `Epoch(194048)` (April 12, 2023, 10:27:35pm UTC) | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to Capella + +### Fork trigger + +The fork is triggered at epoch `CAPELLA_FORK_EPOCH`. + +Note that for the pure Capella networks, we don't apply `upgrade_to_capella` +since it starts with Capella version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == CAPELLA_FORK_EPOCH`, an irregular state +change is made to upgrade to Capella. + +The upgrade occurs after the completion of the inner loop of `process_slots` +that sets `state.slot` equal to `CAPELLA_FORK_EPOCH * SLOTS_PER_EPOCH`. Care +must be taken when transitioning through the fork boundary as implementations +will need a modified +[state transition function](../phase0/beacon-chain.md#beacon-chain-state-transition-function) +that deviates from the Phase 0 document. In particular, the outer +`state_transition` function defined in the Phase 0 document will not expose the +precise fork slot to execute the upgrade in the presence of skipped slots at the +fork boundary. Instead, the logic must be within `process_slots`. + +```python +def upgrade_to_capella(pre: bellatrix.BeaconState) -> BeaconState: + epoch = bellatrix.get_current_epoch(pre) + latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=pre.latest_execution_payload_header.parent_hash, + fee_recipient=pre.latest_execution_payload_header.fee_recipient, + state_root=pre.latest_execution_payload_header.state_root, + receipts_root=pre.latest_execution_payload_header.receipts_root, + logs_bloom=pre.latest_execution_payload_header.logs_bloom, + prev_randao=pre.latest_execution_payload_header.prev_randao, + block_number=pre.latest_execution_payload_header.block_number, + gas_limit=pre.latest_execution_payload_header.gas_limit, + gas_used=pre.latest_execution_payload_header.gas_used, + timestamp=pre.latest_execution_payload_header.timestamp, + extra_data=pre.latest_execution_payload_header.extra_data, + base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas, + block_hash=pre.latest_execution_payload_header.block_hash, + transactions_root=pre.latest_execution_payload_header.transactions_root, + # [New in Capella] + withdrawals_root=Root(), + ) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + current_version=CAPELLA_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=latest_execution_payload_header, + # [New in Capella] + next_withdrawal_index=WithdrawalIndex(0), + # [New in Capella] + next_withdrawal_validator_index=ValidatorIndex(0), + # [New in Capella] + historical_summaries=List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT]([]), + ) + + return post +``` diff --git a/specs/capella/light-client/fork.md b/specs/capella/light-client/fork.md new file mode 100644 index 0000000000..76a7148a2c --- /dev/null +++ b/specs/capella/light-client/fork.md @@ -0,0 +1,99 @@ +# Capella Light Client -- Fork Logic + + + +- [Introduction](#introduction) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) + + + +## Introduction + +This document describes how to upgrade existing light client objects based on +the [Altair specification](../../altair/light-client/sync-protocol.md) to +Capella. This is necessary when processing pre-Capella data with a post-Capella +`LightClientStore`. Note that the data being exchanged over the network +protocols uses the original format. + +## Upgrading light client data + +A Capella `LightClientStore` can still process earlier light client data. In +order to do so, that pre-Capella data needs to be locally upgraded to Capella +before processing. + +```python +def upgrade_lc_header_to_capella(pre: bellatrix.LightClientHeader) -> LightClientHeader: + return LightClientHeader( + beacon=pre.beacon, + ) +``` + +```python +def upgrade_lc_bootstrap_to_capella(pre: bellatrix.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_capella(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=pre.current_sync_committee_branch, + ) +``` + +```python +def upgrade_lc_update_to_capella(pre: bellatrix.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=pre.next_sync_committee_branch, + finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_capella( + pre: bellatrix.LightClientFinalityUpdate, +) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_capella( + pre: bellatrix.LightClientOptimisticUpdate, +) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_capella(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +## Upgrading the store + +Existing `LightClientStore` objects based on Altair MUST be upgraded to Capella +before Capella based light client data can be processed. The `LightClientStore` +upgrade MAY be performed before `CAPELLA_FORK_EPOCH`. + +```python +def upgrade_lc_store_to_capella(pre: bellatrix.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_capella(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_capella(pre.finalized_header), + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + best_valid_update=best_valid_update, + optimistic_header=upgrade_lc_header_to_capella(pre.optimistic_header), + previous_max_active_participants=pre.previous_max_active_participants, + current_max_active_participants=pre.current_max_active_participants, + ) +``` diff --git a/specs/capella/light-client/full-node.md b/specs/capella/light-client/full-node.md new file mode 100644 index 0000000000..d041d866b3 --- /dev/null +++ b/specs/capella/light-client/full-node.md @@ -0,0 +1,65 @@ +# Capella Light Client -- Full Node + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [Modified `block_to_light_client_header`](#modified-block_to_light_client_header) + + + +## Introduction + +This upgrade adds information about the execution payload to light client data +as part of the Capella upgrade. + +## Helper functions + +### Modified `block_to_light_client_header` + +```python +def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: + epoch = compute_epoch_at_slot(block.message.slot) + + if epoch >= CAPELLA_FORK_EPOCH: + payload = block.message.body.execution_payload + execution_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), + ) + execution_branch = ExecutionBranch( + compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX) + ) + else: + # Note that during fork transitions, `finalized_header` may still point to earlier forks. + # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), + # it was not included in the corresponding light client data. To ensure compatibility + # with legacy data going through `upgrade_lc_header_to_capella`, leave out execution data. + execution_header = ExecutionPayloadHeader() + execution_branch = ExecutionBranch() + + return LightClientHeader( + beacon=BeaconBlockHeader( + slot=block.message.slot, + proposer_index=block.message.proposer_index, + parent_root=block.message.parent_root, + state_root=block.message.state_root, + body_root=hash_tree_root(block.message.body), + ), + execution=execution_header, + execution_branch=execution_branch, + ) +``` diff --git a/specs/capella/light-client/p2p-interface.md b/specs/capella/light-client/p2p-interface.md new file mode 100644 index 0000000000..eb979da673 --- /dev/null +++ b/specs/capella/light-client/p2p-interface.md @@ -0,0 +1,94 @@ +# Capella Light Client -- Networking + + + +- [Networking](#networking) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`light_client_finality_update`](#light_client_finality_update) + - [`light_client_optimistic_update`](#light_client_optimistic_update) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [GetLightClientBootstrap](#getlightclientbootstrap) + - [LightClientUpdatesByRange](#lightclientupdatesbyrange) + - [GetLightClientFinalityUpdate](#getlightclientfinalityupdate) + - [GetLightClientOptimisticUpdate](#getlightclientoptimisticupdate) + + + +## Networking + +The +[Altair light client networking specification](../../altair/light-client/p2p-interface.md) +is extended to exchange [Capella light client data](./sync-protocol.md). + +### The gossip domain: gossipsub + +#### Topics and messages + +##### Global topics + +###### `light_client_finality_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientFinalityUpdate` | + +###### `light_client_optimistic_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientOptimisticUpdate` | + +### The Req/Resp domain + +#### Messages + +##### GetLightClientBootstrap + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientBootstrap` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientBootstrap` | + +##### LightClientUpdatesByRange + + + +| `fork_version` | Response chunk SSZ type | +| ------------------------------------------------------ | --------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientUpdate` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientUpdate` | + +##### GetLightClientFinalityUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientFinalityUpdate` | + +##### GetLightClientOptimisticUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` and later | `capella.LightClientOptimisticUpdate` | diff --git a/specs/capella/light-client/sync-protocol.md b/specs/capella/light-client/sync-protocol.md new file mode 100644 index 0000000000..e9b28d5e05 --- /dev/null +++ b/specs/capella/light-client/sync-protocol.md @@ -0,0 +1,87 @@ +# Capella Light Client -- Sync Protocol + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) +- [Containers](#containers) + - [Modified `LightClientHeader`](#modified-lightclientheader) +- [Helper functions](#helper-functions) + - [`get_lc_execution_root`](#get_lc_execution_root) + - [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header) + + + +## Introduction + +This upgrade adds information about the execution payload to light client data +as part of the Capella upgrade. It extends the +[Altair Light Client specifications](../../altair/light-client/sync-protocol.md). +The [fork document](./fork.md) explains how to upgrade existing Altair based +deployments to Capella. + +Additional documents describes the impact of the upgrade on certain roles: + +- [Full node](./full-node.md) +- [Networking](./p2p-interface.md) + +## Custom types + +| Name | SSZ equivalent | Description | +| ----------------- | ------------------------------------------------------ | ------------------------------------------------------------- | +| `ExecutionBranch` | `Vector[Bytes32, floorlog2(EXECUTION_PAYLOAD_GINDEX)]` | Merkle branch of `execution_payload` within `BeaconBlockBody` | + +## Constants + +| Name | Value | +| -------------------------- | -------------------------------------------------------------------- | +| `EXECUTION_PAYLOAD_GINDEX` | `get_generalized_index(BeaconBlockBody, 'execution_payload')` (= 25) | + +## Containers + +### Modified `LightClientHeader` + +```python +class LightClientHeader(Container): + # Beacon block header + beacon: BeaconBlockHeader + # Execution payload header corresponding to `beacon.body_root` (from Capella onward) + execution: ExecutionPayloadHeader + execution_branch: ExecutionBranch +``` + +## Helper functions + +### `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + epoch = compute_epoch_at_slot(header.beacon.slot) + + if epoch >= CAPELLA_FORK_EPOCH: + return hash_tree_root(header.execution) + + return Root() +``` + +### Modified `is_valid_light_client_header` + +```python +def is_valid_light_client_header(header: LightClientHeader) -> bool: + epoch = compute_epoch_at_slot(header.beacon.slot) + + if epoch < CAPELLA_FORK_EPOCH: + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == ExecutionBranch() + ) + + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + root=header.beacon.body_root, + ) +``` diff --git a/specs/capella/p2p-interface.md b/specs/capella/p2p-interface.md new file mode 100644 index 0000000000..1eae552168 --- /dev/null +++ b/specs/capella/p2p-interface.md @@ -0,0 +1,125 @@ +# Capella -- Networking + + + +- [Introduction](#introduction) +- [Modifications in Capella](#modifications-in-capella) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`bls_to_execution_change`](#bls_to_execution_change) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + + + +## 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 + +### The gossip domain: gossipsub + +A new topic is added to support the gossip of withdrawal credential change +messages. And an existing topic is upgraded for updated types in Capella. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. All existing topics +remain stable except the beacon block topic which is updated with the modified +type. + +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: + +| Name | Message Type | +| ------------------------- | ------------------------------ | +| `beacon_block` | `SignedBeaconBlock` (modified) | +| `bls_to_execution_change` | `SignedBLSToExecutionChange` | + +Note that the `ForkDigestValue` path segment of the topic separates the old and +the new `beacon_block` topics. + +##### Global topics + +Capella changes the type of the global beacon block topic and adds one global +topic to propagate withdrawal credential change messages to all potential +proposers of beacon blocks. + +###### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) +`SignedBeaconBlock` found in Capella. Specifically, this type changes with the +addition of `bls_to_execution_changes` to the inner `BeaconBlockBody`. See +Capella [state transition document](./beacon-chain.md#beaconblockbody) for +further details. + +###### `bls_to_execution_change` + +This topic is used to propagate signed bls to execution change messages to be +included in future blocks. + +The following validations MUST pass before forwarding the +`signed_bls_to_execution_change` on the network: + +- _[IGNORE]_ `current_epoch >= CAPELLA_FORK_EPOCH`, where `current_epoch` is + defined by the current wall-clock time. +- _[IGNORE]_ The `signed_bls_to_execution_change` is the first valid signed bls + to execution change received for the validator with index + `signed_bls_to_execution_change.message.validator_index`. +- _[REJECT]_ All of the conditions within `process_bls_to_execution_change` pass + validation. + +#### Transitioning the gossip + +See gossip transition details found in the +[Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics for Capella. + +### The Req/Resp domain + +#### Messages + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The Capella fork-digest is introduced to the `context` enum to specify Capella +block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +The Capella fork-digest is introduced to the `context` enum to specify Capella +block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | diff --git a/specs/capella/validator.md b/specs/capella/validator.md new file mode 100644 index 0000000000..f8d0bf9e97 --- /dev/null +++ b/specs/capella/validator.md @@ -0,0 +1,171 @@ +# Capella -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [Modified `GetPayloadResponse`](#modified-getpayloadresponse) +- [Protocols](#protocols) + - [`ExecutionEngine`](#executionengine) + - [Modified `get_payload`](#modified-get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + - [BLS to execution changes](#bls-to-execution-changes) +- [Enabling validator withdrawals](#enabling-validator-withdrawals) + - [Changing from BLS to execution withdrawal credentials](#changing-from-bls-to-execution-withdrawal-credentials) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement the Capella upgrade. + +## Prerequisites + +This document is an extension of the +[Bellatrix -- Honest Validator](../bellatrix/validator.md) guide. All behaviors +and definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated Beacon Chain doc of [Capella](./beacon-chain.md) are requisite for this +document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Helpers + +### Modified `GetPayloadResponse` + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload + block_value: uint256 +``` + +## Protocols + +### `ExecutionEngine` + +#### Modified `get_payload` + +`get_payload` returns the upgraded Capella `ExecutionPayload` type. + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. + +### Block proposal + +#### Constructing the `BeaconBlockBody` + +##### ExecutionPayload + +`ExecutionPayload`s are constructed as they were in Bellatrix, except that the +expected withdrawals for the slot must be gathered from the `state` (utilizing +the helper `get_expected_withdrawals`) and passed into the `ExecutionEngine` +within `prepare_execution_payload`. + +*Note*: In this section, `state` is the state of the slot for the block proposal +_without_ the block yet applied. That is, `state` is the `previous_state` +processed through any empty slots up to the assigned slot using +`process_slots(previous_state, slot)`. + +*Note*: The only change made to `prepare_execution_payload` is to call +`get_expected_withdrawals()` to set the new `withdrawals` field of +`PayloadAttributes`. + +```python +def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, +) -> Optional[PayloadId]: + # [Modified in Capella] Removed `is_merge_transition_complete` check in Capella + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=get_expected_withdrawals(state), # [New in Capella] + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) +``` + +##### BLS to execution changes + +Up to `MAX_BLS_TO_EXECUTION_CHANGES`, +[`BLSToExecutionChange`](./beacon-chain.md#blstoexecutionchange) objects can be +included in the `block`. The BLS to execution changes must satisfy the +verification conditions found in +[BLS to execution change processing](./beacon-chain.md#new-process_bls_to_execution_change). + +## Enabling validator withdrawals + +Validator balances are withdrawn periodically via an automatic process. For +exited validators, the full balance is withdrawn. For active validators, the +balance in excess of `MAX_EFFECTIVE_BALANCE` is withdrawn. + +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 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 + +Validators who wish to enable withdrawals **MUST** assemble, sign, and broadcast +this message so that it is accepted on the beacon chain. Validators who do not +want to enable withdrawals and have the `BLS_WITHDRAWAL_PREFIX` version of +withdrawal credentials can delay creating this message until they are ready to +enable withdrawals. + +### Changing from BLS to execution withdrawal credentials + +First, the validator must construct a valid +[`BLSToExecutionChange`](./beacon-chain.md#blstoexecutionchange) `message`. This +`message` contains the `validator_index` for the validator who wishes to change +their credentials, the `from_bls_pubkey` -- the BLS public key corresponding to +the **withdrawal BLS secret key** used to form the `BLS_WITHDRAWAL_PREFIX` +withdrawal credential, and the `to_execution_address` specifying the execution +layer address to which the validator's balances will be withdrawn. + +*Note*: The withdrawal key pair used to construct the `BLS_WITHDRAWAL_PREFIX` +withdrawal credential should be distinct from the signing key pair used to +operate the validator under typical circumstances. Consult your validator +deposit tooling documentation for further details if you are not aware of the +difference. + +*Warning*: This message can only be included on-chain once and is irreversible +so ensure the correctness and accessibility to `to_execution_address`. + +Next, the validator signs the assembled `message: BLSToExecutionChange` with the +**withdrawal BLS secret key** and this `signature` is placed into a +`SignedBLSToExecutionChange` message along with the inner `BLSToExecutionChange` +`message`. Note that the `SignedBLSToExecutionChange` message should pass all of +the validations in +[`process_bls_to_execution_change`](./beacon-chain.md#new-process_bls_to_execution_change). + +The `SignedBLSToExecutionChange` message should then be submitted to the +consensus layer network. Once included on-chain, the withdrawal credential +change takes effect. No further action is required for a validator to enter into +the automated withdrawal process. + +*Note*: A node *should* prioritize locally received `BLSToExecutionChange` +operations to ensure these changes make it on-chain through self published +blocks even if the rest of the network censors. diff --git a/specs/custody_game/validator.md b/specs/custody_game/validator.md deleted file mode 100644 index 05ceb854d7..0000000000 --- a/specs/custody_game/validator.md +++ /dev/null @@ -1,85 +0,0 @@ -# Custody Game -- Honest Validator - -**Notice**: This document is a work-in-progress for researchers and implementers. -This is an accompanying document to the [Custody Game](./), which describes the expected actions of a "validator" -participating in the shard data Custody Game. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Prerequisites](#prerequisites) -- [Becoming a validator](#becoming-a-validator) -- [Beacon chain validator assignments](#beacon-chain-validator-assignments) - - [Custody slashings](#custody-slashings) - - [Custody key reveals](#custody-key-reveals) - - [Early derived secret reveals](#early-derived-secret-reveals) - - [Construct attestation](#construct-attestation) -- [How to avoid slashing](#how-to-avoid-slashing) - - [Custody slashing](#custody-slashing) - - - - - -## Introduction - -## Prerequisites - -This document is an extension of the [Sharding -- Validator](../sharding/validator.md). All behaviors and definitions defined in the Sharding doc carry over unless explicitly noted or overridden. - -All terminology, constants, functions, and protocol mechanics defined in the [Custody Game -- The Beacon Chain](./beacon-chain.md) -docs are requisite for this document and used throughout. Please see the Custody Game docs before continuing and use them as a reference throughout. - -## Becoming a validator - -Becoming a validator in Custody Game is unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#becoming-a-validator) for details. - -## Beacon chain validator assignments - -Beacon chain validator assignments to beacon committees and beacon block proposal are unchanged from Phase 0. See the [Phase 0 validator guide](../phase0/validator.md#validator-assignments) for details. - -##### Custody slashings - -Up to `MAX_CUSTODY_SLASHINGS`, [`CustodySlashing`](./beacon-chain.md#custodyslashing) objects can be included in the `block`. The custody slashings must satisfy the verification conditions found in [custody slashings processing](beacon-chain.md#custody-slashings). The validator receives a small "whistleblower" reward for each custody slashing included (THIS IS NOT CURRENTLY THE CASE BUT PROBABLY SHOULD BE). - -##### Custody key reveals - -Up to `MAX_CUSTODY_KEY_REVEALS`, [`CustodyKeyReveal`](./beacon-chain.md#custodykeyreveal) objects can be included in the `block`. The custody key reveals must satisfy the verification conditions found in [custody key reveal processing](beacon-chain.md#custody-key-reveals). The validator receives a small reward for each custody key reveal included. - -##### Early derived secret reveals - -Up to `MAX_EARLY_DERIVED_SECRET_REVEALS`, [`EarlyDerivedSecretReveal`](./beacon-chain.md#earlyderivedsecretreveal) objects can be included in the `block`. The early derived secret reveals must satisfy the verification conditions found in [early derived secret reveal processing](beacon-chain.md#custody-key-reveals). The validator receives a small "whistleblower" reward for each early derived secrete reveal included. - -#### Construct attestation - -`attestation.data`, `attestation.aggregation_bits`, and `attestation.signature` are unchanged from Phase 0. But safety/validity in signing the message is premised upon calculation of the "custody bit" [TODO]. - - -## How to avoid slashing - -Proposer and Attester slashings described in Phase 0 remain in place with the addition of the following. - -### Custody slashing - -To avoid custody slashings, the attester must never sign any shard transition for which the custody bit is one. The custody bit is computed using the custody secret: - -```python -def get_custody_secret(state: BeaconState, - validator_index: ValidatorIndex, - privkey: int, - epoch: Epoch=None) -> BLSSignature: - if epoch is None: - epoch = get_current_epoch(state) - period = get_custody_period_for_validator(validator_index, epoch) - epoch_to_sign = get_randao_epoch_for_custody_period(period, validator_index) - domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign) - signing_root = compute_signing_root(Epoch(epoch_to_sign), domain) - return bls.Sign(privkey, signing_root) -``` - -Note that the valid custody secret is always the one for the **attestation target epoch**, not to be confused with the epoch in which the shard block was generated. -While they are the same most of the time, getting this wrong at custody epoch boundaries would result in a custody slashing. diff --git a/specs/das/p2p-interface.md b/specs/das/p2p-interface.md deleted file mode 100644 index b4a0a9d2c9..0000000000 --- a/specs/das/p2p-interface.md +++ /dev/null @@ -1,229 +0,0 @@ -# Data Availability Sampling -- Networking - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [DAS Subnets](#das-subnets) - - [Horizontal subnets](#horizontal-subnets) - - [Publishing](#publishing) - - [Horizontal propagation](#horizontal-propagation) - - [Horizontal to vertical](#horizontal-to-vertical) - - [Vertical subnets](#vertical-subnets) - - [Slow rotation: Backbone](#slow-rotation-backbone) - - [Quick Rotation: Sampling](#quick-rotation-sampling) -- [DAS in the Gossip domain: Push](#das-in-the-gossip-domain-push) - - [Topics and messages](#topics-and-messages) - - [Horizontal subnets: `shard_blob_{shard}`](#horizontal-subnets-shard_blob_shard) - - [Vertical subnets: `das_sample_{subnet_index}`](#vertical-subnets-das_sample_subnet_index) -- [DAS in the Req-Resp domain: Pull](#das-in-the-req-resp-domain-pull) - - [Messages](#messages) - - [DASQuery](#dasquery) - - - - - - -## 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. - -For sampling, all nodes need to query for `k` random samples each slot. - -*__TODO__: describe big picture of sampling workload size* - -This is a lot of work, and ideally happens at a low latency. - -To achieve quick querying, the query model is changed to *push* the samples to listeners instead, using GossipSub. -The listeners then randomly rotate their subscriptions to keep queries unpredictable. -Except for a small subset of subscriptions, which will function as a backbone to keep topics more stable and allow for efficient peer discovery. - -Publishing can utilize the fan-out functionality in GossipSub, and is easier to split between nodes: -nodes on the horizontal networks can help by producing the same samples and fan-out publishing to their own peers. - -This push model also helps to obfuscate the original source of a message: -the listeners do not have to make individual queries to some identified source. - -The push model does not aim to serve "historical" queries (anything older than the most recent). -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, -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. -- Sinks: continuously look for samples - -At full operation, the network has one proposer, per shard, per slot. - -In the push-model, there are: -- *Vertical subnets*: Sinks can subscribe to indices of samples: there is a sample to subnet mapping. -- *Horizontal subnets*: Sources need to distribute samples to all vertical networks: they participate in a fan-out layer. - -### Horizontal subnets - -The shift of the distribution responsibility to a proposer can only be achieved with amplification: -a regular proposer cannot reach every vertical subnet. - -#### Publishing - -To publish their work, proposers propagate the shard block as a whole on a shard-block subnet. - -The proposer can fan-out their work more aggressively, by using the fan-out functionality of GossipSub: -it may publish to all its peers on the subnet, instead of just those in its mesh. - -#### Horizontal propagation - -Peers on the horizontal subnet are expected to at least perform regular propagation of shard blocks, like participation in any other topic. - -*Although this may be sufficient for testnets, expect parameter changes in the spec here.* - -#### Horizontal to vertical - -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, -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, -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, -nodes can divert to the pull-model, or ultimately flag it as unavailable data. - -Note that the vertical subnets are shared between the different shards, -and a simple hash function `(shard, slot, sample_index) -> subnet_index` defines which samples go where. -This is to evenly distribute samples to subnets, even when one shard has more activity than the other. - -TODO: define `(shard, slot, sample_index) -> subnet_index` hash function. - -#### Slow rotation: Backbone - -To allow for subscriptions to rotate quickly and randomly, a backbone is formed to help onboard peers into other topics. - -This backbone is based on a pure function of the *node* identity and time: -- Nodes can be found *without additional discovery overhead*: - peers on a vertical topic can be found by searching the local peerstore for identities that hash to the desired topic(s), - assuming the peerstore already has a large enough variety of peers. -- Nodes can be held accountable for contributing to the backbone: - peers that particpate in DAS but are not active on the appropriate backbone topics can be scored down. - *Note: This is experimental, DAS should be light enough for all participants to run, but scoring needs to undergo testing* - -A node should anticipate backbone topics to subscribe to based their own identity. -These subscriptions rotate slowly, and with different offsets per node identity to avoid sudden network-wide rotations. - -```python -# TODO hash function: (node, time)->subnets -``` - -Backbone subscription work is outlined in the [DAS participation spec](sampling.md#slow-rotation-backbone) - -#### Quick Rotation: Sampling - -A node MUST maintain `k` random subscriptions to topics, and rotate these according to the [DAS participation spec](sampling.md#quick-rotation-sampling). -If the node does not already have connected peers on the topic it needs to sample, it can search its peerstore and, if necessary, in the DHT for peers in the topic backbone. - -## DAS in the Gossip domain: Push - -### Topics and messages - -Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.md#topics-and-messages), names and payload types are: -| Name | Message Type | -|----------------------------------|---------------------------| -| `das_sample_{subnet_index}` | `DASSample` | - -Also see the [Sharding general networking spec](../sharding/p2p-interface.md) for important topics such as that of the shard-blobs and shard-headers. - -#### Horizontal subnets: `shard_blob_{shard}` - -Extending the regular `shard_blob_{shard}` as [defined in the Sharding networking specification](../sharding/p2p-interface.md#shard-blobs-shard_blob_shard) - -If participating in DAS, upon receiving a `signed_blob` for the first time with a `slot` not older than `MAX_RESAMPLE_TIME`, -a subscriber of a `shard_blob_{shard}` SHOULD reconstruct the samples and publish them to vertical subnets. -Take `blob = signed_blob.blob`: -1. Extend the data: `extended_data = extend_data(blob.data)` -2. Create samples with proofs: `samples = sample_data(blob.slot, blob.shard, extended_data)` -3. Fanout-publish the samples to the vertical subnets of its peers (not all vertical subnets may be reached). - -The [DAS participation spec](sampling.md#horizontal-subnets) outlines when and where to participate in DAS on horizontal subnets. - - -#### Vertical subnets: `das_sample_{subnet_index}` - -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. -- _[IGNORE]_ The commitment for the (`sample.shard`, `sample.slot`, `sample.index`) tuple must be known. - If not known, the client MAY queue the sample if it passes formatting conditions. -- _[REJECT]_ `sample.shard`, `sample.slot` and `sample.index` are hashed into a `sbunet_index` (TODO: define hash) which MUST match the topic `{subnet_index}` parameter. -- _[REJECT]_ `sample.shard` must be within valid range: `0 <= sample.shard < get_active_shard_count(state, compute_epoch_at_slot(sample.slot))`. -- _[REJECT]_ `sample.index` must be within valid range: `0 <= sample.index < sample_count`, where: - - `sample_count = (points_count + POINTS_PER_SAMPLE - 1) // POINTS_PER_SAMPLE` - - `points_count` is the length as claimed along with the commitment, which must be smaller than `MAX_SAMPLES_PER_BLOCK`. -- _[IGNORE]_ The `sample` is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `sample.slot <= current_slot`. A client MAY queue future samples for processing at the appropriate slot if it passed formatting conditions. -- _[IGNORE]_ This is the first received sample with the (`sample.shard`, `sample.slot`, `sample.index`) key tuple. -- _[REJECT]_ As already limited by the SSZ list-limit, it is important the sample data is well-formatted and not too large. -- _[REJECT]_ The `sample.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid. -- _[REJECT]_ The `sample.proof` MUST be valid: `verify_sample(sample, sample_count, commitment)` - -Upon receiving a valid sample, it SHOULD be retained for a buffer period if the local node is part of the backbone that covers this sample. -This is to serve other peers that may have missed it. - - -## DAS in the Req-Resp domain: Pull - -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). - -Note that DAS networking uses a different protocol prefix: `/eth2/das/req` - -The result codes are extended with: -- 3: **ResourceUnavailable** -- when the request was valid but cannot be served at this point in time. - -TODO: unify with phase0? Lighthoue already defined this in their response codes enum. - -### Messages - -#### DASQuery - -**Protocol ID:** `/eth2/das/req/query/1/` - -Request Content: -``` -( - sample_index: SampleIndex -) -``` - -Response Content: -``` -( - DASSample -) -``` - -When the sample is: -- Available: respond with a `Success` result code, and the encoded sample. -- Expected to be available, but not: respond with a `ResourceUnavailable` result code. -- Not available, but never of interest to the node: respond with an `InvalidRequest` result code. - -When the node is part of the backbone and expected to have the sample, the validity of the quest MUST be recognized with `Success` or `ResourceUnavailable`. diff --git a/specs/deneb/beacon-chain.md b/specs/deneb/beacon-chain.md new file mode 100644 index 0000000000..2bc1044bf9 --- /dev/null +++ b/specs/deneb/beacon-chain.md @@ -0,0 +1,545 @@ +# Deneb -- The Beacon Chain + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) + - [Blob](#blob) +- [Preset](#preset) + - [Execution](#execution) +- [Configuration](#configuration) + - [Execution](#execution-1) + - [Validator cycle](#validator-cycle) +- [Containers](#containers) + - [Modified containers](#modified-containers) + - [`BeaconBlockBody`](#beaconblockbody) + - [`ExecutionPayload`](#executionpayload) + - [`ExecutionPayloadHeader`](#executionpayloadheader) + - [`BeaconState`](#beaconstate) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [`kzg_commitment_to_versioned_hash`](#kzg_commitment_to_versioned_hash) + - [Beacon state accessors](#beacon-state-accessors) + - [Modified `get_attestation_participation_flag_indices`](#modified-get_attestation_participation_flag_indices) + - [New `get_validator_activation_churn_limit`](#new-get_validator_activation_churn_limit) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Execution engine](#execution-engine) + - [Request data](#request-data) + - [Modified `NewPayloadRequest`](#modified-newpayloadrequest) + - [Engine APIs](#engine-apis) + - [`is_valid_block_hash`](#is_valid_block_hash) + - [`is_valid_versioned_hashes`](#is_valid_versioned_hashes) + - [Modified `notify_new_payload`](#modified-notify_new_payload) + - [Modified `verify_and_notify_new_payload`](#modified-verify_and_notify_new_payload) + - [Block processing](#block-processing) + - [Modified `process_attestation`](#modified-process_attestation) + - [Execution payload](#execution-payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) + - [Epoch processing](#epoch-processing) + - [Registry updates](#registry-updates) + + + +## Introduction + +Deneb is a consensus-layer upgrade containing a number of features. Including: + +- [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788): Beacon block root in the + EVM +- [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844): Shard Blob Transactions + scale data-availability of Ethereum in a simple, forwards-compatible manner +- [EIP-7044](https://eips.ethereum.org/EIPS/eip-7044): Perpetually Valid Signed + Voluntary Exits +- [EIP-7045](https://eips.ethereum.org/EIPS/eip-7045): Increase Max Attestation + Inclusion Slot +- [EIP-7514](https://eips.ethereum.org/EIPS/eip-7514): Add Max Epoch Churn Limit + +## Custom types + +| Name | SSZ equivalent | Description | +| --------------- | -------------- | ------------------------ | +| `VersionedHash` | `Bytes32` | *[New in Deneb:EIP4844]* | +| `BlobIndex` | `uint64` | *[New in Deneb:EIP4844]* | + +## Constants + +### Blob + +| Name | Value | +| ---------------------------- | ---------------- | +| `VERSIONED_HASH_VERSION_KZG` | `Bytes1('0x01')` | + +## Preset + +### Execution + +| Name | Value | Description | +| -------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| `MAX_BLOB_COMMITMENTS_PER_BLOCK` | `uint64(2**12)` (= 4096) | *[New in Deneb:EIP4844]* hardfork independent fixed theoretical limit same as `TARGET_BLOB_GAS_PER_BLOCK` (see EIP 4844) | + +## Configuration + +### Execution + +| Name | Value | Description | +| --------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- | +| `MAX_BLOBS_PER_BLOCK` | `uint64(6)` | *[New in Deneb:EIP4844]* maximum number of blobs in a single block limited by `MAX_BLOB_COMMITMENTS_PER_BLOCK` | + +*Note*: The blob transactions are packed into the execution payload by the +EL/builder with their corresponding blobs being independently transmitted and +are limited by `MAX_BLOB_GAS_PER_BLOCK // GAS_PER_BLOB`. However the CL limit is +independently defined by `MAX_BLOBS_PER_BLOCK`. + +### Validator cycle + +| Name | Value | +| -------------------------------------- | -------------------- | +| `MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT` | `uint64(2**3)` (= 8) | + +## Containers + +### Modified containers + +#### `BeaconBlockBody` + +*Note*: `BeaconBlock` and `SignedBeaconBlock` types are updated indirectly. + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] + attestations: List[Attestation, MAX_ATTESTATIONS] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + # [Modified in Deneb:EIP4844] + execution_payload: ExecutionPayload + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + # [New in Deneb:EIP4844] + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] +``` + +#### `ExecutionPayload` + +```python +class ExecutionPayload(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + # [New in Deneb:EIP4844] + blob_gas_used: uint64 + # [New in Deneb:EIP4844] + excess_blob_gas: uint64 +``` + +#### `ExecutionPayloadHeader` + +```python +class ExecutionPayloadHeader(Container): + parent_hash: Hash32 + fee_recipient: ExecutionAddress + state_root: Bytes32 + receipts_root: Bytes32 + logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] + prev_randao: Bytes32 + block_number: uint64 + gas_limit: uint64 + gas_used: uint64 + timestamp: uint64 + extra_data: ByteList[MAX_EXTRA_DATA_BYTES] + base_fee_per_gas: uint256 + block_hash: Hash32 + transactions_root: Root + withdrawals_root: Root + # [New in Deneb:EIP4844] + blob_gas_used: uint64 + # [New in Deneb:EIP4844] + excess_blob_gas: uint64 +``` + +#### `BeaconState` + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # [Modified in Deneb:EIP4844] + latest_execution_payload_header: ExecutionPayloadHeader + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] +``` + +## Helper functions + +### Misc + +#### `kzg_commitment_to_versioned_hash` + +```python +def kzg_commitment_to_versioned_hash(kzg_commitment: KZGCommitment) -> VersionedHash: + return VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:] +``` + +### Beacon state accessors + +#### Modified `get_attestation_participation_flag_indices` + +*Note*: The function `get_attestation_participation_flag_indices` is modified to +set the `TIMELY_TARGET_FLAG` for any correct target attestation, regardless of +`inclusion_delay` as a baseline reward for any speed of inclusion of an +attestation that contributes to justification of the contained chain for +EIP-7045. + +```python +def get_attestation_participation_flag_indices( + state: BeaconState, data: AttestationData, inclusion_delay: uint64 +) -> Sequence[int]: + """ + Return the flag indices that are satisfied by an attestation. + """ + if data.target.epoch == get_current_epoch(state): + justified_checkpoint = state.current_justified_checkpoint + else: + justified_checkpoint = state.previous_justified_checkpoint + + # Matching roots + is_matching_source = data.source == justified_checkpoint + is_matching_target = is_matching_source and data.target.root == get_block_root( + state, data.target.epoch + ) + is_matching_head = is_matching_target and data.beacon_block_root == get_block_root_at_slot( + state, data.slot + ) + assert is_matching_source + + participation_flag_indices = [] + if is_matching_source and inclusion_delay <= integer_squareroot(SLOTS_PER_EPOCH): + participation_flag_indices.append(TIMELY_SOURCE_FLAG_INDEX) + if is_matching_target: # [Modified in Deneb:EIP7045] + participation_flag_indices.append(TIMELY_TARGET_FLAG_INDEX) + if is_matching_head and inclusion_delay == MIN_ATTESTATION_INCLUSION_DELAY: + participation_flag_indices.append(TIMELY_HEAD_FLAG_INDEX) + + return participation_flag_indices +``` + +#### New `get_validator_activation_churn_limit` + +```python +def get_validator_activation_churn_limit(state: BeaconState) -> uint64: + """ + Return the validator activation churn limit for the current epoch. + """ + return min(MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT, get_validator_churn_limit(state)) +``` + +## Beacon chain state transition function + +### Execution engine + +#### Request data + +##### Modified `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload + versioned_hashes: Sequence[VersionedHash] + parent_beacon_block_root: Root +``` + +#### Engine APIs + +##### `is_valid_block_hash` + +*Note*: The function `is_valid_block_hash` is modified to include the additional +`parent_beacon_block_root` parameter for EIP-4788. + +```python +def is_valid_block_hash( + self: ExecutionEngine, execution_payload: ExecutionPayload, parent_beacon_block_root: Root +) -> bool: + """ + Return ``True`` if and only if ``execution_payload.block_hash`` is computed correctly. + """ + ... +``` + +##### `is_valid_versioned_hashes` + +```python +def is_valid_versioned_hashes( + self: ExecutionEngine, new_payload_request: NewPayloadRequest +) -> bool: + """ + Return ``True`` if and only if the version hashes computed by the blob transactions of + ``new_payload_request.execution_payload`` matches ``new_payload_request.versioned_hashes``. + """ + ... +``` + +##### Modified `notify_new_payload` + +*Note*: The function `notify_new_payload` is modified to include the additional +`parent_beacon_block_root` parameter for EIP-4788. + +```python +def notify_new_payload( + self: ExecutionEngine, execution_payload: ExecutionPayload, parent_beacon_block_root: Root +) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` is valid with respect to ``self.execution_state``. + """ + ... +``` + +##### Modified `verify_and_notify_new_payload` + +```python +def verify_and_notify_new_payload( + self: ExecutionEngine, new_payload_request: NewPayloadRequest +) -> bool: + """ + Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``. + """ + execution_payload = new_payload_request.execution_payload + # [New in Deneb:EIP4788] + parent_beacon_block_root = new_payload_request.parent_beacon_block_root + + if b"" in execution_payload.transactions: + return False + + # [Modified in Deneb:EIP4788] + if not self.is_valid_block_hash(execution_payload, parent_beacon_block_root): + return False + + # [New in Deneb:EIP4844] + if not self.is_valid_versioned_hashes(new_payload_request): + return False + + # [Modified in Deneb:EIP4788] + if not self.notify_new_payload(execution_payload, parent_beacon_block_root): + return False + + return True +``` + +### Block processing + +#### Modified `process_attestation` + +*Note*: The function `process_attestation` is modified to expand valid slots for +inclusion to those in both `target.epoch` epoch and `target.epoch + 1` epoch for +EIP-7045. Additionally, it utilizes an updated version of +`get_attestation_participation_flag_indices` to ensure rewards are available for +the extended attestation inclusion range for EIP-7045. + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot # [Modified in Deneb:EIP7045] + assert data.index < get_committee_count_per_slot(state, data.target.epoch) + + committee = get_beacon_committee(state, data.slot, data.index) + assert len(attestation.aggregation_bits) == len(committee) + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices( + state, data, state.slot - data.slot + ) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, attestation): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag( + epoch_participation[index], flag_index + ): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = ( + (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + ) + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +``` + +#### Execution payload + +##### Modified `process_execution_payload` + +*Note*: The function `process_execution_payload` is modified to pass +`versioned_hashes` into `execution_engine.verify_and_notify_new_payload` and to +assign the new fields in `ExecutionPayloadHeader` for EIP-4844. It is also +modified to pass in the parent beacon block root to support EIP-4788. + +```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_time_at_slot(state, state.slot) + + # [New in Deneb:EIP4844] Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK + + # Verify the execution payload is valid + # [Modified in Deneb:EIP4844] Pass `versioned_hashes` to Execution Engine + # [Modified in Deneb:EIP4788] Pass `parent_beacon_block_root` to Execution Engine + 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, + ) + ) + + # 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, # [New in Deneb:EIP4844] + excess_blob_gas=payload.excess_blob_gas, # [New in Deneb:EIP4844] + ) +``` + +#### Modified `process_voluntary_exit` + +*Note*: The function `process_voluntary_exit` is modified to use the fixed fork +version -- `CAPELLA_FORK_VERSION` -- for EIP-7044. + +```python +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD + # Verify signature + # [Modified in Deneb:EIP7044] + domain = compute_domain( + DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root + ) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) +``` + +### Epoch processing + +#### Registry updates + +*Note*: The function `process_registry_updates` is modified to utilize +`get_validator_activation_churn_limit()` to rate limit the activation queue for +EIP-7514. + +```python +def process_registry_updates(state: BeaconState) -> None: + # Process activation eligibility and ejections + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): + validator.activation_eligibility_epoch = get_current_epoch(state) + 1 + + if ( + is_active_validator(validator, get_current_epoch(state)) + and validator.effective_balance <= EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) + + # Queue validators eligible for activation and not yet dequeued for activation + activation_queue = sorted( + [ + index + for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + ], + # Order by the sequence of activation_eligibility_epoch setting and then index + key=lambda index: (state.validators[index].activation_eligibility_epoch, index), + ) + # Dequeued validators for activation up to activation churn limit + # [Modified in Deneb:EIP7514] + for index in activation_queue[: get_validator_activation_churn_limit(state)]: + validator = state.validators[index] + validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) +``` diff --git a/specs/deneb/fork-choice.md b/specs/deneb/fork-choice.md new file mode 100644 index 0000000000..10a163ff9c --- /dev/null +++ b/specs/deneb/fork-choice.md @@ -0,0 +1,131 @@ +# Deneb -- Fork Choice + + + +- [Introduction](#introduction) +- [Containers](#containers) +- [Helpers](#helpers) + - [Modified `PayloadAttributes`](#modified-payloadattributes) + - [`is_data_available`](#is_data_available) +- [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [`on_block`](#on_block) + + + +## Introduction + +This is the modification of the fork choice accompanying the Deneb upgrade. + +## Containers + +## Helpers + +### Modified `PayloadAttributes` + +`PayloadAttributes` is extended with the parent beacon block root for EIP-4788. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress + withdrawals: Sequence[Withdrawal] + parent_beacon_block_root: Root # [New in Deneb:EIP4788] +``` + +### `is_data_available` + +*[New in Deneb:EIP4844]* + +The implementation of `is_data_available` will become more sophisticated during +later scaling upgrades. Initially, verification requires every verifying actor +to retrieve all matching `Blob`s and `KZGProof`s, and validate them with +`verify_blob_kzg_proof_batch`. + +The block MUST NOT be considered valid until all valid `Blob`s have been +downloaded. Blocks that have been previously validated as available SHOULD be +considered available even if the associated `Blob`s have subsequently been +pruned. + +*Note*: Extraneous or invalid Blobs (in addition to KZG expected/referenced +valid blobs) received on the p2p network MUST NOT invalidate a block that is +otherwise valid and available. + +```python +def is_data_available( + beacon_block_root: Root, blob_kzg_commitments: Sequence[KZGCommitment] +) -> bool: + # `retrieve_blobs_and_proofs` is implementation and context dependent + # It returns all the blobs for the given block root, and raises an exception if not available + # Note: the p2p network does not guarantee sidecar retrieval outside of + # `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` + blobs, proofs = retrieve_blobs_and_proofs(beacon_block_root) + + return verify_blob_kzg_proof_batch(blobs, blob_kzg_commitments, proofs) +``` + +## Updated fork-choice handlers + +### `on_block` + +*Note*: The only modification is the addition of the blob data availability +check. + +```python +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block + + # [New in Deneb:EIP4844] + # Check if blob data is available + # If not, this block MAY be queued and subsequently considered when blob data becomes available + # *Note*: Extraneous or invalid Blobs (in addition to the expected/referenced valid blobs) + # received on the p2p network MUST NOT invalidate a block that is otherwise valid and available + assert is_data_available(hash_tree_root(block), block.body.blob_kzg_commitments) + + # Check the block is valid and compute the post-state + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[block.parent_root]) + block_root = hash_tree_root(block) + state_transition(state, signed_block, True) + + # Add new block to the store + store.blocks[block_root] = block + # Add new state for this block to the store + store.block_states[block_root] = state + + # Add block timeliness to the store + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely + + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) + + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) + + # Eagerly compute unrealized justification and finality. + compute_pulled_up_tip(store, block_root) +``` diff --git a/specs/deneb/fork.md b/specs/deneb/fork.md new file mode 100644 index 0000000000..0ac100380c --- /dev/null +++ b/specs/deneb/fork.md @@ -0,0 +1,124 @@ +# Deneb -- Fork Logic + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to Deneb](#fork-to-deneb) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of Deneb upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| -------------------- | ------------------------------------------------ | +| `DENEB_FORK_VERSION` | `Version('0x04000000')` | +| `DENEB_FORK_EPOCH` | `Epoch(269568)` (March 13, 2024, 01:55:35pm UTC) | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to Deneb + +### Fork trigger + +The fork is triggered at epoch `DENEB_FORK_EPOCH`. + +Note that for the pure Deneb networks, we don't apply `upgrade_to_deneb` since +it starts with Deneb version logic. + +### Upgrading the state + +```python +def upgrade_to_deneb(pre: capella.BeaconState) -> BeaconState: + epoch = capella.get_current_epoch(pre) + latest_execution_payload_header = ExecutionPayloadHeader( + parent_hash=pre.latest_execution_payload_header.parent_hash, + fee_recipient=pre.latest_execution_payload_header.fee_recipient, + state_root=pre.latest_execution_payload_header.state_root, + receipts_root=pre.latest_execution_payload_header.receipts_root, + logs_bloom=pre.latest_execution_payload_header.logs_bloom, + prev_randao=pre.latest_execution_payload_header.prev_randao, + block_number=pre.latest_execution_payload_header.block_number, + gas_limit=pre.latest_execution_payload_header.gas_limit, + gas_used=pre.latest_execution_payload_header.gas_used, + timestamp=pre.latest_execution_payload_header.timestamp, + extra_data=pre.latest_execution_payload_header.extra_data, + base_fee_per_gas=pre.latest_execution_payload_header.base_fee_per_gas, + block_hash=pre.latest_execution_payload_header.block_hash, + transactions_root=pre.latest_execution_payload_header.transactions_root, + withdrawals_root=pre.latest_execution_payload_header.withdrawals_root, + # [New in Deneb:EIP4844] + blob_gas_used=uint64(0), + # [New in Deneb:EIP4844] + excess_blob_gas=uint64(0), + ) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in Deneb] + current_version=DENEB_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # [Modified in Deneb:EIP4844] + latest_execution_payload_header=latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + ) + + return post +``` diff --git a/specs/deneb/light-client/fork.md b/specs/deneb/light-client/fork.md new file mode 100644 index 0000000000..03edec6cbc --- /dev/null +++ b/specs/deneb/light-client/fork.md @@ -0,0 +1,119 @@ +# Deneb Light Client -- Fork Logic + + + +- [Introduction](#introduction) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) + + + +## Introduction + +This document describes how to upgrade existing light client objects based on +the [Capella specification](../../capella/light-client/sync-protocol.md) to +Deneb. This is necessary when processing pre-Deneb data with a post-Deneb +`LightClientStore`. Note that the data being exchanged over the network +protocols uses the original format. + +## Upgrading light client data + +A Deneb `LightClientStore` can still process earlier light client data. In order +to do so, that pre-Deneb data needs to be locally upgraded to Deneb before +processing. + +```python +def upgrade_lc_header_to_deneb(pre: capella.LightClientHeader) -> LightClientHeader: + return LightClientHeader( + beacon=pre.beacon, + execution=ExecutionPayloadHeader( + parent_hash=pre.execution.parent_hash, + fee_recipient=pre.execution.fee_recipient, + state_root=pre.execution.state_root, + receipts_root=pre.execution.receipts_root, + logs_bloom=pre.execution.logs_bloom, + prev_randao=pre.execution.prev_randao, + block_number=pre.execution.block_number, + gas_limit=pre.execution.gas_limit, + gas_used=pre.execution.gas_used, + timestamp=pre.execution.timestamp, + extra_data=pre.execution.extra_data, + base_fee_per_gas=pre.execution.base_fee_per_gas, + block_hash=pre.execution.block_hash, + transactions_root=pre.execution.transactions_root, + withdrawals_root=pre.execution.withdrawals_root, + blob_gas_used=uint64(0), # [New in Deneb:EIP4844] + excess_blob_gas=uint64(0), # [New in Deneb:EIP4844] + ), + execution_branch=pre.execution_branch, + ) +``` + +```python +def upgrade_lc_bootstrap_to_deneb(pre: capella.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_deneb(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=pre.current_sync_committee_branch, + ) +``` + +```python +def upgrade_lc_update_to_deneb(pre: capella.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_deneb(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=pre.next_sync_committee_branch, + finalized_header=upgrade_lc_header_to_deneb(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_deneb( + pre: capella.LightClientFinalityUpdate, +) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_deneb(pre.attested_header), + finalized_header=upgrade_lc_header_to_deneb(pre.finalized_header), + finality_branch=pre.finality_branch, + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_deneb( + pre: capella.LightClientOptimisticUpdate, +) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_deneb(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +## Upgrading the store + +Existing `LightClientStore` objects based on Capella MUST be upgraded to Deneb +before Deneb based light client data can be processed. The `LightClientStore` +upgrade MAY be performed before `DENEB_FORK_EPOCH`. + +```python +def upgrade_lc_store_to_deneb(pre: capella.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_deneb(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_deneb(pre.finalized_header), + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + best_valid_update=best_valid_update, + optimistic_header=upgrade_lc_header_to_deneb(pre.optimistic_header), + previous_max_active_participants=pre.previous_max_active_participants, + current_max_active_participants=pre.current_max_active_participants, + ) +``` diff --git a/specs/deneb/light-client/full-node.md b/specs/deneb/light-client/full-node.md new file mode 100644 index 0000000000..a677bfc061 --- /dev/null +++ b/specs/deneb/light-client/full-node.md @@ -0,0 +1,70 @@ +# Deneb Light Client -- Full Node + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [Modified `block_to_light_client_header`](#modified-block_to_light_client_header) + + + +## Introduction + +Execution payload data is updated to account for the Deneb upgrade. + +## Helper functions + +### Modified `block_to_light_client_header` + +```python +def block_to_light_client_header(block: SignedBeaconBlock) -> LightClientHeader: + epoch = compute_epoch_at_slot(block.message.slot) + + if epoch >= CAPELLA_FORK_EPOCH: + payload = block.message.body.execution_payload + execution_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), + ) + + # [New in Deneb:EIP4844] + if epoch >= DENEB_FORK_EPOCH: + execution_header.blob_gas_used = payload.blob_gas_used + execution_header.excess_blob_gas = payload.excess_blob_gas + + execution_branch = ExecutionBranch( + compute_merkle_proof(block.message.body, EXECUTION_PAYLOAD_GINDEX) + ) + else: + # Note that during fork transitions, `finalized_header` may still point to earlier forks. + # While Bellatrix blocks also contain an `ExecutionPayload` (minus `withdrawals_root`), + # it was not included in the corresponding light client data. To ensure compatibility + # with legacy data going through `upgrade_lc_header_to_capella`, leave out execution data. + execution_header = ExecutionPayloadHeader() + execution_branch = ExecutionBranch() + + return LightClientHeader( + beacon=BeaconBlockHeader( + slot=block.message.slot, + proposer_index=block.message.proposer_index, + parent_root=block.message.parent_root, + state_root=block.message.state_root, + body_root=hash_tree_root(block.message.body), + ), + execution=execution_header, + execution_branch=execution_branch, + ) +``` diff --git a/specs/deneb/light-client/p2p-interface.md b/specs/deneb/light-client/p2p-interface.md new file mode 100644 index 0000000000..89c7fb6d66 --- /dev/null +++ b/specs/deneb/light-client/p2p-interface.md @@ -0,0 +1,100 @@ +# Deneb Light Client -- Networking + + + +- [Networking](#networking) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`light_client_finality_update`](#light_client_finality_update) + - [`light_client_optimistic_update`](#light_client_optimistic_update) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [GetLightClientBootstrap](#getlightclientbootstrap) + - [LightClientUpdatesByRange](#lightclientupdatesbyrange) + - [GetLightClientFinalityUpdate](#getlightclientfinalityupdate) + - [GetLightClientOptimisticUpdate](#getlightclientoptimisticupdate) + + + +## Networking + +The +[Capella light client networking specification](../../capella/light-client/p2p-interface.md) +is extended to exchange [Deneb light client data](./sync-protocol.md). + +### The gossip domain: gossipsub + +#### Topics and messages + +##### Global topics + +###### `light_client_finality_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientFinalityUpdate` | + +###### `light_client_optimistic_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientOptimisticUpdate` | + +### The Req/Resp domain + +#### Messages + +##### GetLightClientBootstrap + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientBootstrap` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientBootstrap` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientBootstrap` | + +##### LightClientUpdatesByRange + + + +| `fork_version` | Response chunk SSZ type | +| ------------------------------------------------------ | --------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientUpdate` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientUpdate` | + +##### GetLightClientFinalityUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientFinalityUpdate` | + +##### GetLightClientOptimisticUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` and later | `deneb.LightClientOptimisticUpdate` | diff --git a/specs/deneb/light-client/sync-protocol.md b/specs/deneb/light-client/sync-protocol.md new file mode 100644 index 0000000000..e5e3542d71 --- /dev/null +++ b/specs/deneb/light-client/sync-protocol.md @@ -0,0 +1,87 @@ +# Deneb Light Client -- Sync Protocol + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [Modified `get_lc_execution_root`](#modified-get_lc_execution_root) + - [Modified `is_valid_light_client_header`](#modified-is_valid_light_client_header) + + + +## Introduction + +This upgrade updates light client data to include the Deneb changes to the +[`ExecutionPayload`](../beacon-chain.md) structure. It extends the +[Capella Light Client specifications](../../capella/light-client/sync-protocol.md). +The [fork document](./fork.md) explains how to upgrade existing Capella based +deployments to Deneb. + +Additional documents describes the impact of the upgrade on certain roles: + +- [Full node](./full-node.md) +- [Networking](./p2p-interface.md) + +## Helper functions + +### Modified `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + epoch = compute_epoch_at_slot(header.beacon.slot) + + # [New in Deneb] + if epoch >= DENEB_FORK_EPOCH: + return hash_tree_root(header.execution) + + # [Modified in Deneb] + if epoch >= CAPELLA_FORK_EPOCH: + execution_header = capella.ExecutionPayloadHeader( + parent_hash=header.execution.parent_hash, + fee_recipient=header.execution.fee_recipient, + state_root=header.execution.state_root, + receipts_root=header.execution.receipts_root, + logs_bloom=header.execution.logs_bloom, + prev_randao=header.execution.prev_randao, + block_number=header.execution.block_number, + gas_limit=header.execution.gas_limit, + gas_used=header.execution.gas_used, + timestamp=header.execution.timestamp, + extra_data=header.execution.extra_data, + base_fee_per_gas=header.execution.base_fee_per_gas, + block_hash=header.execution.block_hash, + transactions_root=header.execution.transactions_root, + withdrawals_root=header.execution.withdrawals_root, + ) + return hash_tree_root(execution_header) + + return Root() +``` + +### Modified `is_valid_light_client_header` + +```python +def is_valid_light_client_header(header: LightClientHeader) -> bool: + epoch = compute_epoch_at_slot(header.beacon.slot) + + # [New in Deneb:EIP4844] + if epoch < DENEB_FORK_EPOCH: + if header.execution.blob_gas_used != uint64(0): + return False + if header.execution.excess_blob_gas != uint64(0): + return False + + if epoch < CAPELLA_FORK_EPOCH: + return ( + header.execution == ExecutionPayloadHeader() + and header.execution_branch == ExecutionBranch() + ) + + return is_valid_merkle_branch( + leaf=get_lc_execution_root(header), + branch=header.execution_branch, + depth=floorlog2(EXECUTION_PAYLOAD_GINDEX), + index=get_subtree_index(EXECUTION_PAYLOAD_GINDEX), + root=header.beacon.body_root, + ) +``` diff --git a/specs/deneb/p2p-interface.md b/specs/deneb/p2p-interface.md new file mode 100644 index 0000000000..a0c5d68da4 --- /dev/null +++ b/specs/deneb/p2p-interface.md @@ -0,0 +1,513 @@ +# Deneb -- Networking + + + +- [Introduction](#introduction) +- [Modifications in Deneb](#modifications-in-deneb) + - [Constant](#constant) + - [Preset](#preset) + - [Configuration](#configuration) + - [Containers](#containers) + - [`BlobSidecar`](#blobsidecar) + - [`BlobIdentifier`](#blobidentifier) + - [Helpers](#helpers) + - [`verify_blob_sidecar_inclusion_proof`](#verify_blob_sidecar_inclusion_proof) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) + - [Blob subnets](#blob-subnets) + - [`blob_sidecar_{subnet_id}`](#blob_sidecar_subnet_id) + - [Blob retrieval via local execution layer client](#blob-retrieval-via-local-execution-layer-client) + - [Attestation subnets](#attestation-subnets) + - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) + - [Transitioning the gossip](#transitioning-the-gossip) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [BlobSidecarsByRange v1](#blobsidecarsbyrange-v1) + - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1) +- [Design decision rationale](#design-decision-rationale) + - [Why are blobs relayed as a sidecar, separate from beacon blocks?](#why-are-blobs-relayed-as-a-sidecar-separate-from-beacon-blocks) + + + +## 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 + +*[New in Deneb:EIP4844]* + +### Preset + +*[New in Deneb:EIP4844]* + +| Name | Value | Description | +| -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `KZG_COMMITMENT_INCLUSION_PROOF_DEPTH` | `uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) + 1 + ceillog2(MAX_BLOB_COMMITMENTS_PER_BLOCK))` (= 17) | Merkle proof depth for `blob_kzg_commitments` list item | + +### Configuration + +*[New in Deneb:EIP4844]* + +| Name | Value | Description | +| --------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------ | +| `MAX_REQUEST_BLOCKS_DENEB` | `2**7` (= 128) | Maximum number of blocks in a single request | +| `MAX_REQUEST_BLOB_SIDECARS` | `MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK` | Maximum number of blob sidecars in a single request | +| `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` | `2**12` (= 4096 epochs, ~18 days) | The minimum epoch range over which a node must serve blob sidecars | +| `BLOB_SIDECAR_SUBNET_COUNT` | `6` | The number of blob sidecar subnets used in the gossipsub protocol. | + +### Containers + +#### `BlobSidecar` + +*[New in Deneb:EIP4844]* + +*Note*: `index` is the index of the blob in the block. + +```python +class BlobSidecar(Container): + index: BlobIndex + blob: Blob + kzg_commitment: KZGCommitment + kzg_proof: KZGProof + signed_block_header: SignedBeaconBlockHeader + kzg_commitment_inclusion_proof: Vector[Bytes32, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH] +``` + +#### `BlobIdentifier` + +*[New in Deneb:EIP4844]* + +```python +class BlobIdentifier(Container): + block_root: Root + index: BlobIndex +``` + +#### Helpers + +##### `verify_blob_sidecar_inclusion_proof` + +```python +def verify_blob_sidecar_inclusion_proof(blob_sidecar: BlobSidecar) -> bool: + gindex = get_subtree_index( + get_generalized_index(BeaconBlockBody, "blob_kzg_commitments", blob_sidecar.index) + ) + return is_valid_merkle_branch( + leaf=blob_sidecar.kzg_commitment.hash_tree_root(), + branch=blob_sidecar.kzg_commitment_inclusion_proof, + depth=KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, + index=gindex, + root=blob_sidecar.signed_block_header.message.body_root, + ) +``` + +### The gossip domain: gossipsub + +Some gossip meshes are upgraded in the fork of Deneb to support upgraded types. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. + +The `beacon_block` topic is modified to also support Deneb blocks and new topics +are added per table below. + +The `voluntary_exit` topic is implicitly modified despite the lock-in use of +`CAPELLA_FORK_VERSION` for this message signature validation for EIP-7044. + +The `beacon_aggregate_and_proof` and `beacon_attestation_{subnet_id}` topics are +modified to support the gossip of attestations created in epoch `N` to be +gossiped through the entire range of slots in epoch `N+1` rather than only +through one epoch of slots for EIP-7045. + +The specification around the creation, validation, and dissemination of messages +has not changed from the Capella document unless explicitly noted here. + +The derivation of the `message-id` remains stable. + +The new topics along with the type of the `data` field of a gossipsub message +are given in this table: + +| Name | Message Type | +| -------------------------- | ------------------------------------ | +| `blob_sidecar_{subnet_id}` | `BlobSidecar` [New in Deneb:EIP4844] | + +##### Global topics + +###### `beacon_block` + +The *type* of the payload of this topic changes to the (modified) +`SignedBeaconBlock` found in Deneb. + +*[Modified in Deneb:EIP4844]* + +New validation: + +- _[REJECT]_ The length of KZG commitments is less than or equal to the + limitation defined in Consensus Layer -- i.e. validate that + `len(signed_beacon_block.message.body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK` + +###### `beacon_aggregate_and_proof` + +*[Modified in Deneb:EIP7045]* + +The following validation is removed: + +- _[IGNORE]_ `aggregate.data.slot` is within the last + `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` + (a client MAY queue future aggregates for processing at the appropriate slot). + +The following validations are added in its place: + +- _[IGNORE]_ `aggregate.data.slot` is equal to or earlier than the + `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `aggregate.data.slot <= current_slot` (a client MAY queue future aggregates + for processing at the appropriate slot). +- _[IGNORE]_ the epoch of `aggregate.data.slot` is either the current or + previous epoch (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `compute_epoch_at_slot(aggregate.data.slot) in (get_previous_epoch(state), get_current_epoch(state))` + +##### Blob subnets + +###### `blob_sidecar_{subnet_id}` + +*[New in Deneb:EIP4844]* + +This topic is used to propagate blob sidecars, where each blob index maps to +some `subnet_id`. + +The following validations MUST pass before forwarding the `blob_sidecar` on the +network, assuming the alias +`block_header = blob_sidecar.signed_block_header.message`: + +- _[REJECT]_ The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- + i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`. +- _[REJECT]_ The sidecar is for the correct subnet -- i.e. + `compute_subnet_for_blob_sidecar(blob_sidecar.index) == subnet_id`. +- _[IGNORE]_ The sidecar is not from a future slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `block_header.slot <= current_slot` (a client MAY queue future sidecars for + processing at the appropriate slot). +- _[IGNORE]_ The sidecar is from a slot greater than the latest finalized slot + -- i.e. validate that + `block_header.slot > compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)` +- _[REJECT]_ The proposer signature of `blob_sidecar.signed_block_header`, is + valid with respect to the `block_header.proposer_index` pubkey. +- _[IGNORE]_ The sidecar's block's parent (defined by + `block_header.parent_root`) has been seen (via gossip or non-gossip sources) + (a client MAY queue sidecars for processing once the parent block is + retrieved). +- _[REJECT]_ The sidecar's block's parent (defined by + `block_header.parent_root`) passes validation. +- _[REJECT]_ The sidecar is from a higher slot than the sidecar's block's parent + (defined by `block_header.parent_root`). +- _[REJECT]_ The current finalized_checkpoint is an ancestor of the sidecar's + block -- i.e. + `get_checkpoint_block(store, block_header.parent_root, store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root`. +- _[REJECT]_ The sidecar's inclusion proof is valid as verified by + `verify_blob_sidecar_inclusion_proof(blob_sidecar)`. +- _[REJECT]_ The sidecar's blob is valid as verified by + `verify_blob_kzg_proof(blob_sidecar.blob, blob_sidecar.kzg_commitment, blob_sidecar.kzg_proof)`. +- _[IGNORE]_ The sidecar is the first sidecar for the tuple + `(block_header.slot, block_header.proposer_index, blob_sidecar.index)` with + valid header signature, sidecar inclusion proof, and kzg proof. +- _[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)`: + + + +| `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`). Honest nodes SHOULD +query `engine_getBlobsV1` as soon as they receive a valid gossip block that +contains data, and import the returned blobs. + +When clients use the local execution layer to retrieve blobs, they MUST behave +as if the corresponding `blob_sidecar` had been received via gossip. In +particular they MUST: + +- Publish the corresponding `blob_sidecar` on the `blob_sidecar_{subnet_id}` + subnet. +- Update gossip rule related data structures (i.e. update the anti-equivocation + cache). + +##### Attestation subnets + +###### `beacon_attestation_{subnet_id}` + +*[Modified in Deneb:EIP7045]* + +The following validation is removed: + +- _[IGNORE]_ `attestation.data.slot` is within the last + `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` + (a client MAY queue future attestations for processing at the appropriate + slot). + +The following validations are added in its place: + +- _[IGNORE]_ `attestation.data.slot` is equal to or earlier than the + `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `attestation.data.slot <= current_slot` (a client MAY queue future attestation + for processing at the appropriate slot). +- _[IGNORE]_ the epoch of `attestation.data.slot` is either the current or + previous epoch (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `compute_epoch_at_slot(attestation.data.slot) in (get_previous_epoch(state), get_current_epoch(state))` + +#### Transitioning the gossip + +See gossip transition details found in the +[Altair document](../altair/p2p-interface.md#transitioning-the-gossip) for +details on how to handle transitioning gossip topics for this upgrade. + +### The Req/Resp domain + +#### Messages + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The Deneb fork-digest is introduced to the `context` enum to specify Deneb +beacon block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | + +No more than `MAX_REQUEST_BLOCKS_DENEB` may be requested at a time. + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | + +No more than `MAX_REQUEST_BLOCKS_DENEB` may be requested at a time. + +*[Modified in Deneb:EIP4844]* Clients SHOULD include a block in the response as +soon as it passes the gossip validation rules. Clients SHOULD NOT respond with +blocks that fail the beacon chain state transition. + +##### BlobSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/1/` + +*[New in Deneb:EIP4844]* + +Request Content: + +``` +( + start_slot: Slot + count: uint64 +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS] +) +``` + +Requests blob sidecars in the slot range `[start_slot, start_slot + count)`, +leading up to the current head block as selected by fork choice. + +Before consuming the next response chunk, the response reader SHOULD verify the +blob sidecar is well-formatted, has valid inclusion proof, and is correct w.r.t. +the expected KZG commitments through `verify_blob_kzg_proof`. + +`BlobSidecarsByRange` is primarily used to sync blobs that may have been missed +on gossip and to sync within the `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` window. + +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 `BlobSidecar` payload. + +Let `blob_serve_range` be +`[max(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH), current_epoch]`. +Clients MUST keep a record of blob sidecars seen on the epoch range +`blob_serve_range` where `current_epoch` is defined by the current wall-clock +time, and clients MUST support serving requests of blobs on this range. + +Peers that are unable to reply to blob sidecar requests within the range +`blob_serve_range` SHOULD respond with error code `3: ResourceUnavailable`. Such +peers that are unable to successfully reply to this range of requests MAY get +descored or disconnected at any time. + +*Note*: The above requirement implies that nodes that start from a recent weak +subjectivity checkpoint MUST backfill the local blobs database to at least the +range `blob_serve_range` to be fully compliant with `BlobSidecarsByRange` +requests. + +*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can +begin participating in the networking immediately, other peers MAY disconnect +and/or temporarily ban such an un-synced or semi-synced client. + +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` sidecars. + +Clients MUST include all blob sidecars of each block from which they include +blob sidecars. + +The following blob sidecars, where they exist, MUST be sent in consecutive +`(slot, index)` order. + +Slots that do not contain known blobs MUST be skipped, mimicking the behaviour +of the `BlocksByRange` request. Only response chunks with known blobs should +therefore be sent. + +Clients MAY limit the number of blob sidecars in the response. + +The response MUST contain no more than `count * MAX_BLOBS_PER_BLOCK` blob +sidecars. + +Clients MUST respond with blob sidecars from their view of the current fork +choice -- that is, blob sidecars as included by blocks from the single chain +defined by the current head. Of note, blocks from slots before the finalization +MUST lead to the finalized block reported in the `Status` handshake. + +Clients MUST respond with blob sidecars that are consistent from a single chain +within the context of the request. + +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)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------------ | ------------------- | +| `DENEB_FORK_VERSION` and later | `deneb.BlobSidecar` | + +##### BlobSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/` + +*[New in Deneb:EIP4844]* + +Request Content: + +``` +( + List[BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS] +) +``` + +Response Content: + +``` +( + List[BlobSidecar, MAX_REQUEST_BLOB_SIDECARS] +) +``` + +Requests sidecars by block root and index. The response is a list of +`BlobSidecar` whose length is less than or equal to the number of requests. It +may be less in the case that the responding peer is missing blocks or sidecars. + +Before consuming the next response chunk, the response reader SHOULD verify the +blob sidecar is well-formatted, has valid inclusion proof, and is correct w.r.t. +the expected KZG commitments through `verify_blob_kzg_proof`. + +No more than `MAX_REQUEST_BLOB_SIDECARS` may be requested at a time. + +`BlobSidecarsByRoot` is primarily used to recover recent blobs (e.g. when +receiving a block with a transaction whose corresponding blob is missing). + +The response MUST consist of zero or more `response_chunk`. Each _successful_ +`response_chunk` MUST contain a single `BlobSidecar` payload. + +Clients MUST support requesting sidecars since `minimum_request_epoch`, where +`minimum_request_epoch = max(finalized_epoch, current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_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 blob 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. + +Clients SHOULD include a sidecar in the response as soon as it passes the gossip +validation rules. 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)`: + + + +| `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? + +This "sidecar" design provides forward compatibility for further data increases +by black-boxing `is_data_available()`: with full sharding `is_data_available()` +can be replaced by data-availability-sampling (DAS) thus avoiding all blobs +being downloaded by all beacon nodes on the network. + +Such sharding design may introduce an updated `BlobSidecar` to identify the +shard, but does not affect the `BeaconBlock` structure. diff --git a/specs/deneb/polynomial-commitments.md b/specs/deneb/polynomial-commitments.md new file mode 100644 index 0000000000..16ddda6644 --- /dev/null +++ b/specs/deneb/polynomial-commitments.md @@ -0,0 +1,615 @@ +# Deneb -- Polynomial Commitments + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Cryptographic types](#cryptographic-types) +- [Constants](#constants) +- [Preset](#preset) + - [Blob](#blob) + - [Trusted setup](#trusted-setup) +- [Helper functions](#helper-functions) + - [Bit-reversal permutation](#bit-reversal-permutation) + - [`is_power_of_two`](#is_power_of_two) + - [`reverse_bits`](#reverse_bits) + - [`bit_reversal_permutation`](#bit_reversal_permutation) + - [BLS12-381 helpers](#bls12-381-helpers) + - [`multi_exp`](#multi_exp) + - [`hash_to_bls_field`](#hash_to_bls_field) + - [`bytes_to_bls_field`](#bytes_to_bls_field) + - [`bls_field_to_bytes`](#bls_field_to_bytes) + - [`validate_kzg_g1`](#validate_kzg_g1) + - [`bytes_to_kzg_commitment`](#bytes_to_kzg_commitment) + - [`bytes_to_kzg_proof`](#bytes_to_kzg_proof) + - [`blob_to_polynomial`](#blob_to_polynomial) + - [`compute_challenge`](#compute_challenge) + - [`g1_lincomb`](#g1_lincomb) + - [`compute_powers`](#compute_powers) + - [`compute_roots_of_unity`](#compute_roots_of_unity) + - [Polynomials](#polynomials) + - [`evaluate_polynomial_in_evaluation_form`](#evaluate_polynomial_in_evaluation_form) + - [KZG](#kzg) + - [`blob_to_kzg_commitment`](#blob_to_kzg_commitment) + - [`verify_kzg_proof`](#verify_kzg_proof) + - [`verify_kzg_proof_impl`](#verify_kzg_proof_impl) + - [`verify_kzg_proof_batch`](#verify_kzg_proof_batch) + - [`compute_kzg_proof`](#compute_kzg_proof) + - [`compute_quotient_eval_within_domain`](#compute_quotient_eval_within_domain) + - [`compute_kzg_proof_impl`](#compute_kzg_proof_impl) + - [`compute_blob_kzg_proof`](#compute_blob_kzg_proof) + - [`verify_blob_kzg_proof`](#verify_blob_kzg_proof) + - [`verify_blob_kzg_proof_batch`](#verify_blob_kzg_proof_batch) + + + +## Introduction + +This document specifies basic polynomial operations and KZG polynomial +commitment operations that are essential for the implementation of the EIP-4844 +feature in the Deneb specification. The implementations are not optimized for +performance, but readability. All practical implementations should optimize the +polynomial operations. + +Functions flagged as "Public method" MUST be provided by the underlying KZG +library as public functions. All other functions are private functions used +internally by the KZG library. + +Public functions MUST accept raw bytes as input and perform the required +cryptographic normalization before invoking any internal functions. + +## Custom types + +| Name | SSZ equivalent | Description | +| --------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `G1Point` | `Bytes48` | | +| `G2Point` | `Bytes96` | | +| `KZGCommitment` | `Bytes48` | Validation: Perform [BLS standard's](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-2.5) "KeyValidate" check but do allow the identity point | +| `KZGProof` | `Bytes48` | Same as for `KZGCommitment` | +| `Blob` | `ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB]` | A basic data blob | + +## Cryptographic types + +| Name | SSZ equivalent | Description | +| ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------------------------------------- | +| [`BLSFieldElement`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/deneb.py#L18-L19) | `uint256` | A value in the finite field defined by `BLS_MODULUS` | +| [`Polynomial`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/deneb.py#L22-L28) | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_BLOB]` | A polynomial in evaluation form | + +## Constants + +| Name | Value | Notes | +| ------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `BLS_MODULUS` | `52435875175126190479447740508185965837690552500527637822603658699938581184513` | Scalar field modulus of BLS12-381 | +| `BYTES_PER_COMMITMENT` | `uint64(48)` | The number of bytes in a KZG commitment | +| `BYTES_PER_PROOF` | `uint64(48)` | The number of bytes in a KZG proof | +| `BYTES_PER_FIELD_ELEMENT` | `uint64(32)` | Bytes used to encode a BLS scalar field element | +| `BYTES_PER_BLOB` | `uint64(BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB)` | The number of bytes in a blob | +| `G1_POINT_AT_INFINITY` | `Bytes48(b'\xc0' + b'\x00' * 47)` | Serialized form of the point at infinity on the G1 group | +| `KZG_ENDIANNESS` | `'big'` | The endianness of the field elements including blobs | +| `PRIMITIVE_ROOT_OF_UNITY` | `7` | The primitive root of unity from which all roots of unity should be derived | + +## Preset + +### Blob + +| Name | Value | +| ----------------------------------- | --------------------- | +| `FIELD_ELEMENTS_PER_BLOB` | `uint64(4096)` | +| `FIAT_SHAMIR_PROTOCOL_DOMAIN` | `b'FSBLOBVERIFY_V1_'` | +| `RANDOM_CHALLENGE_KZG_BATCH_DOMAIN` | `b'RCKZGBATCH___V1_'` | + +### Trusted setup + +| Name | Value | +| ----------------------- | ------------------------------------------ | +| `KZG_SETUP_G2_LENGTH` | `65` | +| `KZG_SETUP_G1_MONOMIAL` | `Vector[G1Point, FIELD_ELEMENTS_PER_BLOB]` | +| `KZG_SETUP_G1_LAGRANGE` | `Vector[G1Point, FIELD_ELEMENTS_PER_BLOB]` | +| `KZG_SETUP_G2_MONOMIAL` | `Vector[G2Point, KZG_SETUP_G2_LENGTH]` | + +## Helper functions + +### Bit-reversal permutation + +All polynomials (which are always given in Lagrange form) should be interpreted +as being in bit-reversal permutation. In practice, clients can implement this by +storing the lists `KZG_SETUP_G1_LAGRANGE` and roots of unity in bit-reversal +permutation, so these functions only have to be called once at startup. + +#### `is_power_of_two` + +```python +def is_power_of_two(value: int) -> bool: + """ + Check if ``value`` is a power of two integer. + """ + return (value > 0) and (value & (value - 1) == 0) +``` + +#### `reverse_bits` + +```python +def reverse_bits(n: int, order: int) -> int: + """ + Reverse the bit order of an integer ``n``. + """ + assert is_power_of_two(order) + # Convert n to binary with the same number of bits as "order" - 1, then reverse its bit order + return int(("{:0" + str(order.bit_length() - 1) + "b}").format(n)[::-1], 2) +``` + +#### `bit_reversal_permutation` + +```python +def bit_reversal_permutation(sequence: Sequence[T]) -> Sequence[T]: + """ + Return a copy with bit-reversed permutation. The permutation is an involution (inverts itself). + + The input and output are a sequence of generic type ``T`` objects. + """ + return [sequence[reverse_bits(i, len(sequence))] for i in range(len(sequence))] +``` + +### BLS12-381 helpers + +#### `multi_exp` + +This function performs a multi-scalar multiplication between `points` and +`integers`. `points` can either be in G1 or G2. + +```python +def multi_exp(_points: Sequence[TPoint], _integers: Sequence[uint64]) -> Sequence[TPoint]: ... +``` + +#### `hash_to_bls_field` + +```python +def hash_to_bls_field(data: bytes) -> BLSFieldElement: + """ + Hash ``data`` and convert the output to a BLS scalar field element. + The output is not uniform over the BLS field. + """ + hashed_data = hash(data) + return BLSFieldElement(int.from_bytes(hashed_data, KZG_ENDIANNESS) % BLS_MODULUS) +``` + +#### `bytes_to_bls_field` + +```python +def bytes_to_bls_field(b: Bytes32) -> BLSFieldElement: + """ + Convert untrusted bytes to a trusted and validated BLS scalar field element. + This function does not accept inputs greater than the BLS modulus. + """ + field_element = int.from_bytes(b, KZG_ENDIANNESS) + assert field_element < BLS_MODULUS + return BLSFieldElement(field_element) +``` + +#### `bls_field_to_bytes` + +```python +def bls_field_to_bytes(x: BLSFieldElement) -> Bytes32: + return int.to_bytes(int(x), 32, KZG_ENDIANNESS) +``` + +#### `validate_kzg_g1` + +```python +def validate_kzg_g1(b: Bytes48) -> None: + """ + Perform BLS validation required by the types `KZGProof` and `KZGCommitment`. + """ + if b == G1_POINT_AT_INFINITY: + return + + assert bls.KeyValidate(b) +``` + +#### `bytes_to_kzg_commitment` + +```python +def bytes_to_kzg_commitment(b: Bytes48) -> KZGCommitment: + """ + Convert untrusted bytes into a trusted and validated KZGCommitment. + """ + validate_kzg_g1(b) + return KZGCommitment(b) +``` + +#### `bytes_to_kzg_proof` + +```python +def bytes_to_kzg_proof(b: Bytes48) -> KZGProof: + """ + Convert untrusted bytes into a trusted and validated KZGProof. + """ + validate_kzg_g1(b) + return KZGProof(b) +``` + +#### `blob_to_polynomial` + +```python +def blob_to_polynomial(blob: Blob) -> Polynomial: + """ + Convert a blob to list of BLS field scalars. + """ + polynomial = Polynomial() + for i in range(FIELD_ELEMENTS_PER_BLOB): + value = bytes_to_bls_field( + blob[i * BYTES_PER_FIELD_ELEMENT : (i + 1) * BYTES_PER_FIELD_ELEMENT] + ) + polynomial[i] = value + return polynomial +``` + +#### `compute_challenge` + +```python +def compute_challenge(blob: Blob, commitment: KZGCommitment) -> BLSFieldElement: + """ + Return the Fiat-Shamir challenge required by the rest of the protocol. + """ + + # Append the degree of the polynomial as a domain separator + degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 16, KZG_ENDIANNESS) + data = FIAT_SHAMIR_PROTOCOL_DOMAIN + degree_poly + + data += blob + data += commitment + + # Transcript has been prepared: time to create the challenge + return hash_to_bls_field(data) +``` + +#### `g1_lincomb` + +```python +def g1_lincomb( + points: Sequence[KZGCommitment], scalars: Sequence[BLSFieldElement] +) -> KZGCommitment: + """ + BLS multiscalar multiplication in G1. This can be naively implemented using double-and-add. + """ + assert len(points) == len(scalars) + + if len(points) == 0: + return bls.G1_to_bytes48(bls.Z1()) + + points_g1 = [] + for point in points: + points_g1.append(bls.bytes48_to_G1(point)) + + result = bls.multi_exp(points_g1, scalars) + return KZGCommitment(bls.G1_to_bytes48(result)) +``` + +#### `compute_powers` + +```python +def compute_powers(x: BLSFieldElement, n: uint64) -> Sequence[BLSFieldElement]: + """ + Return ``x`` to power of [0, n-1], if n > 0. When n==0, an empty array is returned. + """ + current_power = BLSFieldElement(1) + powers = [] + for _ in range(n): + powers.append(current_power) + current_power = current_power * x + return powers +``` + +#### `compute_roots_of_unity` + +```python +def compute_roots_of_unity(order: uint64) -> Sequence[BLSFieldElement]: + """ + Return roots of unity of ``order``. + """ + assert (BLS_MODULUS - 1) % int(order) == 0 + root_of_unity = BLSFieldElement( + pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // int(order), BLS_MODULUS) + ) + return compute_powers(root_of_unity, order) +``` + +### Polynomials + +#### `evaluate_polynomial_in_evaluation_form` + +```python +def evaluate_polynomial_in_evaluation_form( + polynomial: Polynomial, z: BLSFieldElement +) -> BLSFieldElement: + """ + Evaluate a polynomial (in evaluation form) at an arbitrary point ``z``. + - When ``z`` is in the domain, the evaluation can be found by indexing the polynomial at the + position that ``z`` is in the domain. + - When ``z`` is not in the domain, the barycentric formula is used: + f(z) = (z**WIDTH - 1) / WIDTH * sum_(i=0)^WIDTH (f(DOMAIN[i]) * DOMAIN[i]) / (z - DOMAIN[i]) + """ + width = len(polynomial) + assert width == FIELD_ELEMENTS_PER_BLOB + inverse_width = BLSFieldElement(width).inverse() + + roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB)) + + # If we are asked to evaluate within the domain, we already know the answer + if z in roots_of_unity_brp: + eval_index = roots_of_unity_brp.index(z) + return polynomial[eval_index] + + result = BLSFieldElement(0) + for i in range(width): + a = polynomial[i] * roots_of_unity_brp[i] + b = z - roots_of_unity_brp[i] + result += a / b + r = z.pow(BLSFieldElement(width)) - BLSFieldElement(1) + result = result * r * inverse_width + return result +``` + +### KZG + +KZG core functions. These are also defined in Deneb execution specs. + +#### `blob_to_kzg_commitment` + +```python +def blob_to_kzg_commitment(blob: Blob) -> KZGCommitment: + """ + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + return g1_lincomb(bit_reversal_permutation(KZG_SETUP_G1_LAGRANGE), blob_to_polynomial(blob)) +``` + +#### `verify_kzg_proof` + +```python +def verify_kzg_proof( + commitment_bytes: Bytes48, z_bytes: Bytes32, y_bytes: Bytes32, proof_bytes: Bytes48 +) -> bool: + """ + Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``. + Receives inputs as bytes. + Public method. + """ + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(z_bytes) == BYTES_PER_FIELD_ELEMENT + assert len(y_bytes) == BYTES_PER_FIELD_ELEMENT + assert len(proof_bytes) == BYTES_PER_PROOF + + return verify_kzg_proof_impl( + bytes_to_kzg_commitment(commitment_bytes), + bytes_to_bls_field(z_bytes), + bytes_to_bls_field(y_bytes), + bytes_to_kzg_proof(proof_bytes), + ) +``` + +#### `verify_kzg_proof_impl` + +```python +def verify_kzg_proof_impl( + commitment: KZGCommitment, z: BLSFieldElement, y: BLSFieldElement, proof: KZGProof +) -> bool: + """ + Verify KZG proof that ``p(z) == y`` where ``p(z)`` is the polynomial represented by ``polynomial_kzg``. + """ + # Verify: P - y = Q * (X - z) + X_minus_z = bls.add( + bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[1]), + bls.multiply(bls.G2(), -z), + ) + P_minus_y = bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), -y)) + return bls.pairing_check( + [[P_minus_y, bls.neg(bls.G2())], [bls.bytes48_to_G1(proof), X_minus_z]] + ) +``` + +#### `verify_kzg_proof_batch` + +```python +def verify_kzg_proof_batch( + commitments: Sequence[KZGCommitment], + zs: Sequence[BLSFieldElement], + ys: Sequence[BLSFieldElement], + proofs: Sequence[KZGProof], +) -> bool: + """ + Verify multiple KZG proofs efficiently. + """ + + assert len(commitments) == len(zs) == len(ys) == len(proofs) + + # Compute a random challenge. Note that it does not have to be computed from a hash, + # r just has to be random. + degree_poly = int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, KZG_ENDIANNESS) + num_commitments = int.to_bytes(len(commitments), 8, KZG_ENDIANNESS) + data = RANDOM_CHALLENGE_KZG_BATCH_DOMAIN + degree_poly + num_commitments + + # Append all inputs to the transcript before we hash + for commitment, z, y, proof in zip(commitments, zs, ys, proofs): + data += commitment + bls_field_to_bytes(z) + bls_field_to_bytes(y) + proof + + r = hash_to_bls_field(data) + r_powers = compute_powers(r, len(commitments)) + + # Verify: e(sum r^i proof_i, [s]) == + # e(sum r^i (commitment_i - [y_i]) + sum r^i z_i proof_i, [1]) + proof_lincomb = g1_lincomb(proofs, r_powers) + proof_z_lincomb = g1_lincomb(proofs, [z * r_power for z, r_power in zip(zs, r_powers)]) + C_minus_ys = [ + bls.add(bls.bytes48_to_G1(commitment), bls.multiply(bls.G1(), -y)) + 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(), + ], + ] + ) +``` + +#### `compute_kzg_proof` + +```python +def compute_kzg_proof(blob: Blob, z_bytes: Bytes32) -> Tuple[KZGProof, Bytes32]: + """ + Compute KZG proof at point `z` for the polynomial represented by `blob`. + Do this by computing the quotient polynomial in evaluation form: q(x) = (p(x) - p(z)) / (x - z). + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + assert len(z_bytes) == BYTES_PER_FIELD_ELEMENT + polynomial = blob_to_polynomial(blob) + proof, y = compute_kzg_proof_impl(polynomial, bytes_to_bls_field(z_bytes)) + return proof, int(y).to_bytes(BYTES_PER_FIELD_ELEMENT, KZG_ENDIANNESS) +``` + +#### `compute_quotient_eval_within_domain` + +```python +def compute_quotient_eval_within_domain( + z: BLSFieldElement, polynomial: Polynomial, y: BLSFieldElement +) -> BLSFieldElement: + """ + Given `y == p(z)` for a polynomial `p(x)`, compute `q(z)`: the KZG quotient polynomial evaluated at `z` for the + special case where `z` is in roots of unity. + + For more details, read https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html section "Dividing + when one of the points is zero". The code below computes q(x_m) for the roots of unity special case. + """ + roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB)) + result = BLSFieldElement(0) + for i, omega_i in enumerate(roots_of_unity_brp): + if omega_i == z: # skip the evaluation point in the sum + continue + + f_i = polynomial[i] - y + numerator = f_i * omega_i + denominator = z * (z - omega_i) + result += numerator / denominator + + return result +``` + +#### `compute_kzg_proof_impl` + +```python +def compute_kzg_proof_impl( + polynomial: Polynomial, z: BLSFieldElement +) -> Tuple[KZGProof, BLSFieldElement]: + """ + Helper function for `compute_kzg_proof()` and `compute_blob_kzg_proof()`. + """ + roots_of_unity_brp = bit_reversal_permutation(compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB)) + + # For all x_i, compute p(x_i) - p(z) + y = evaluate_polynomial_in_evaluation_form(polynomial, z) + polynomial_shifted = [p - y for p in polynomial] + + # For all x_i, compute (x_i - z) + denominator_poly = [x - z for x in roots_of_unity_brp] + + # Compute the quotient polynomial directly in evaluation form + quotient_polynomial = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_BLOB + for i, (a, b) in enumerate(zip(polynomial_shifted, denominator_poly)): + if b == BLSFieldElement(0): + # The denominator is zero hence `z` is a root of unity: we must handle it as a special case + quotient_polynomial[i] = compute_quotient_eval_within_domain( + roots_of_unity_brp[i], polynomial, y + ) + else: + # Compute: q(x_i) = (p(x_i) - p(z)) / (x_i - z). + quotient_polynomial[i] = a / b + + return KZGProof( + g1_lincomb(bit_reversal_permutation(KZG_SETUP_G1_LAGRANGE), quotient_polynomial) + ), y +``` + +#### `compute_blob_kzg_proof` + +```python +def compute_blob_kzg_proof(blob: Blob, commitment_bytes: Bytes48) -> KZGProof: + """ + Given a blob, return the KZG proof that is used to verify it against the commitment. + This method does not verify that the commitment is correct with respect to `blob`. + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + commitment = bytes_to_kzg_commitment(commitment_bytes) + polynomial = blob_to_polynomial(blob) + evaluation_challenge = compute_challenge(blob, commitment) + proof, _ = compute_kzg_proof_impl(polynomial, evaluation_challenge) + return proof +``` + +#### `verify_blob_kzg_proof` + +```python +def verify_blob_kzg_proof(blob: Blob, commitment_bytes: Bytes48, proof_bytes: Bytes48) -> bool: + """ + Given a blob and a KZG proof, verify that the blob data corresponds to the provided commitment. + + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(proof_bytes) == BYTES_PER_PROOF + + commitment = bytes_to_kzg_commitment(commitment_bytes) + + polynomial = blob_to_polynomial(blob) + evaluation_challenge = compute_challenge(blob, commitment) + + # Evaluate polynomial at `evaluation_challenge` + y = evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge) + + # Verify proof + proof = bytes_to_kzg_proof(proof_bytes) + return verify_kzg_proof_impl(commitment, evaluation_challenge, y, proof) +``` + +#### `verify_blob_kzg_proof_batch` + +```python +def verify_blob_kzg_proof_batch( + blobs: Sequence[Blob], commitments_bytes: Sequence[Bytes48], proofs_bytes: Sequence[Bytes48] +) -> bool: + """ + Given a list of blobs and blob KZG proofs, verify that they correspond to the provided commitments. + Will return True if there are zero blobs/commitments/proofs. + Public method. + """ + + assert len(blobs) == len(commitments_bytes) == len(proofs_bytes) + + commitments, evaluation_challenges, ys, proofs = [], [], [], [] + for blob, commitment_bytes, proof_bytes in zip(blobs, commitments_bytes, proofs_bytes): + assert len(blob) == BYTES_PER_BLOB + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + assert len(proof_bytes) == BYTES_PER_PROOF + commitment = bytes_to_kzg_commitment(commitment_bytes) + commitments.append(commitment) + polynomial = blob_to_polynomial(blob) + evaluation_challenge = compute_challenge(blob, commitment) + evaluation_challenges.append(evaluation_challenge) + ys.append(evaluate_polynomial_in_evaluation_form(polynomial, evaluation_challenge)) + proofs.append(bytes_to_kzg_proof(proof_bytes)) + + return verify_kzg_proof_batch(commitments, evaluation_challenges, ys, proofs) +``` diff --git a/specs/deneb/validator.md b/specs/deneb/validator.md new file mode 100644 index 0000000000..d415f80bda --- /dev/null +++ b/specs/deneb/validator.md @@ -0,0 +1,209 @@ +# Deneb -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [`BlobsBundle`](#blobsbundle) + - [Modified `GetPayloadResponse`](#modified-getpayloadresponse) +- [Protocol](#protocol) + - [`ExecutionEngine`](#executionengine) + - [Modified `get_payload`](#modified-get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Block and sidecar proposal](#block-and-sidecar-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [ExecutionPayload](#executionpayload) + - [Blob KZG commitments](#blob-kzg-commitments) + - [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars) + - [Sidecar](#sidecar) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement Deneb. + +## Prerequisites + +This document is an extension of the +[Capella -- Honest Validator](../capella/validator.md) guide. All behaviors and +definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated [Beacon Chain doc of Deneb](./beacon-chain.md) are requisite for this +document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Helpers + +### `BlobsBundle` + +*[New in Deneb:EIP4844]* + +```python +@dataclass +class BlobsBundle(object): + commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + blobs: List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK] +``` + +### Modified `GetPayloadResponse` + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload + block_value: uint256 + blobs_bundle: BlobsBundle # [New in Deneb:EIP4844] +``` + +```python +def compute_signed_block_header(signed_block: SignedBeaconBlock) -> SignedBeaconBlockHeader: + block = signed_block.message + block_header = BeaconBlockHeader( + slot=block.slot, + proposer_index=block.proposer_index, + parent_root=block.parent_root, + state_root=block.state_root, + body_root=hash_tree_root(block.body), + ) + return SignedBeaconBlockHeader(message=block_header, signature=signed_block.signature) +``` + +## Protocol + +### `ExecutionEngine` + +#### Modified `get_payload` + +Given the `payload_id`, `get_payload` returns the most recent version of the +execution payload that has been built since the corresponding call to +`notify_forkchoice_updated` method. + +```python +def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + """ + Return ExecutionPayload, uint256, BlobsBundle objects. + """ + # pylint: disable=unused-argument + ... +``` + +## Beacon chain responsibilities + +All validator responsibilities remain unchanged other than those noted below. + +### Block and sidecar proposal + +#### Constructing the `BeaconBlockBody` + +##### ExecutionPayload + +`prepare_execution_payload` is updated from the Capella specs to provide the +parent beacon block root. + +*Note*: In this section, `state` is the state of the slot for the block proposal +_without_ the block yet applied. That is, `state` is the `previous_state` +processed through any empty slots up to the assigned slot using +`process_slots(previous_state, slot)`. + +*Note*: The only change made to `prepare_execution_payload` is to add the parent +beacon block root as an additional parameter to the `PayloadAttributes`. + +```python +def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, +) -> Optional[PayloadId]: + # Verify consistency of the parent hash with respect to the previous execution payload header + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=get_expected_withdrawals(state), + parent_beacon_block_root=hash_tree_root( + state.latest_block_header + ), # [New in Deneb:EIP4788] + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) +``` + +##### Blob KZG commitments + +*[New in Deneb:EIP4844]* + +1. The execution payload is obtained from the execution engine as defined above + using `payload_id`. The response also includes a `blobs_bundle` entry + containing the corresponding `blobs`, `commitments`, and `proofs`. +2. Set `block.body.blob_kzg_commitments = commitments`. + +#### Constructing the `BlobSidecar`s + +*[New in Deneb:EIP4844]* + +To construct a `BlobSidecar`, a `blob_sidecar` is defined with the necessary +context for block and sidecar proposal. + +##### Sidecar + +Blobs associated with a block are packaged into sidecar objects for distribution +to the associated sidecar topic, the `blob_sidecar_{subnet_id}` pubsub topic. + +Each `sidecar` is obtained from: + +```python +def get_blob_sidecars( + signed_block: SignedBeaconBlock, blobs: Sequence[Blob], blob_kzg_proofs: Sequence[KZGProof] +) -> Sequence[BlobSidecar]: + block = signed_block.message + signed_block_header = compute_signed_block_header(signed_block) + return [ + BlobSidecar( + index=index, + blob=blob, + kzg_commitment=block.body.blob_kzg_commitments[index], + kzg_proof=blob_kzg_proofs[index], + signed_block_header=signed_block_header, + kzg_commitment_inclusion_proof=compute_merkle_proof( + block.body, + get_generalized_index(BeaconBlockBody, "blob_kzg_commitments", index), + ), + ) + for index, blob in enumerate(blobs) + ] +``` + +The `subnet_id` for the `blob_sidecar` is calculated with: + +- Let `blob_index = blob_sidecar.index`. +- Let `subnet_id = compute_subnet_for_blob_sidecar(blob_index)`. + +```python +def compute_subnet_for_blob_sidecar(blob_index: BlobIndex) -> SubnetID: + return SubnetID(blob_index % BLOB_SIDECAR_SUBNET_COUNT) +``` + +After publishing the peers on the network may request the sidecar through +sync-requests, or a local user may be interested. + +The validator MUST hold on to sidecars for +`MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` epochs and serve when capable, to ensure +the data-availability of these blobs throughout the network. + +After `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` nodes MAY prune the sidecars +and/or stop serving them. diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md new file mode 100644 index 0000000000..582bfd4e94 --- /dev/null +++ b/specs/electra/beacon-chain.md @@ -0,0 +1,1856 @@ +# Electra -- The Beacon Chain + + + +- [Introduction](#introduction) +- [Constants](#constants) + - [Misc](#misc) + - [Withdrawal prefixes](#withdrawal-prefixes) + - [Execution layer triggered requests](#execution-layer-triggered-requests) +- [Preset](#preset) + - [Gwei values](#gwei-values) + - [Rewards and penalties](#rewards-and-penalties) + - [State list lengths](#state-list-lengths) + - [Max operations per block](#max-operations-per-block) + - [Execution](#execution) + - [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) + - [`PendingDeposit`](#pendingdeposit) + - [`PendingPartialWithdrawal`](#pendingpartialwithdrawal) + - [`PendingConsolidation`](#pendingconsolidation) + - [`DepositRequest`](#depositrequest) + - [`WithdrawalRequest`](#withdrawalrequest) + - [`ConsolidationRequest`](#consolidationrequest) + - [`ExecutionRequests`](#executionrequests) + - [`SingleAttestation`](#singleattestation) + - [Modified containers](#modified-containers) + - [`AttesterSlashing`](#attesterslashing) + - [`BeaconBlockBody`](#beaconblockbody) + - [Modified containers](#modified-containers-1) + - [`Attestation`](#attestation) + - [`IndexedAttestation`](#indexedattestation) + - [`BeaconState`](#beaconstate) +- [Helper functions](#helper-functions) + - [Predicates](#predicates) + - [Modified `compute_proposer_index`](#modified-compute_proposer_index) + - [Modified `is_eligible_for_activation_queue`](#modified-is_eligible_for_activation_queue) + - [New `is_compounding_withdrawal_credential`](#new-is_compounding_withdrawal_credential) + - [New `has_compounding_withdrawal_credential`](#new-has_compounding_withdrawal_credential) + - [New `has_execution_withdrawal_credential`](#new-has_execution_withdrawal_credential) + - [Modified `is_fully_withdrawable_validator`](#modified-is_fully_withdrawable_validator) + - [Modified `is_partially_withdrawable_validator`](#modified-is_partially_withdrawable_validator) + - [Misc](#misc-1) + - [New `get_committee_indices`](#new-get_committee_indices) + - [New `get_max_effective_balance`](#new-get_max_effective_balance) + - [Beacon state accessors](#beacon-state-accessors) + - [New `get_balance_churn_limit`](#new-get_balance_churn_limit) + - [New `get_activation_exit_churn_limit`](#new-get_activation_exit_churn_limit) + - [New `get_consolidation_churn_limit`](#new-get_consolidation_churn_limit) + - [New `get_pending_balance_to_withdraw`](#new-get_pending_balance_to_withdraw) + - [Modified `get_attesting_indices`](#modified-get_attesting_indices) + - [Modified `get_next_sync_committee_indices`](#modified-get_next_sync_committee_indices) + - [Beacon state mutators](#beacon-state-mutators) + - [Modified `initiate_validator_exit`](#modified-initiate_validator_exit) + - [New `switch_to_compounding_validator`](#new-switch_to_compounding_validator) + - [New `queue_excess_active_balance`](#new-queue_excess_active_balance) + - [New `compute_exit_epoch_and_update_churn`](#new-compute_exit_epoch_and_update_churn) + - [New `compute_consolidation_epoch_and_update_churn`](#new-compute_consolidation_epoch_and_update_churn) + - [Modified `slash_validator`](#modified-slash_validator) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Epoch processing](#epoch-processing) + - [Modified `process_epoch`](#modified-process_epoch) + - [Modified `process_registry_updates`](#modified-process_registry_updates) + - [Modified `process_slashings`](#modified-process_slashings) + - [New `apply_pending_deposit`](#new-apply_pending_deposit) + - [New `process_pending_deposits`](#new-process_pending_deposits) + - [New `process_pending_consolidations`](#new-process_pending_consolidations) + - [Modified `process_effective_balance_updates`](#modified-process_effective_balance_updates) + - [Execution engine](#execution-engine) + - [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) + - [Withdrawals](#withdrawals) + - [Modified `get_expected_withdrawals`](#modified-get_expected_withdrawals) + - [Modified `process_withdrawals`](#modified-process_withdrawals) + - [Execution payload](#execution-payload) + - [New `get_execution_requests_list`](#new-get_execution_requests_list) + - [Modified `process_execution_payload`](#modified-process_execution_payload) + - [Operations](#operations) + - [Modified `process_operations`](#modified-process_operations) + - [Attestations](#attestations) + - [Modified `process_attestation`](#modified-process_attestation) + - [Deposits](#deposits) + - [Modified `get_validator_from_deposit`](#modified-get_validator_from_deposit) + - [Modified `add_validator_to_registry`](#modified-add_validator_to_registry) + - [Modified `apply_deposit`](#modified-apply_deposit) + - [New `is_valid_deposit_signature`](#new-is_valid_deposit_signature) + - [Modified `process_deposit`](#modified-process_deposit) + - [Voluntary exits](#voluntary-exits) + - [Modified `process_voluntary_exit`](#modified-process_voluntary_exit) + - [Execution layer withdrawal requests](#execution-layer-withdrawal-requests) + - [New `process_withdrawal_request`](#new-process_withdrawal_request) + - [Deposit requests](#deposit-requests) + - [New `process_deposit_request`](#new-process_deposit_request) + - [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) + + + +## Introduction + +Electra is a consensus-layer upgrade containing a number of features. Including: + +- [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110): Supply validator deposits + on chain +- [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. + +## Constants + +The following values are (non-configurable) constants used throughout the +specification. + +### Misc + +| Name | Value | Description | +| ------------------------------------ | ------------------- | --------------------------------------------------------------------------------- | +| `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 | Description | +| ------------------------------- | ---------------- | ----------------------------------------------------------------------------------- | +| `COMPOUNDING_WITHDRAWAL_PREFIX` | `Bytes1('0x02')` | *[New in Electra:EIP7251]* Withdrawal credential prefix for a compounding validator | + +### Execution layer triggered requests + +| Name | Value | +| ---------------------------- | ---------------- | +| `DEPOSIT_REQUEST_TYPE` | `Bytes1('0x00')` | +| `WITHDRAWAL_REQUEST_TYPE` | `Bytes1('0x01')` | +| `CONSOLIDATION_REQUEST_TYPE` | `Bytes1('0x02')` | + +## Preset + +### Gwei values + +| 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) | + +### State list lengths + +| Name | Value | Unit | +| ----------------------------------- | ------------------------------- | --------------------------- | +| `PENDING_DEPOSITS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending deposits | +| `PENDING_PARTIAL_WITHDRAWALS_LIMIT` | `uint64(2**27)` (= 134,217,728) | pending partial withdrawals | +| `PENDING_CONSOLIDATIONS_LIMIT` | `uint64(2**18)` (= 262,144) | pending consolidations | + +### Max operations per block + +| Name | Value | +| -------------------------------- | ------------ | +| `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 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(2**1)` (= 2) | *[New in Electra:EIP7251]* Maximum number of execution layer consolidation requests in each payload | + +### Withdrawals processing + +| Name | Value | Description | +| -------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------- | +| `MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP` | `uint64(2**3)` (= 8) | *[New in Electra:EIP7002]* Maximum number of pending partial withdrawals to process per payload | + +### Pending deposits processing + +| Name | Value | Description | +| -------------------------------- | --------------------- | ---------------------------------------------------------------------------------- | +| `MAX_PENDING_DEPOSITS_PER_EPOCH` | `uint64(2**4)` (= 16) | *[New in Electra:EIP6110]* Maximum number of pending deposits to process per epoch | + +## 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 | +| ------------------------------------------- | ---------------------------------------- | +| `MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA` | `Gwei(2**7 * 10**9)` (= 128,000,000,000) | +| `MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT` | `Gwei(2**8 * 10**9)` (= 256,000,000,000) | + +## Containers + +### New containers + +#### `PendingDeposit` + +*Note*: The container is new in EIP7251. + +```python +class PendingDeposit(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature + slot: Slot +``` + +#### `PendingPartialWithdrawal` + +*Note*: The container is new in EIP7251. + +```python +class PendingPartialWithdrawal(Container): + validator_index: ValidatorIndex + amount: Gwei + withdrawable_epoch: Epoch +``` + +#### `PendingConsolidation` + +*Note*: The container is new in EIP7251. + +```python +class PendingConsolidation(Container): + source_index: ValidatorIndex + target_index: ValidatorIndex +``` + +#### `DepositRequest` + +*Note*: The container is new in EIP6110. + +```python +class DepositRequest(Container): + pubkey: BLSPubkey + withdrawal_credentials: Bytes32 + amount: Gwei + signature: BLSSignature + index: uint64 +``` + +#### `WithdrawalRequest` + +*Note*: The container is new in EIP7251:EIP7002. + +```python +class WithdrawalRequest(Container): + source_address: ExecutionAddress + validator_pubkey: BLSPubkey + amount: Gwei +``` + +#### `ConsolidationRequest` + +*Note*: The container is new in EIP7251. + +```python +class ConsolidationRequest(Container): + source_address: ExecutionAddress + source_pubkey: BLSPubkey + target_pubkey: BLSPubkey +``` + +#### `ExecutionRequests` + +```python +class ExecutionRequests(Container): + # [New in Electra:EIP6110] + deposits: List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD] + # [New in Electra:EIP7002:EIP7251] + withdrawals: List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD] + # [New in Electra:EIP7251] + consolidations: List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD] +``` + +#### `SingleAttestation` + +```python +class SingleAttestation(Container): + committee_index: CommitteeIndex + attester_index: ValidatorIndex + data: AttestationData + signature: BLSSignature +``` + +### Modified containers + +#### `AttesterSlashing` + +```python +class AttesterSlashing(Container): + # [Modified in Electra:EIP7549] + attestation_1: IndexedAttestation + # [Modified in Electra:EIP7549] + attestation_2: IndexedAttestation +``` + +#### `BeaconBlockBody` + +```python +class BeaconBlockBody(Container): + randao_reveal: BLSSignature + eth1_data: Eth1Data + graffiti: Bytes32 + proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] + # [Modified in Electra:EIP7549] + attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS_ELECTRA] + # [Modified in Electra:EIP7549] + attestations: List[Attestation, MAX_ATTESTATIONS_ELECTRA] + deposits: List[Deposit, MAX_DEPOSITS] + voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] + sync_aggregate: SyncAggregate + execution_payload: ExecutionPayload + bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + # [New in Electra] + execution_requests: ExecutionRequests +``` + +### Modified containers + +#### `Attestation` + +```python +class Attestation(Container): + # [Modified in Electra:EIP7549] + aggregation_bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + data: AttestationData + signature: BLSSignature + # [New in Electra:EIP7549] + committee_bits: Bitvector[MAX_COMMITTEES_PER_SLOT] +``` + +#### `IndexedAttestation` + +```python +class IndexedAttestation(Container): + # [Modified in Electra:EIP7549] + attesting_indices: List[ValidatorIndex, MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT] + data: AttestationData + signature: BLSSignature +``` + +#### `BeaconState` + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + latest_execution_payload_header: ExecutionPayloadHeader + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + # [New in Electra:EIP6110] + deposit_requests_start_index: uint64 + # [New in Electra:EIP7251] + deposit_balance_to_consume: Gwei + # [New in Electra:EIP7251] + exit_balance_to_consume: Gwei + # [New in Electra:EIP7251] + earliest_exit_epoch: Epoch + # [New in Electra:EIP7251] + consolidation_balance_to_consume: Gwei + # [New in Electra:EIP7251] + earliest_consolidation_epoch: Epoch + # [New in Electra:EIP7251] + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] + # [New in Electra:EIP7251] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + # [New in Electra:EIP7251] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] +``` + +## Helper functions + +### Predicates + +#### Modified `compute_proposer_index` + +*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: + """ + Return from ``indices`` a random index sampled by effective balance. + """ + assert len(indices) > 0 + 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)] + # [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_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: + return candidate_index + i += 1 +``` + +#### Modified `is_eligible_for_activation_queue` + +*Note*: The function `is_eligible_for_activation_queue` is modified to use +`MIN_ACTIVATION_BALANCE` instead of `MAX_EFFECTIVE_BALANCE`. + +```python +def is_eligible_for_activation_queue(validator: Validator) -> bool: + """ + Check if ``validator`` is eligible to be placed into the activation queue. + """ + return ( + validator.activation_eligibility_epoch == FAR_FUTURE_EPOCH + # [Modified in Electra:EIP7251] + and validator.effective_balance >= MIN_ACTIVATION_BALANCE + ) +``` + +#### New `is_compounding_withdrawal_credential` + +```python +def is_compounding_withdrawal_credential(withdrawal_credentials: Bytes32) -> bool: + return withdrawal_credentials[:1] == COMPOUNDING_WITHDRAWAL_PREFIX +``` + +#### New `has_compounding_withdrawal_credential` + +```python +def has_compounding_withdrawal_credential(validator: Validator) -> bool: + """ + Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential. + """ + return is_compounding_withdrawal_credential(validator.withdrawal_credentials) +``` + +#### New `has_execution_withdrawal_credential` + +```python +def has_execution_withdrawal_credential(validator: Validator) -> bool: + """ + Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. + """ + return ( + has_eth1_withdrawal_credential(validator) # 0x01 + or has_compounding_withdrawal_credential(validator) # 0x02 + ) +``` + +#### Modified `is_fully_withdrawable_validator` + +*Note*: The function `is_fully_withdrawable_validator` is modified to use +`has_execution_withdrawal_credential` instead of +`has_eth1_withdrawal_credential`. + +```python +def is_fully_withdrawable_validator(validator: Validator, balance: Gwei, epoch: Epoch) -> bool: + """ + Check if ``validator`` is fully withdrawable. + """ + return ( + # [Modified in Electra:EIP7251] + has_execution_withdrawal_credential(validator) + and validator.withdrawable_epoch <= epoch + and balance > 0 + ) +``` + +#### Modified `is_partially_withdrawable_validator` + +*Note*: The function `is_partially_withdrawable_validator` is modified to use +`get_max_effective_balance` instead of `MAX_EFFECTIVE_BALANCE` and +`has_execution_withdrawal_credential` instead of +`has_eth1_withdrawal_credential`. + +```python +def is_partially_withdrawable_validator(validator: Validator, balance: Gwei) -> bool: + """ + Check if ``validator`` is partially withdrawable. + """ + max_effective_balance = get_max_effective_balance(validator) + # [Modified in Electra:EIP7251] + has_max_effective_balance = validator.effective_balance == max_effective_balance + # [Modified in Electra:EIP7251] + has_excess_balance = balance > max_effective_balance + return ( + # [Modified in Electra:EIP7251] + has_execution_withdrawal_credential(validator) + and has_max_effective_balance + and has_excess_balance + ) +``` + +### Misc + +#### New `get_committee_indices` + +```python +def get_committee_indices(committee_bits: Bitvector) -> Sequence[CommitteeIndex]: + return [CommitteeIndex(index) for index, bit in enumerate(committee_bits) if bit] +``` + +#### New `get_max_effective_balance` + +```python +def get_max_effective_balance(validator: Validator) -> Gwei: + """ + Get max effective balance for ``validator``. + """ + if has_compounding_withdrawal_credential(validator): + return MAX_EFFECTIVE_BALANCE_ELECTRA + else: + return MIN_ACTIVATION_BALANCE +``` + +### Beacon state accessors + +#### New `get_balance_churn_limit` + +```python +def get_balance_churn_limit(state: BeaconState) -> Gwei: + """ + Return the churn limit for the current epoch. + """ + churn = max( + MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA, get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT + ) + return churn - churn % EFFECTIVE_BALANCE_INCREMENT +``` + +#### New `get_activation_exit_churn_limit` + +```python +def get_activation_exit_churn_limit(state: BeaconState) -> Gwei: + """ + Return the churn limit for the current epoch dedicated to activations and exits. + """ + return min(MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state)) +``` + +#### New `get_consolidation_churn_limit` + +```python +def get_consolidation_churn_limit(state: BeaconState) -> Gwei: + return get_balance_churn_limit(state) - get_activation_exit_churn_limit(state) +``` + +#### New `get_pending_balance_to_withdraw` + +```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.validator_index == validator_index + ) +``` + +#### Modified `get_attesting_indices` + +*Note*: The function `get_attesting_indices` is modified to support EIP7549. + +```python +def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]: + """ + Return the set of attesting indices corresponding to ``aggregation_bits`` and ``committee_bits``. + """ + output: Set[ValidatorIndex] = set() + committee_indices = get_committee_indices(attestation.committee_bits) + committee_offset = 0 + for committee_index in committee_indices: + committee = get_beacon_committee(state, attestation.data.slot, committee_index) + committee_attesters = set( + attester_index + for i, attester_index in enumerate(committee) + if attestation.aggregation_bits[committee_offset + i] + ) + output = output.union(committee_attesters) + + committee_offset += len(committee) + + return output +``` + +#### Modified `get_next_sync_committee_indices` + +*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]: + """ + Return the sync committee indices, with possible duplicates, for the next sync committee. + """ + epoch = Epoch(get_current_epoch(state) + 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 = 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] + # [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_VALUE >= MAX_EFFECTIVE_BALANCE_ELECTRA * random_value: + sync_committee_indices.append(candidate_index) + i += 1 + return sync_committee_indices +``` + +### Beacon state mutators + +#### Modified `initiate_validator_exit` + +*Note*: The function `initiate_validator_exit` is modified to use the new +`compute_exit_epoch_and_update_churn` function. + +```python +def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: + """ + Initiate the exit of the validator with index ``index``. + """ + # Return if validator already initiated exit + validator = state.validators[index] + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + + # Compute exit queue epoch [Modified in Electra:EIP7251] + exit_queue_epoch = compute_exit_epoch_and_update_churn(state, validator.effective_balance) + + # Set validator exit epoch and withdrawable epoch + validator.exit_epoch = exit_queue_epoch + validator.withdrawable_epoch = Epoch(validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY) +``` + +#### New `switch_to_compounding_validator` + +```python +def switch_to_compounding_validator(state: BeaconState, index: ValidatorIndex) -> None: + validator = state.validators[index] + validator.withdrawal_credentials = ( + COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + ) + queue_excess_active_balance(state, index) +``` + +#### New `queue_excess_active_balance` + +```python +def queue_excess_active_balance(state: BeaconState, index: ValidatorIndex) -> None: + balance = state.balances[index] + if balance > MIN_ACTIVATION_BALANCE: + excess_balance = balance - MIN_ACTIVATION_BALANCE + state.balances[index] = MIN_ACTIVATION_BALANCE + validator = state.validators[index] + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + state.pending_deposits.append( + PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=excess_balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + ) + ) +``` + +#### New `compute_exit_epoch_and_update_churn` + +```python +def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch: + earliest_exit_epoch = max( + state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state)) + ) + per_epoch_churn = get_activation_exit_churn_limit(state) + # New epoch for exits. + if state.earliest_exit_epoch < earliest_exit_epoch: + exit_balance_to_consume = per_epoch_churn + else: + exit_balance_to_consume = state.exit_balance_to_consume + + # Exit doesn't fit in the current earliest epoch. + if exit_balance > exit_balance_to_consume: + balance_to_process = exit_balance - exit_balance_to_consume + additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1 + earliest_exit_epoch += additional_epochs + exit_balance_to_consume += additional_epochs * per_epoch_churn + + # Consume the balance and update state variables. + state.exit_balance_to_consume = exit_balance_to_consume - exit_balance + state.earliest_exit_epoch = earliest_exit_epoch + + return state.earliest_exit_epoch +``` + +#### New `compute_consolidation_epoch_and_update_churn` + +```python +def compute_consolidation_epoch_and_update_churn( + state: BeaconState, consolidation_balance: Gwei +) -> Epoch: + earliest_consolidation_epoch = max( + state.earliest_consolidation_epoch, compute_activation_exit_epoch(get_current_epoch(state)) + ) + per_epoch_consolidation_churn = get_consolidation_churn_limit(state) + # New epoch for consolidations. + if state.earliest_consolidation_epoch < earliest_consolidation_epoch: + consolidation_balance_to_consume = per_epoch_consolidation_churn + else: + consolidation_balance_to_consume = state.consolidation_balance_to_consume + + # Consolidation doesn't fit in the current earliest epoch. + if consolidation_balance > consolidation_balance_to_consume: + balance_to_process = consolidation_balance - consolidation_balance_to_consume + additional_epochs = (balance_to_process - 1) // per_epoch_consolidation_churn + 1 + earliest_consolidation_epoch += additional_epochs + consolidation_balance_to_consume += additional_epochs * per_epoch_consolidation_churn + + # Consume the balance and update state variables. + state.consolidation_balance_to_consume = ( + consolidation_balance_to_consume - consolidation_balance + ) + state.earliest_consolidation_epoch = earliest_consolidation_epoch + + return state.earliest_consolidation_epoch +``` + +#### Modified `slash_validator` + +*Note*: The function `slash_validator` is modified to change how the slashing +penalty and proposer/whistleblower rewards are calculated in accordance with +EIP7251. + +```python +def slash_validator( + state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex = None +) -> None: + """ + Slash the validator with index ``slashed_index``. + """ + epoch = get_current_epoch(state) + initiate_validator_exit(state, slashed_index) + validator = state.validators[slashed_index] + validator.slashed = True + validator.withdrawable_epoch = max( + validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR) + ) + state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance + # [Modified in Electra:EIP7251] + slashing_penalty = validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA + decrease_balance(state, slashed_index, slashing_penalty) + + # Apply proposer and whistleblower rewards + proposer_index = get_beacon_proposer_index(state) + if whistleblower_index is None: + whistleblower_index = proposer_index + # [Modified in Electra:EIP7251] + whistleblower_reward = Gwei( + validator.effective_balance // WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA + ) + proposer_reward = Gwei(whistleblower_reward * PROPOSER_WEIGHT // WEIGHT_DENOMINATOR) + increase_balance(state, proposer_index, proposer_reward) + increase_balance(state, whistleblower_index, Gwei(whistleblower_reward - proposer_reward)) +``` + +## Beacon chain state transition function + +### Epoch processing + +#### Modified `process_epoch` + +*Note*: The function `process_epoch` is modified to call updated functions and +to process pending balance deposits and pending consolidations which are new in +Electra. + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + # [Modified in Electra:EIP7251] + process_registry_updates(state) + # [Modified in Electra:EIP7251] + process_slashings(state) + process_eth1_data_reset(state) + # [New in Electra:EIP7251] + process_pending_deposits(state) + # [New in Electra:EIP7251] + process_pending_consolidations(state) + # [Modified in Electra:EIP7251] + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_summaries_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) +``` + +#### Modified `process_registry_updates` + +*Note*: The function `process_registry_updates` is modified to use the updated +definitions of `initiate_validator_exit` and `is_eligible_for_activation_queue`, +changes how the activation epochs are computed for eligible validators, and +processes activations in the same loop as activation eligibility updates and +ejections. + +```python +def process_registry_updates(state: BeaconState) -> None: + current_epoch = get_current_epoch(state) + activation_epoch = compute_activation_exit_epoch(current_epoch) + + # Process activation eligibility, ejections, and activations + for index, validator in enumerate(state.validators): + if is_eligible_for_activation_queue(validator): # [Modified in Electra:EIP7251] + validator.activation_eligibility_epoch = current_epoch + 1 + elif ( + is_active_validator(validator, current_epoch) + and validator.effective_balance <= EJECTION_BALANCE + ): + initiate_validator_exit(state, ValidatorIndex(index)) # [Modified in Electra:EIP7251] + elif is_eligible_for_activation(state, validator): + validator.activation_epoch = activation_epoch +``` + +#### Modified `process_slashings` + +*Note*: The function `process_slashings` is modified to use a new algorithm to +compute correlation penalty. + +```python +def process_slashings(state: BeaconState) -> None: + epoch = get_current_epoch(state) + total_balance = get_total_active_balance(state) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, total_balance + ) + increment = ( + EFFECTIVE_BALANCE_INCREMENT # Factored out from total balance to avoid uint64 overflow + ) + penalty_per_effective_balance_increment = adjusted_total_slashing_balance // ( + total_balance // increment + ) + for index, validator in enumerate(state.validators): + if ( + validator.slashed + and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch + ): + effective_balance_increments = validator.effective_balance // increment + # [Modified in Electra:EIP7251] + penalty = penalty_per_effective_balance_increment * effective_balance_increments + decrease_balance(state, ValidatorIndex(index), penalty) +``` + +#### New `apply_pending_deposit` + +```python +def apply_pending_deposit(state: BeaconState, deposit: PendingDeposit) -> None: + """ + Applies ``deposit`` to the ``state``. + """ + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if is_valid_deposit_signature( + deposit.pubkey, deposit.withdrawal_credentials, deposit.amount, deposit.signature + ): + add_validator_to_registry( + state, deposit.pubkey, deposit.withdrawal_credentials, deposit.amount + ) + else: + validator_index = ValidatorIndex(validator_pubkeys.index(deposit.pubkey)) + increase_balance(state, validator_index, deposit.amount) +``` + +#### New `process_pending_deposits` + +Iterating over `pending_deposits` queue this function runs the following checks +before applying pending deposit: + +1. All Eth1 bridge deposits are processed before the first deposit request gets + processed. +2. Deposit position in the queue is finalized. +3. Deposit does not exceed the `MAX_PENDING_DEPOSITS_PER_EPOCH` limit. +4. Deposit does not exceed the activation churn limit. + +```python +def process_pending_deposits(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit( + state + ) + processed_amount = 0 + next_deposit_index = 0 + deposits_to_postpone = [] + is_churn_limit_reached = False + finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) + + for deposit in state.pending_deposits: + # Do not process deposit requests if Eth1 bridge deposits are not yet applied. + if ( + # Is deposit request + deposit.slot > GENESIS_SLOT + and + # There are pending Eth1 bridge deposits + state.eth1_deposit_index < state.deposit_requests_start_index + ): + break + + # Check if deposit has been finalized, otherwise, stop processing. + if deposit.slot > finalized_slot: + break + + # Check if number of processed deposits has not reached the limit, otherwise, stop processing. + if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH: + break + + # Read validator state + is_validator_exited = False + is_validator_withdrawn = False + validator_pubkeys = [v.pubkey for v in state.validators] + if deposit.pubkey in validator_pubkeys: + validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))] + is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH + is_validator_withdrawn = validator.withdrawable_epoch < next_epoch + + if is_validator_withdrawn: + # Deposited balance will never become active. Increase balance but do not consume churn + apply_pending_deposit(state, deposit) + elif is_validator_exited: + # Validator is exiting, postpone the deposit until after withdrawable epoch + deposits_to_postpone.append(deposit) + else: + # Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch. + is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing + if is_churn_limit_reached: + break + + # Consume churn and apply deposit. + processed_amount += deposit.amount + apply_pending_deposit(state, deposit) + + # Regardless of how the deposit was handled, we move on in the queue. + next_deposit_index += 1 + + state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone + + # Accumulate churn only if the churn limit has been hit. + if is_churn_limit_reached: + state.deposit_balance_to_consume = available_for_processing - processed_amount + else: + state.deposit_balance_to_consume = Gwei(0) +``` + +#### New `process_pending_consolidations` + +```python +def process_pending_consolidations(state: BeaconState) -> None: + next_epoch = Epoch(get_current_epoch(state) + 1) + next_pending_consolidation = 0 + for pending_consolidation in state.pending_consolidations: + source_validator = state.validators[pending_consolidation.source_index] + if source_validator.slashed: + next_pending_consolidation += 1 + continue + if source_validator.withdrawable_epoch > next_epoch: + break + + # Calculate the consolidated 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) + increase_balance(state, pending_consolidation.target_index, source_effective_balance) + next_pending_consolidation += 1 + + state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:] +``` + +#### Modified `process_effective_balance_updates` + +*Note*: The function `process_effective_balance_updates` is modified to use the +new limit for the maximum effective balance. + +```python +def process_effective_balance_updates(state: BeaconState) -> None: + # Update effective balances with hysteresis + for index, validator in enumerate(state.validators): + balance = state.balances[index] + HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER + UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER + # [Modified in Electra:EIP7251] + max_effective_balance = get_max_effective_balance(validator) + + if ( + balance + DOWNWARD_THRESHOLD < validator.effective_balance + or validator.effective_balance + UPWARD_THRESHOLD < balance + ): + validator.effective_balance = min( + balance - balance % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance + ) +``` + +### Execution engine + +#### Request data + +##### Modified `NewPayloadRequest` + +```python +@dataclass +class NewPayloadRequest(object): + execution_payload: ExecutionPayload + versioned_hashes: Sequence[VersionedHash] + parent_beacon_block_root: Root + # [New in Electra] + execution_requests: ExecutionRequests +``` + +#### 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_list`. + +```python +def notify_new_payload( + self: ExecutionEngine, + execution_payload: ExecutionPayload, + parent_beacon_block_root: Root, + execution_requests_list: Sequence[bytes], +) -> bool: + """ + Return ``True`` if and only if ``execution_payload`` and ``execution_requests_list`` + are valid with respect to ``self.execution_state``. + """ + ... +``` + +##### Modified `verify_and_notify_new_payload` + +*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, new_payload_request: NewPayloadRequest +) -> bool: + """ + Return ``True`` if and only if ``new_payload_request`` is valid with respect to ``self.execution_state``. + """ + execution_payload = new_payload_request.execution_payload + parent_beacon_block_root = new_payload_request.parent_beacon_block_root + # [New in Electra] + execution_requests_list = get_execution_requests_list(new_payload_request.execution_requests) + + if b"" in execution_payload.transactions: + return False + + # [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): + return False + + # [Modified in Electra] + if not self.notify_new_payload( + execution_payload, parent_beacon_block_root, execution_requests_list + ): + return False + + return True +``` + +### Block processing + +```python +def process_block(state: BeaconState, block: BeaconBlock) -> None: + process_block_header(state, block) + # [Modified in Electra:EIP7251] + process_withdrawals(state, block.body.execution_payload) + # [Modified in Electra:EIP6110] + process_execution_payload(state, block.body, EXECUTION_ENGINE) + process_randao(state, block.body) + process_eth1_data(state, block.body) + # [Modified in Electra:EIP6110:EIP7002:EIP7549:EIP7251] + process_operations(state, block.body) + process_sync_aggregate(state, block.body.sync_aggregate) +``` + +#### Withdrawals + +##### Modified `get_expected_withdrawals` + +*Note*: The function `get_expected_withdrawals` is modified to support EIP7251. + +```python +def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]: + epoch = get_current_epoch(state) + withdrawal_index = state.next_withdrawal_index + validator_index = state.next_withdrawal_validator_index + withdrawals: List[Withdrawal] = [] + processed_partial_withdrawals_count = 0 + + # [New in Electra:EIP7251] Consume pending partial withdrawals + for withdrawal in state.pending_partial_withdrawals: + if ( + withdrawal.withdrawable_epoch > epoch + or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + ): + break + + validator = state.validators[withdrawal.validator_index] + has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE + total_withdrawn = sum( + w.amount for w in withdrawals if w.validator_index == withdrawal.validator_index + ) + balance = state.balances[withdrawal.validator_index] - total_withdrawn + has_excess_balance = balance > MIN_ACTIVATION_BALANCE + if ( + validator.exit_epoch == FAR_FUTURE_EPOCH + and has_sufficient_effective_balance + and has_excess_balance + ): + withdrawable_balance = min(balance - MIN_ACTIVATION_BALANCE, withdrawal.amount) + withdrawals.append( + Withdrawal( + index=withdrawal_index, + validator_index=withdrawal.validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=withdrawable_balance, + ) + ) + withdrawal_index += WithdrawalIndex(1) + + processed_partial_withdrawals_count += 1 + + # Sweep for remaining. + bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) + for _ in range(bound): + validator = state.validators[validator_index] + # [Modified in Electra:EIP7251] + total_withdrawn = sum(w.amount for w in withdrawals if w.validator_index == validator_index) + balance = state.balances[validator_index] - total_withdrawn + if is_fully_withdrawable_validator(validator, balance, epoch): + withdrawals.append( + Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + amount=balance, + ) + ) + withdrawal_index += WithdrawalIndex(1) + elif is_partially_withdrawable_validator(validator, balance): + withdrawals.append( + Withdrawal( + index=withdrawal_index, + validator_index=validator_index, + address=ExecutionAddress(validator.withdrawal_credentials[12:]), + # [Modified in Electra:EIP7251] + amount=balance - get_max_effective_balance(validator), + ) + ) + withdrawal_index += WithdrawalIndex(1) + if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + break + validator_index = ValidatorIndex((validator_index + 1) % len(state.validators)) + return withdrawals, processed_partial_withdrawals_count +``` + +##### Modified `process_withdrawals` + +*Note*: The function `process_withdrawals` is modified to support EIP7251. + +```python +def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: + # [Modified in Electra:EIP7251] + expected_withdrawals, processed_partial_withdrawals_count = get_expected_withdrawals(state) + + assert payload.withdrawals == expected_withdrawals + + for withdrawal in expected_withdrawals: + decrease_balance(state, withdrawal.validator_index, withdrawal.amount) + + # [New in Electra:EIP7251] Update pending partial withdrawals + state.pending_partial_withdrawals = state.pending_partial_withdrawals[ + processed_partial_withdrawals_count: + ] + + # Update the next withdrawal index if this block contained withdrawals + if len(expected_withdrawals) != 0: + latest_withdrawal = expected_withdrawals[-1] + state.next_withdrawal_index = WithdrawalIndex(latest_withdrawal.index + 1) + + # Update the next validator index to start the next withdrawal sweep + if len(expected_withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: + # Next sweep starts after the latest withdrawal's validator index + next_validator_index = ValidatorIndex( + (expected_withdrawals[-1].validator_index + 1) % len(state.validators) + ) + state.next_withdrawal_validator_index = next_validator_index + else: + # Advance sweep by the max length of the sweep if there was not a full set of withdrawals + next_index = state.next_withdrawal_validator_index + MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + next_validator_index = ValidatorIndex(next_index % len(state.validators)) + state.next_withdrawal_validator_index = next_validator_index +``` + +#### Execution payload + +##### New `get_execution_requests_list` + +*Note*: Encodes execution requests as defined by +[EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). + +```python +def get_execution_requests_list(execution_requests: ExecutionRequests) -> Sequence[bytes]: + requests = [ + (DEPOSIT_REQUEST_TYPE, execution_requests.deposits), + (WITHDRAWAL_REQUEST_TYPE, execution_requests.withdrawals), + (CONSOLIDATION_REQUEST_TYPE, execution_requests.consolidations), + ] + + return [ + request_type + ssz_serialize(request_data) + for request_type, request_data in requests + if len(request_data) != 0 + ] +``` + +##### Modified `process_execution_payload` + +*Note*: The function `process_execution_payload` is modified to pass +`execution_requests` into `execution_engine.verify_and_notify_new_payload` (via +the updated `NewPayloadRequest`). + +```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_time_at_slot(state, state.slot) + # [Modified in Electra:EIP7691] Verify commitments are under limit + assert len(body.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK_ELECTRA + # 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, + # [New in Electra] + 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, + ) +``` + +#### Operations + +##### Modified `process_operations` + +*Note*: The function `process_operations` is modified to support all of the new +functionality in Electra. + +```python +def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: + # [Modified in Electra:EIP6110] + # Disable former deposit mechanism once all prior deposits are processed + eth1_deposit_index_limit = min( + state.eth1_data.deposit_count, state.deposit_requests_start_index + ) + if state.eth1_deposit_index < eth1_deposit_index_limit: + assert len(body.deposits) == min( + MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index + ) + else: + assert len(body.deposits) == 0 + + def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: + for operation in operations: + fn(state, operation) + + for_ops(body.proposer_slashings, process_proposer_slashing) + for_ops(body.attester_slashings, process_attester_slashing) + # [Modified in Electra:EIP7549] + for_ops(body.attestations, process_attestation) + for_ops(body.deposits, process_deposit) + # [Modified in Electra:EIP7251] + for_ops(body.voluntary_exits, process_voluntary_exit) + for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) + # [New in Electra:EIP6110] + for_ops(body.execution_requests.deposits, process_deposit_request) + # [New in Electra:EIP7002:EIP7251] + for_ops(body.execution_requests.withdrawals, process_withdrawal_request) + # [New in Electra:EIP7251] + for_ops(body.execution_requests.consolidations, process_consolidation_request) +``` + +##### Attestations + +###### Modified `process_attestation` + +*Note*: The function is modified to support EIP7549. + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot + + # [Modified in Electra:EIP7549] + assert data.index == 0 + committee_indices = get_committee_indices(attestation.committee_bits) + committee_offset = 0 + for committee_index in committee_indices: + assert committee_index < get_committee_count_per_slot(state, data.target.epoch) + committee = get_beacon_committee(state, data.slot, committee_index) + committee_attesters = set( + attester_index + for i, attester_index in enumerate(committee) + if attestation.aggregation_bits[committee_offset + i] + ) + assert len(committee_attesters) > 0 + committee_offset += len(committee) + + # Bitfield length matches total number of participants + assert len(attestation.aggregation_bits) == committee_offset + + # Participation flag indices + participation_flag_indices = get_attestation_participation_flag_indices( + state, data, state.slot - data.slot + ) + + # Verify signature + assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation)) + + # Update epoch participation flags + if data.target.epoch == get_current_epoch(state): + epoch_participation = state.current_epoch_participation + else: + epoch_participation = state.previous_epoch_participation + + proposer_reward_numerator = 0 + for index in get_attesting_indices(state, attestation): + for flag_index, weight in enumerate(PARTICIPATION_FLAG_WEIGHTS): + if flag_index in participation_flag_indices and not has_flag( + epoch_participation[index], flag_index + ): + epoch_participation[index] = add_flag(epoch_participation[index], flag_index) + proposer_reward_numerator += get_base_reward(state, index) * weight + + # Reward proposer + proposer_reward_denominator = ( + (WEIGHT_DENOMINATOR - PROPOSER_WEIGHT) * WEIGHT_DENOMINATOR // PROPOSER_WEIGHT + ) + proposer_reward = Gwei(proposer_reward_numerator // proposer_reward_denominator) + increase_balance(state, get_beacon_proposer_index(state), proposer_reward) +``` + +##### Deposits + +###### Modified `get_validator_from_deposit` + +*Note*: The function is modified to use `MAX_EFFECTIVE_BALANCE_ELECTRA` for +compounding withdrawal credential. + +```python +def get_validator_from_deposit( + pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> Validator: + validator = Validator( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + effective_balance=Gwei(0), + slashed=False, + activation_eligibility_epoch=FAR_FUTURE_EPOCH, + activation_epoch=FAR_FUTURE_EPOCH, + exit_epoch=FAR_FUTURE_EPOCH, + withdrawable_epoch=FAR_FUTURE_EPOCH, + ) + + # [Modified in Electra:EIP7251] + max_effective_balance = get_max_effective_balance(validator) + validator.effective_balance = min( + amount - amount % EFFECTIVE_BALANCE_INCREMENT, max_effective_balance + ) + + return validator +``` + +###### Modified `add_validator_to_registry` + +*Note*: The function `add_validator_to_registry` is modified to use the modified +`get_validator_from_deposit`. + +```python +def add_validator_to_registry( + state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> None: + index = get_index_for_new_validator(state) + # [Modified in Electra:EIP7251] + validator = get_validator_from_deposit(pubkey, withdrawal_credentials, amount) + set_or_append_list(state.validators, index, validator) + set_or_append_list(state.balances, index, amount) + set_or_append_list(state.previous_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.current_epoch_participation, index, ParticipationFlags(0b0000_0000)) + set_or_append_list(state.inactivity_scores, index, uint64(0)) +``` + +###### Modified `apply_deposit` + +*Note*: The function `apply_deposit` is modified to support EIP7251. + +```python +def apply_deposit( + state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64, + signature: BLSSignature, +) -> None: + validator_pubkeys = [v.pubkey for v in state.validators] + if pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature): + # [Modified in Electra:EIP7251] + add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) + else: + return + + # [Modified in Electra:EIP7251] + # Increase balance by deposit amount + state.pending_deposits.append( + PendingDeposit( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=signature, + slot=GENESIS_SLOT, # Use GENESIS_SLOT to distinguish from a pending deposit request + ) + ) +``` + +###### New `is_valid_deposit_signature` + +```python +def is_valid_deposit_signature( + pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64, signature: BLSSignature +) -> bool: + deposit_message = DepositMessage( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + ) + # Fork-agnostic domain since deposits are valid across forks + domain = compute_domain(DOMAIN_DEPOSIT) + signing_root = compute_signing_root(deposit_message, domain) + return bls.Verify(pubkey, signing_root, signature) +``` + +###### Modified `process_deposit` + +*Note*: The function `process_deposit` is modified to use the modified +`apply_deposit`. + +```python +def process_deposit(state: BeaconState, deposit: Deposit) -> None: + # Verify the Merkle branch + assert is_valid_merkle_branch( + leaf=hash_tree_root(deposit.data), + branch=deposit.proof, + # Add 1 for the List length mix-in + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, + index=state.eth1_deposit_index, + root=state.eth1_data.deposit_root, + ) + + # Deposits must be processed in order + state.eth1_deposit_index += 1 + + # [Modified in Electra:EIP7251] + apply_deposit( + state=state, + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + signature=deposit.data.signature, + ) +``` + +##### Voluntary exits + +###### Modified `process_voluntary_exit` + +*Note*: The function `process_voluntary_exit` is modified to ensure the +validator has no pending withdrawals in the queue. + +```python +def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVoluntaryExit) -> None: + voluntary_exit = signed_voluntary_exit.message + validator = state.validators[voluntary_exit.validator_index] + # Verify the validator is active + assert is_active_validator(validator, get_current_epoch(state)) + # Verify exit has not been initiated + assert validator.exit_epoch == FAR_FUTURE_EPOCH + # Exits must specify an epoch when they become valid; they are not valid before then + assert get_current_epoch(state) >= voluntary_exit.epoch + # Verify the validator has been active long enough + assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD + # [New in Electra:EIP7251] + # Only exit validator if it has no pending withdrawals in the queue + + assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 + # Verify signature + domain = compute_domain( + DOMAIN_VOLUNTARY_EXIT, CAPELLA_FORK_VERSION, state.genesis_validators_root + ) + signing_root = compute_signing_root(voluntary_exit, domain) + assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) + # Initiate exit + initiate_validator_exit(state, voluntary_exit.validator_index) +``` + +##### Execution layer withdrawal requests + +###### New `process_withdrawal_request` + +```python +def process_withdrawal_request(state: BeaconState, withdrawal_request: WithdrawalRequest) -> None: + amount = withdrawal_request.amount + is_full_exit_request = amount == FULL_EXIT_REQUEST_AMOUNT + + # If partial withdrawal queue is full, only full exits are processed + if ( + len(state.pending_partial_withdrawals) == PENDING_PARTIAL_WITHDRAWALS_LIMIT + and not is_full_exit_request + ): + return + + validator_pubkeys = [v.pubkey for v in state.validators] + # Verify pubkey exists + request_pubkey = withdrawal_request.validator_pubkey + if request_pubkey not in validator_pubkeys: + return + index = ValidatorIndex(validator_pubkeys.index(request_pubkey)) + validator = state.validators[index] + + # Verify withdrawal credentials + has_correct_credential = has_execution_withdrawal_credential(validator) + is_correct_source_address = ( + validator.withdrawal_credentials[12:] == withdrawal_request.source_address + ) + if not (has_correct_credential and is_correct_source_address): + return + # Verify the validator is active + if not is_active_validator(validator, get_current_epoch(state)): + return + # Verify exit has not been initiated + if validator.exit_epoch != FAR_FUTURE_EPOCH: + return + # Verify the validator has been active long enough + if get_current_epoch(state) < validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + + pending_balance_to_withdraw = get_pending_balance_to_withdraw(state, index) + + if is_full_exit_request: + # Only exit validator if it has no pending withdrawals in the queue + if pending_balance_to_withdraw == 0: + initiate_validator_exit(state, index) + return + + has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE + has_excess_balance = ( + state.balances[index] > MIN_ACTIVATION_BALANCE + pending_balance_to_withdraw + ) + + # Only allow partial withdrawals with compounding withdrawal credentials + if ( + has_compounding_withdrawal_credential(validator) + and has_sufficient_effective_balance + and has_excess_balance + ): + to_withdraw = min( + state.balances[index] - MIN_ACTIVATION_BALANCE - pending_balance_to_withdraw, amount + ) + 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( + validator_index=index, + amount=to_withdraw, + withdrawable_epoch=withdrawable_epoch, + ) + ) +``` + +##### Deposit requests + +###### New `process_deposit_request` + +```python +def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: + # Set deposit request start index + if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX: + state.deposit_requests_start_index = deposit_request.index + + # Create pending deposit + state.pending_deposits.append( + PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + ) + ) +``` + +##### Execution layer consolidation requests + +###### New `is_valid_switch_to_compounding_request` + +```python +def is_valid_switch_to_compounding_request( + state: BeaconState, consolidation_request: ConsolidationRequest +) -> bool: + # Switch to compounding requires source and target be equal + if consolidation_request.source_pubkey != consolidation_request.target_pubkey: + return False + + # Verify pubkey exists + source_pubkey = consolidation_request.source_pubkey + validator_pubkeys = [v.pubkey for v in state.validators] + if source_pubkey not in validator_pubkeys: + return False + + source_validator = state.validators[ValidatorIndex(validator_pubkeys.index(source_pubkey))] + + # Verify request has been authorized + if source_validator.withdrawal_credentials[12:] != consolidation_request.source_address: + return False + + # Verify source withdrawal credentials + if not has_eth1_withdrawal_credential(source_validator): + return False + + # Verify the source is active + current_epoch = get_current_epoch(state) + if not is_active_validator(source_validator, current_epoch): + return False + + # Verify exit for source has not been initiated + if source_validator.exit_epoch != FAR_FUTURE_EPOCH: + return False + + return True +``` + +###### New `process_consolidation_request` + +```python +def process_consolidation_request( + state: BeaconState, consolidation_request: ConsolidationRequest +) -> None: + if is_valid_switch_to_compounding_request(state, consolidation_request): + validator_pubkeys = [v.pubkey for v in state.validators] + request_source_pubkey = consolidation_request.source_pubkey + source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) + switch_to_compounding_validator(state, source_index) + return + + # Verify that source != target, so a consolidation cannot be used as an exit + if consolidation_request.source_pubkey == consolidation_request.target_pubkey: + return + # If the pending consolidations queue is full, consolidation requests are ignored + if len(state.pending_consolidations) == PENDING_CONSOLIDATIONS_LIMIT: + return + # If there is too little available consolidation churn limit, consolidation requests are ignored + if get_consolidation_churn_limit(state) <= MIN_ACTIVATION_BALANCE: + return + + validator_pubkeys = [v.pubkey for v in state.validators] + # Verify pubkeys exists + request_source_pubkey = consolidation_request.source_pubkey + request_target_pubkey = consolidation_request.target_pubkey + if request_source_pubkey not in validator_pubkeys: + return + if request_target_pubkey not in validator_pubkeys: + return + source_index = ValidatorIndex(validator_pubkeys.index(request_source_pubkey)) + target_index = ValidatorIndex(validator_pubkeys.index(request_target_pubkey)) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + + # Verify source withdrawal credentials + has_correct_credential = has_execution_withdrawal_credential(source_validator) + is_correct_source_address = ( + source_validator.withdrawal_credentials[12:] == consolidation_request.source_address + ) + if not (has_correct_credential and is_correct_source_address): + return + + # Verify that target has compounding withdrawal credentials + if not has_compounding_withdrawal_credential(target_validator): + return + + # Verify the source and the target are active + current_epoch = get_current_epoch(state) + if not is_active_validator(source_validator, current_epoch): + return + if not is_active_validator(target_validator, current_epoch): + return + # Verify exits for source and target have not been initiated + if source_validator.exit_epoch != FAR_FUTURE_EPOCH: + return + if target_validator.exit_epoch != FAR_FUTURE_EPOCH: + return + # Verify the source has been active long enough + if current_epoch < source_validator.activation_epoch + SHARD_COMMITTEE_PERIOD: + return + # Verify the source has no pending withdrawals in the queue + if get_pending_balance_to_withdraw(state, source_index) > 0: + return + + # Initiate source validator exit and append pending consolidation + source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn( + state, source_validator.effective_balance + ) + source_validator.withdrawable_epoch = Epoch( + source_validator.exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + state.pending_consolidations.append( + PendingConsolidation(source_index=source_index, target_index=target_index) + ) +``` diff --git a/specs/electra/fork.md b/specs/electra/fork.md new file mode 100644 index 0000000000..1d24925405 --- /dev/null +++ b/specs/electra/fork.md @@ -0,0 +1,171 @@ +# Electra -- Fork Logic + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) +- [Fork to Electra](#fork-to-electra) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of the Electra upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ---------------------- | --------------------------------------------- | +| `ELECTRA_FORK_VERSION` | `Version('0x05000000')` | +| `ELECTRA_FORK_EPOCH` | `Epoch(364032)` (May 7, 2025, 10:05:11am UTC) | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +## Fork to Electra + +### Fork trigger + +The fork is triggered at epoch `ELECTRA_FORK_EPOCH`. + +Note that for the pure Electra networks, we don't apply `upgrade_to_electra` +since it starts with Electra version logic. + +### Upgrading the state + +If `state.slot % SLOTS_PER_EPOCH == 0` and +`compute_epoch_at_slot(state.slot) == ELECTRA_FORK_EPOCH`, an irregular state +change is made to upgrade to Electra. + +```python +def upgrade_to_electra(pre: deneb.BeaconState) -> BeaconState: + epoch = deneb.get_current_epoch(pre) + + earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(pre)) + for validator in pre.validators: + if validator.exit_epoch != FAR_FUTURE_EPOCH: + if validator.exit_epoch > earliest_exit_epoch: + earliest_exit_epoch = validator.exit_epoch + earliest_exit_epoch += Epoch(1) + + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in Electra] + current_version=ELECTRA_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=pre.latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + # [New in Electra:EIP6110] + deposit_requests_start_index=UNSET_DEPOSIT_REQUESTS_START_INDEX, + # [New in Electra:EIP7251] + deposit_balance_to_consume=0, + # [New in Electra:EIP7251] + exit_balance_to_consume=0, + # [New in Electra:EIP7251] + earliest_exit_epoch=earliest_exit_epoch, + # [New in Electra:EIP7251] + consolidation_balance_to_consume=0, + # [New in Electra:EIP7251] + earliest_consolidation_epoch=compute_activation_exit_epoch(get_current_epoch(pre)), + # [New in Electra:EIP7251] + pending_deposits=[], + # [New in Electra:EIP7251] + pending_partial_withdrawals=[], + # [New in Electra:EIP7251] + pending_consolidations=[], + ) + + post.exit_balance_to_consume = get_activation_exit_churn_limit(post) + post.consolidation_balance_to_consume = get_consolidation_churn_limit(post) + + # [New in Electra:EIP7251] + # add validators that are not yet active to pending balance deposits + pre_activation = sorted( + [ + index + for index, validator in enumerate(post.validators) + if validator.activation_epoch == FAR_FUTURE_EPOCH + ], + key=lambda index: (post.validators[index].activation_eligibility_epoch, index), + ) + + for index in pre_activation: + balance = post.balances[index] + post.balances[index] = 0 + validator = post.validators[index] + validator.effective_balance = 0 + validator.activation_eligibility_epoch = FAR_FUTURE_EPOCH + # Use bls.G2_POINT_AT_INFINITY as a signature field placeholder + # and GENESIS_SLOT to distinguish from a pending deposit request + post.pending_deposits.append( + PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=balance, + signature=bls.G2_POINT_AT_INFINITY, + slot=GENESIS_SLOT, + ) + ) + + # Ensure early adopters of compounding credentials go through the activation churn + for index, validator in enumerate(post.validators): + if has_compounding_withdrawal_credential(validator): + queue_excess_active_balance(post, ValidatorIndex(index)) + + return post +``` diff --git a/specs/electra/light-client/fork.md b/specs/electra/light-client/fork.md new file mode 100644 index 0000000000..dd18f5e9d4 --- /dev/null +++ b/specs/electra/light-client/fork.md @@ -0,0 +1,120 @@ +# Electra Light Client -- Fork Logic + + + +- [Introduction](#introduction) +- [Helper functions](#helper-functions) + - [`normalize_merkle_branch`](#normalize_merkle_branch) +- [Upgrading light client data](#upgrading-light-client-data) +- [Upgrading the store](#upgrading-the-store) + + + +## Introduction + +This document describes how to upgrade existing light client objects based on +the [Deneb specification](../../deneb/light-client/sync-protocol.md) to Electra. +This is necessary when processing pre-Electra data with a post-Electra +`LightClientStore`. Note that the data being exchanged over the network +protocols uses the original format. + +## Helper functions + +### `normalize_merkle_branch` + +```python +def normalize_merkle_branch( + branch: Sequence[Bytes32], gindex: GeneralizedIndex +) -> Sequence[Bytes32]: + depth = floorlog2(gindex) + num_extra = depth - len(branch) + return [Bytes32()] * num_extra + [*branch] +``` + +## Upgrading light client data + +An Electra `LightClientStore` can still process earlier light client data. In +order to do so, that pre-Electra data needs to be locally upgraded to Electra +before processing. + +```python +def upgrade_lc_header_to_electra(pre: deneb.LightClientHeader) -> LightClientHeader: + return LightClientHeader( + beacon=pre.beacon, + execution=pre.execution, + execution_branch=pre.execution_branch, + ) +``` + +```python +def upgrade_lc_bootstrap_to_electra(pre: deneb.LightClientBootstrap) -> LightClientBootstrap: + return LightClientBootstrap( + header=upgrade_lc_header_to_electra(pre.header), + current_sync_committee=pre.current_sync_committee, + current_sync_committee_branch=normalize_merkle_branch( + pre.current_sync_committee_branch, CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA + ), + ) +``` + +```python +def upgrade_lc_update_to_electra(pre: deneb.LightClientUpdate) -> LightClientUpdate: + return LightClientUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + next_sync_committee=pre.next_sync_committee, + next_sync_committee_branch=normalize_merkle_branch( + pre.next_sync_committee_branch, NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA + ), + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch=normalize_merkle_branch(pre.finality_branch, FINALIZED_ROOT_GINDEX_ELECTRA), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_finality_update_to_electra( + pre: deneb.LightClientFinalityUpdate, +) -> LightClientFinalityUpdate: + return LightClientFinalityUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + finality_branch=normalize_merkle_branch(pre.finality_branch, FINALIZED_ROOT_GINDEX_ELECTRA), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +```python +def upgrade_lc_optimistic_update_to_electra( + pre: deneb.LightClientOptimisticUpdate, +) -> LightClientOptimisticUpdate: + return LightClientOptimisticUpdate( + attested_header=upgrade_lc_header_to_electra(pre.attested_header), + sync_aggregate=pre.sync_aggregate, + signature_slot=pre.signature_slot, + ) +``` + +## Upgrading the store + +Existing `LightClientStore` objects based on Deneb MUST be upgraded to Electra +before Electra based light client data can be processed. The `LightClientStore` +upgrade MAY be performed before `ELECTRA_FORK_EPOCH`. + +```python +def upgrade_lc_store_to_electra(pre: deneb.LightClientStore) -> LightClientStore: + if pre.best_valid_update is None: + best_valid_update = None + else: + best_valid_update = upgrade_lc_update_to_electra(pre.best_valid_update) + return LightClientStore( + finalized_header=upgrade_lc_header_to_electra(pre.finalized_header), + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + best_valid_update=best_valid_update, + optimistic_header=upgrade_lc_header_to_electra(pre.optimistic_header), + previous_max_active_participants=pre.previous_max_active_participants, + current_max_active_participants=pre.current_max_active_participants, + ) +``` diff --git a/specs/electra/light-client/p2p-interface.md b/specs/electra/light-client/p2p-interface.md new file mode 100644 index 0000000000..0f283f3690 --- /dev/null +++ b/specs/electra/light-client/p2p-interface.md @@ -0,0 +1,106 @@ +# Electra Light Client -- Networking + + + +- [Networking](#networking) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`light_client_finality_update`](#light_client_finality_update) + - [`light_client_optimistic_update`](#light_client_optimistic_update) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [GetLightClientBootstrap](#getlightclientbootstrap) + - [LightClientUpdatesByRange](#lightclientupdatesbyrange) + - [GetLightClientFinalityUpdate](#getlightclientfinalityupdate) + - [GetLightClientOptimisticUpdate](#getlightclientoptimisticupdate) + + + +## Networking + +The +[Deneb light client networking specification](../../deneb/light-client/p2p-interface.md) +is extended to exchange [Electra light client data](./sync-protocol.md). + +### The gossip domain: gossipsub + +#### Topics and messages + +##### Global topics + +###### `light_client_finality_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientFinalityUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientFinalityUpdate` | + +###### `light_client_optimistic_update` + + + +| `fork_version` | Message SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientOptimisticUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientOptimisticUpdate` | + +### The Req/Resp domain + +#### Messages + +##### GetLightClientBootstrap + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------ | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientBootstrap` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientBootstrap` | +| `DENEB_FORK_VERSION` | `deneb.LightClientBootstrap` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientBootstrap` | + +##### LightClientUpdatesByRange + + + +| `fork_version` | Response chunk SSZ type | +| ------------------------------------------------------ | --------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientUpdate` | + +##### GetLightClientFinalityUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ----------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientFinalityUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientFinalityUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientFinalityUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientFinalityUpdate` | + +##### GetLightClientOptimisticUpdate + + + +| `fork_version` | Response SSZ type | +| ------------------------------------------------------ | ------------------------------------- | +| `GENESIS_FORK_VERSION` | n/a | +| `ALTAIR_FORK_VERSION` through `BELLATRIX_FORK_VERSION` | `altair.LightClientOptimisticUpdate` | +| `CAPELLA_FORK_VERSION` | `capella.LightClientOptimisticUpdate` | +| `DENEB_FORK_VERSION` | `deneb.LightClientOptimisticUpdate` | +| `ELECTRA_FORK_VERSION` and later | `electra.LightClientOptimisticUpdate` | diff --git a/specs/electra/light-client/sync-protocol.md b/specs/electra/light-client/sync-protocol.md new file mode 100644 index 0000000000..fdc43eb83e --- /dev/null +++ b/specs/electra/light-client/sync-protocol.md @@ -0,0 +1,152 @@ +# Electra Light Client -- Sync Protocol + + + +- [Introduction](#introduction) +- [Custom types](#custom-types) +- [Constants](#constants) + - [Frozen constants](#frozen-constants) + - [New constants](#new-constants) +- [Helper functions](#helper-functions) + - [Modified `finalized_root_gindex_at_slot`](#modified-finalized_root_gindex_at_slot) + - [Modified `current_sync_committee_gindex_at_slot`](#modified-current_sync_committee_gindex_at_slot) + - [Modified `next_sync_committee_gindex_at_slot`](#modified-next_sync_committee_gindex_at_slot) + - [Modified `get_lc_execution_root`](#modified-get_lc_execution_root) + + + +## Introduction + +This upgrade updates light client data to include the Electra changes to the +[`ExecutionPayload`](../beacon-chain.md) structure and to the generalized +indices of surrounding containers. It extends the +[Deneb Light Client specifications](../../deneb/light-client/sync-protocol.md). +The [fork document](./fork.md) explains how to upgrade existing Deneb based +deployments to Electra. + +Additional documents describes the impact of the upgrade on certain roles: + +- [Networking](./p2p-interface.md) + +## Custom types + +| Name | SSZ equivalent | Description | +| ---------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------- | +| `FinalityBranch` | `Vector[Bytes32, floorlog2(FINALIZED_ROOT_GINDEX_ELECTRA)]` | Merkle branch of `finalized_checkpoint.root` within `BeaconState` | +| `CurrentSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `current_sync_committee` within `BeaconState` | +| `NextSyncCommitteeBranch` | `Vector[Bytes32, floorlog2(NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA)]` | Merkle branch of `next_sync_committee` within `BeaconState` | + +## Constants + +### Frozen constants + +Existing `GeneralizedIndex` constants are frozen at their +[Altair](../../altair/light-client/sync-protocol.md#constants) values. + +| Name | Value | +| ------------------------------- | ----------------------------------------------------------------------------------- | +| `FINALIZED_ROOT_GINDEX` | `get_generalized_index(altair.BeaconState, 'finalized_checkpoint', 'root')` (= 105) | +| `CURRENT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'current_sync_committee')` (= 54) | +| `NEXT_SYNC_COMMITTEE_GINDEX` | `get_generalized_index(altair.BeaconState, 'next_sync_committee')` (= 55) | + +### New constants + +| Name | Value | +| --------------------------------------- | ---------------------------------------------------------------------------- | +| `FINALIZED_ROOT_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'finalized_checkpoint', 'root')` (= 169) | +| `CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'current_sync_committee')` (= 86) | +| `NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA` | `get_generalized_index(BeaconState, 'next_sync_committee')` (= 87) | + +## Helper functions + +### Modified `finalized_root_gindex_at_slot` + +```python +def finalized_root_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return FINALIZED_ROOT_GINDEX_ELECTRA + return FINALIZED_ROOT_GINDEX +``` + +### Modified `current_sync_committee_gindex_at_slot` + +```python +def current_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA + return CURRENT_SYNC_COMMITTEE_GINDEX +``` + +### Modified `next_sync_committee_gindex_at_slot` + +```python +def next_sync_committee_gindex_at_slot(slot: Slot) -> GeneralizedIndex: + epoch = compute_epoch_at_slot(slot) + + # [Modified in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA + return NEXT_SYNC_COMMITTEE_GINDEX +``` + +### Modified `get_lc_execution_root` + +```python +def get_lc_execution_root(header: LightClientHeader) -> Root: + epoch = compute_epoch_at_slot(header.beacon.slot) + + # [New in Electra] + if epoch >= ELECTRA_FORK_EPOCH: + return hash_tree_root(header.execution) + + # [Modified in Electra] + if epoch >= DENEB_FORK_EPOCH: + execution_header = deneb.ExecutionPayloadHeader( + parent_hash=header.execution.parent_hash, + fee_recipient=header.execution.fee_recipient, + state_root=header.execution.state_root, + receipts_root=header.execution.receipts_root, + logs_bloom=header.execution.logs_bloom, + prev_randao=header.execution.prev_randao, + block_number=header.execution.block_number, + gas_limit=header.execution.gas_limit, + gas_used=header.execution.gas_used, + timestamp=header.execution.timestamp, + extra_data=header.execution.extra_data, + base_fee_per_gas=header.execution.base_fee_per_gas, + block_hash=header.execution.block_hash, + transactions_root=header.execution.transactions_root, + withdrawals_root=header.execution.withdrawals_root, + blob_gas_used=header.execution.blob_gas_used, + excess_blob_gas=header.execution.excess_blob_gas, + ) + return hash_tree_root(execution_header) + + if epoch >= CAPELLA_FORK_EPOCH: + execution_header = capella.ExecutionPayloadHeader( + parent_hash=header.execution.parent_hash, + fee_recipient=header.execution.fee_recipient, + state_root=header.execution.state_root, + receipts_root=header.execution.receipts_root, + logs_bloom=header.execution.logs_bloom, + prev_randao=header.execution.prev_randao, + block_number=header.execution.block_number, + gas_limit=header.execution.gas_limit, + gas_used=header.execution.gas_used, + timestamp=header.execution.timestamp, + extra_data=header.execution.extra_data, + base_fee_per_gas=header.execution.base_fee_per_gas, + block_hash=header.execution.block_hash, + transactions_root=header.execution.transactions_root, + withdrawals_root=header.execution.withdrawals_root, + ) + return hash_tree_root(execution_header) + + return Root() +``` diff --git a/specs/electra/p2p-interface.md b/specs/electra/p2p-interface.md new file mode 100644 index 0000000000..b6b9f0f24c --- /dev/null +++ b/specs/electra/p2p-interface.md @@ -0,0 +1,215 @@ +# Electra -- Networking + + + +- [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_block`](#beacon_block) + - [`beacon_aggregate_and_proof`](#beacon_aggregate_and_proof) + - [`blob_sidecar_{subnet_id}`](#blob_sidecar_subnet_id) + - [Attestation subnets](#attestation-subnets) + - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) + - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) + - [BlobSidecarsByRange v1](#blobsidecarsbyrange-v1) + - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1) + + + +## 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. + +#### Topics and messages + +Topics follow the same specification as in prior upgrades. + +The `beacon_block` topic is modified to also support Electra blocks. + +The `beacon_aggregate_and_proof` and `beacon_attestation_{subnet_id}` topics are +modified to support the gossip of the new attestation type. + +The `attester_slashing` topic is modified to support the gossip of the new +`AttesterSlashing` type. + +The specification around the creation, validation, and dissemination of messages +has not changed from the Capella document unless explicitly noted here. + +The derivation of the `message-id` remains stable. + +##### Global topics + +###### `beacon_block` + +*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(signed_beacon_block.message.body.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]` + +The following validations are added: + +- [REJECT] `len(committee_indices) == 1`, where + `committee_indices = get_committee_indices(aggregate)`. +- [REJECT] `aggregate.data.index == 0` + +###### `blob_sidecar_{subnet_id}` + +*[Modified in Electra:EIP7691]* + +The existing validations all apply as given from previous forks, with the +following exceptions: + +- Uses of `MAX_BLOBS_PER_BLOCK` in existing validations are replaced with + `MAX_BLOBS_PER_BLOCK_ELECTRA`. + +##### Attestation subnets + +###### `beacon_attestation_{subnet_id}` + +The topic is updated to propagate `SingleAttestation` objects. + +The following convenience variables are re-defined: + +- `index = attestation.committee_index` + +The following validations are added: + +- _[REJECT]_ `attestation.data.index == 0` +- _[REJECT]_ The attester is a member of the committee -- i.e. + `attestation.attester_index in get_beacon_committee(state, attestation.data.slot, index)`. + +The following validations are removed: + +- _[REJECT]_ The attestation is unaggregated -- 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 + +##### BeaconBlocksByRange v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` + +The Electra fork-digest is introduced to the `context` enum to specify Electra +beacon block type. + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` | + +##### BeaconBlocksByRoot v2 + +**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` + +Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: + + + +| `fork_version` | Chunk SSZ type | +| ------------------------ | ----------------------------- | +| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | +| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | +| `BELLATRIX_FORK_VERSION` | `bellatrix.SignedBeaconBlock` | +| `CAPELLA_FORK_VERSION` | `capella.SignedBeaconBlock` | +| `DENEB_FORK_VERSION` | `deneb.SignedBeaconBlock` | +| `ELECTRA_FORK_VERSION` | `electra.SignedBeaconBlock` | + +##### BlobSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/1/` + +*[Modified in Electra:EIP7691]* + +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. + +##### BlobSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/` + +*[Modified in Electra:EIP7691]* + +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. diff --git a/specs/electra/validator.md b/specs/electra/validator.md new file mode 100644 index 0000000000..438a42fc51 --- /dev/null +++ b/specs/electra/validator.md @@ -0,0 +1,345 @@ +# Electra -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Helpers](#helpers) + - [Modified `GetPayloadResponse`](#modified-getpayloadresponse) +- [Containers](#containers) + - [Modified containers](#modified-containers) + - [`AggregateAndProof`](#aggregateandproof) + - [`SignedAggregateAndProof`](#signedaggregateandproof) +- [Protocol](#protocol) + - [`ExecutionEngine`](#executionengine) + - [Modified `get_payload`](#modified-get_payload) +- [Block proposal](#block-proposal) + - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) + - [Attester slashings](#attester-slashings) + - [Attestations](#attestations) + - [Deposits](#deposits) + - [Execution payload](#execution-payload) + - [Execution Requests](#execution-requests) + - [Constructing the `BlobSidecar`s](#constructing-the-blobsidecars) + - [Sidecar](#sidecar) +- [Attesting](#attesting) + - [Construct attestation](#construct-attestation) +- [Attestation aggregation](#attestation-aggregation) + - [Construct aggregate](#construct-aggregate) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement Electra. + +## Prerequisites + +This document is an extension of the +[Deneb -- Honest Validator](../deneb/validator.md) guide. All behaviors and +definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in the +updated Beacon Chain doc of [Electra](./beacon-chain.md) are requisite for this +document and used throughout. Please see related Beacon Chain doc before +continuing and use them as a reference throughout. + +## Helpers + +### Modified `GetPayloadResponse` + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload + block_value: uint256 + blobs_bundle: BlobsBundle + execution_requests: Sequence[bytes] # [New in Electra] +``` + +## Containers + +### Modified containers + +#### `AggregateAndProof` + +```python +class AggregateAndProof(Container): + aggregator_index: ValidatorIndex + # [Modified in Electra:EIP7549] + aggregate: Attestation + selection_proof: BLSSignature +``` + +#### `SignedAggregateAndProof` + +```python +class SignedAggregateAndProof(Container): + # [Modified in Electra:EIP7549] + message: AggregateAndProof + signature: BLSSignature +``` + +## Protocol + +### `ExecutionEngine` + +#### Modified `get_payload` + +Given the `payload_id`, `get_payload` returns the most recent version of the +execution payload that has been built since the corresponding call to +`notify_forkchoice_updated` method. + +```python +def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + """ + Return ExecutionPayload, uint256, BlobsBundle and execution requests (as Sequence[bytes]) objects. + """ + # pylint: disable=unused-argument + ... +``` + +## Block proposal + +### Constructing the `BeaconBlockBody` + +#### Attester slashings + +Changed the max attester slashings size to `MAX_ATTESTER_SLASHINGS_ELECTRA`. + +#### Attestations + +Changed the max attestations size to `MAX_ATTESTATIONS_ELECTRA`. + +The network attestation aggregates contain only the assigned committee +attestations. Attestation aggregates received by the block proposer from the +committee aggregators with disjoint `committee_bits` sets and equal +`AttestationData` SHOULD be consolidated into a single `Attestation` object. The +proposer should run the following function to construct an on chain final +aggregate from a list of network aggregates with equal `AttestationData`: + +```python +def compute_on_chain_aggregate(network_aggregates: Sequence[Attestation]) -> Attestation: + aggregates = sorted( + network_aggregates, key=lambda a: get_committee_indices(a.committee_bits)[0] + ) + + data = aggregates[0].data + aggregation_bits = Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]() + for a in aggregates: + for b in a.aggregation_bits: + aggregation_bits.append(b) + + signature = bls.Aggregate([a.signature for a in aggregates]) + + committee_indices = [get_committee_indices(a.committee_bits)[0] for a in aggregates] + committee_flags = [(index in committee_indices) for index in range(0, MAX_COMMITTEES_PER_SLOT)] + committee_bits = Bitvector[MAX_COMMITTEES_PER_SLOT](committee_flags) + + return Attestation( + aggregation_bits=aggregation_bits, + data=data, + committee_bits=committee_bits, + signature=signature, + ) +``` + +#### Deposits + +*[New in Electra:EIP6110]* The expected number of deposits MUST be changed from +`min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)` to the +result of the following function: + +```python +def get_eth1_pending_deposit_count(state: BeaconState) -> uint64: + eth1_deposit_index_limit = min( + state.eth1_data.deposit_count, state.deposit_requests_start_index + ) + if state.eth1_deposit_index < eth1_deposit_index_limit: + return min(MAX_DEPOSITS, eth1_deposit_index_limit - state.eth1_deposit_index) + else: + return uint64(0) +``` + +*Note*: Clients will be able to remove the `Eth1Data` polling mechanism in an +uncoordinated fashion once the transition period is finished. The transition +period is considered finished when a network reaches the point where +`state.eth1_deposit_index == state.deposit_requests_start_index`. + +```python +def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data: + # [New in Electra:EIP6110] + if state.eth1_deposit_index == state.deposit_requests_start_index: + return state.eth1_data + + period_start = voting_period_start_time(state) + # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height + votes_to_consider = [ + get_eth1_data(block) + for block in eth1_chain + if ( + is_candidate_block(block, period_start) + # Ensure cannot move back to earlier deposit contract states + and get_eth1_data(block).deposit_count >= state.eth1_data.deposit_count + ) + ] + + # Valid votes already cast during this period + valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] + + # Default vote on latest eth1 block data in the period range unless eth1 chain is not live + # Non-substantive casting for linter + state_eth1_data: Eth1Data = state.eth1_data + default_vote = ( + votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + ) + + return max( + valid_votes, + # Tiebreak by smallest distance + key=lambda v: ( + valid_votes.count(v), + -valid_votes.index(v), + ), + default=default_vote, + ) +``` + +#### Execution payload + +`prepare_execution_payload` is updated from the Deneb specs. + +*Note*: In this section, `state` is the state of the slot for the block proposal +_without_ the block yet applied. That is, `state` is the `previous_state` +processed through any empty slots up to the assigned slot using +`process_slots(previous_state, slot)`. + +*Note*: The only change to `prepare_execution_payload` is the new definition of +`get_expected_withdrawals`. + +```python +def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, +) -> Optional[PayloadId]: + # Verify consistency of the parent hash with respect to the previous execution payload header + parent_hash = state.latest_execution_payload_header.block_hash + + # Set the forkchoice head and initiate the payload build process + withdrawals, _ = get_expected_withdrawals(state) # [Modified in EIP-7251] + + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=withdrawals, + parent_beacon_block_root=hash_tree_root(state.latest_block_header), + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=parent_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) +``` + +#### Execution Requests + +*[New in Electra]* + +1. The execution payload is obtained from the execution engine as defined above + using `payload_id`. The response also includes a `execution_requests` entry + containing a list of bytes. Each element on the list corresponds to one SSZ + list of requests as defined in + [EIP-7685](https://eips.ethereum.org/EIPS/eip-7685). The first byte of each + request is used to determine the request type. Requests must be ordered by + request type in ascending order. As a result, there can only be at most one + instance of each request type. +2. Set + `block.body.execution_requests = get_execution_requests(execution_requests)`, + where: + +```python +def get_execution_requests(execution_requests_list: Sequence[bytes]) -> ExecutionRequests: + deposits = [] + withdrawals = [] + consolidations = [] + + request_types = [ + DEPOSIT_REQUEST_TYPE, + WITHDRAWAL_REQUEST_TYPE, + CONSOLIDATION_REQUEST_TYPE, + ] + + prev_request_type = None + for request in execution_requests_list: + request_type, request_data = request[0:1], request[1:] + + # Check that the request type is valid + assert request_type in request_types + # Check that the request data is not empty + assert len(request_data) != 0 + # Check that requests are in strictly ascending order + # Each successive type must be greater than the last with no duplicates + assert prev_request_type is None or prev_request_type < request_type + prev_request_type = request_type + + if request_type == DEPOSIT_REQUEST_TYPE: + deposits = ssz_deserialize( + List[DepositRequest, MAX_DEPOSIT_REQUESTS_PER_PAYLOAD], request_data + ) + elif request_type == WITHDRAWAL_REQUEST_TYPE: + withdrawals = ssz_deserialize( + List[WithdrawalRequest, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD], request_data + ) + elif request_type == CONSOLIDATION_REQUEST_TYPE: + consolidations = ssz_deserialize( + List[ConsolidationRequest, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD], request_data + ) + + return ExecutionRequests( + deposits=deposits, + withdrawals=withdrawals, + consolidations=consolidations, + ) +``` + +### Constructing the `BlobSidecar`s + +#### Sidecar + +*[Modified in Electra:EIP7691]* + +```python +def compute_subnet_for_blob_sidecar(blob_index: BlobIndex) -> SubnetID: + return SubnetID(blob_index % BLOB_SIDECAR_SUBNET_COUNT_ELECTRA) +``` + +## Attesting + +### Construct attestation + +The validator creates `attestation` as a `SingleAttestation` container with +updated field assignments: + +- Set `attestation_data.index = 0`. +- Set `attestation.committee_index` to the index associated with the validator's + committee. +- Set `attestation.attester_index` to the index of the validator. + +## Attestation aggregation + +### Construct aggregate + +- Set `attestation_data.index = 0`. +- Let `aggregation_bits` be a + `Bitlist[MAX_VALIDATORS_PER_COMMITTEE * MAX_COMMITTEES_PER_SLOT]` of length + `len(committee)`, where each bit set from each individual attestation is set + to `0b1`. +- Set `attestation.committee_bits = committee_bits`, where `committee_bits` has + the bit set corresponding to `committee_index` in each individual attestation. diff --git a/specs/electra/weak-subjectivity.md b/specs/electra/weak-subjectivity.md new file mode 100644 index 0000000000..1c932b947c --- /dev/null +++ b/specs/electra/weak-subjectivity.md @@ -0,0 +1,72 @@ +# Electra -- Weak Subjectivity Guide + + + +- [Introduction](#introduction) +- [Weak Subjectivity Period](#weak-subjectivity-period) + - [Calculating the Weak Subjectivity Period](#calculating-the-weak-subjectivity-period) + - [Modified `compute_weak_subjectivity_period`](#modified-compute_weak_subjectivity_period) + - [Modified `is_within_weak_subjectivity_period`](#modified-is_within_weak_subjectivity_period) + + + +## Introduction + +This document is an extension of the +[Phase 0 -- Weak Subjectivity Guide](../phase0/weak-subjectivity.md). All +behaviors and definitions defined in this document, and documents it extends, +carry over unless explicitly noted or overridden. + +This document is a guide for implementing Weak Subjectivity protections in +Electra. The Weak Subjectivity Period (WSP) calculations have changed in Electra +due to EIP-7251, which increases the maximum effective balance for validators +and allows validators to consolidate. + +## Weak Subjectivity Period + +### Calculating the Weak Subjectivity Period + +#### Modified `compute_weak_subjectivity_period` + +```python +def compute_weak_subjectivity_period(state: BeaconState) -> uint64: + """ + Returns the weak subjectivity period for the current ``state``. + This computation takes into account the effect of: + - validator set churn (bounded by ``get_balance_churn_limit()`` per epoch) + A detailed calculation can be found at: + https://notes.ethereum.org/@CarlBeek/electra_weak_subjectivity + """ + t = get_total_active_balance(state) + delta = get_balance_churn_limit(state) + epochs_for_validator_set_churn = SAFETY_DECAY * t // (2 * delta * 100) + return MIN_VALIDATOR_WITHDRAWABILITY_DELAY + epochs_for_validator_set_churn +``` + +A brief reference for what these values look like in practice +([reference script](https://gist.github.com/jtraglia/457fd9ae7d2080fef1e4034a39b80c46)): + +| Safety Decay | Total Active Balance (ETH) | Weak Sub. Period (Epochs) | +| -----------: | -------------------------: | ------------------------: | +| 10 | 1,048,576 | 665 | +| 10 | 2,097,152 | 1,075 | +| 10 | 4,194,304 | 1,894 | +| 10 | 8,388,608 | 3,532 | +| 10 | 16,777,216 | 3,532 | +| 10 | 33,554,432 | 3,532 | + +#### Modified `is_within_weak_subjectivity_period` + +```python +def is_within_weak_subjectivity_period( + store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint +) -> bool: + # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint + assert ws_state.latest_block_header.state_root == ws_checkpoint.root + assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch + + ws_period = compute_weak_subjectivity_period(ws_state) # [Modified in Electra] + ws_state_epoch = compute_epoch_at_slot(ws_state.slot) + current_epoch = compute_epoch_at_slot(get_current_slot(store)) + return current_epoch <= ws_state_epoch + ws_period +``` diff --git a/specs/fulu/beacon-chain.md b/specs/fulu/beacon-chain.md new file mode 100644 index 0000000000..71105a0d6c --- /dev/null +++ b/specs/fulu/beacon-chain.md @@ -0,0 +1,324 @@ +# Fulu -- The Beacon Chain + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) + - [Blob schedule](#blob-schedule) +- [Beacon chain state transition function](#beacon-chain-state-transition-function) + - [Block processing](#block-processing) + - [Execution payload](#execution-payload) + - [Modified `process_execution_payload`](#modified-process_execution_payload) +- [Containers](#containers) + - [Extended Containers](#extended-containers) + - [`BeaconState`](#beaconstate) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [New `BlobParameters`](#new-blobparameters) + - [New `get_blob_parameters`](#new-get_blob_parameters) + - [Modified `compute_fork_digest`](#modified-compute_fork_digest) + - [New `compute_proposer_indices`](#new-compute_proposer_indices) + - [Beacon state accessors](#beacon-state-accessors) + - [Modified `get_beacon_proposer_index`](#modified-get_beacon_proposer_index) + - [New `get_beacon_proposer_indices`](#new-get_beacon_proposer_indices) + - [Epoch processing](#epoch-processing) + - [Modified `process_epoch`](#modified-process_epoch) + - [New `process_proposer_lookahead`](#new-process_proposer_lookahead) + + + +## Introduction + +*Note*: This specification is built upon [Electra](../electra/beacon-chain.md) +and is under active development. + +## Configuration + +### Blob schedule + +*[New in Fulu:EIP7892]* This schedule defines the maximum blobs per block limit +for a given epoch. + +There MUST NOT exist multiple blob schedule entries with the same epoch value. +The maximum blobs per block limit for blob schedules entries MUST be less than +or equal to `MAX_BLOB_COMMITMENTS_PER_BLOCK`. The blob schedule entries SHOULD +be sorted by epoch in ascending order. The blob schedule MAY be empty. + +*Note*: The blob schedule is to be determined. + + + +| Epoch | Max Blobs Per Block | Description | +| ----- | ------------------- | ----------- | + +## Beacon chain state transition function + +### Block processing + +#### 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_time_at_slot(state, state.slot) + # [Modified in Fulu:EIP7892] Verify commitments are under limit + assert ( + len(body.blob_kzg_commitments) + <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block + ) + # 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, + ) +``` + +## Containers + +### Extended Containers + +#### `BeaconState` + +*Note*: The `BeaconState` container is extended with the `proposer_lookahead` +field, which is a list of validator indices covering the full lookahead period, +starting from the beginning of the current epoch. For example, +`proposer_lookahead[0]` is the validator index for the first proposer in the +current epoch, `proposer_lookahead[1]` is the validator index for the next +proposer in the current epoch, and so forth. The length of the +`proposer_lookahead` list is `(MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH`, +reflecting how far ahead proposer indices are computed based on the +`MIN_SEED_LOOKAHEAD` parameter. + +```python +class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + latest_execution_payload_header: ExecutionPayloadHeader + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + deposit_requests_start_index: uint64 + deposit_balance_to_consume: Gwei + exit_balance_to_consume: Gwei + earliest_exit_epoch: Epoch + consolidation_balance_to_consume: Gwei + earliest_consolidation_epoch: Epoch + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + # [New in Fulu:EIP7917] + proposer_lookahead: Vector[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH] +``` + +## Helper functions + +### Misc + +#### New `BlobParameters` + +```python +@dataclass +class BlobParameters: + epoch: Epoch + max_blobs_per_block: uint64 +``` + +#### New `get_blob_parameters` + +```python +def get_blob_parameters(epoch: Epoch) -> BlobParameters: + """ + Return the blob parameters at a given epoch. + """ + for entry in sorted(BLOB_SCHEDULE, key=lambda e: e["EPOCH"], reverse=True): + if epoch >= entry["EPOCH"]: + return BlobParameters(entry["EPOCH"], entry["MAX_BLOBS_PER_BLOCK"]) + return BlobParameters(ELECTRA_FORK_EPOCH, MAX_BLOBS_PER_BLOCK_ELECTRA) +``` + +#### Modified `compute_fork_digest` + +*Note:* The `compute_fork_digest` helper is updated to account for +Blob-Parameters-Only forks. Also, the `fork_version` parameter has been removed +and is now computed for the given epoch with `compute_fork_version`. + +```python +def compute_fork_digest( + genesis_validators_root: Root, + epoch: Epoch, # [New in Fulu:EIP7892] +) -> ForkDigest: + """ + Return the 4-byte fork digest for the ``version`` and ``genesis_validators_root`` + XOR'd with the hash of the blob parameters for ``epoch``. + + This is a digest primarily used for domain separation on the p2p layer. + 4-bytes suffices for practical separation of forks/chains. + """ + fork_version = compute_fork_version(epoch) + base_digest = compute_fork_data_root(fork_version, genesis_validators_root) + blob_parameters = get_blob_parameters(epoch) + + # Bitmask digest with hash of blob parameters + return ForkDigest( + bytes( + xor( + base_digest, + hash( + uint_to_bytes(uint64(blob_parameters.epoch)) + + uint_to_bytes(uint64(blob_parameters.max_blobs_per_block)) + ), + ) + )[:4] + ) +``` + +#### New `compute_proposer_indices` + +```python +def compute_proposer_indices( + state: BeaconState, epoch: Epoch, seed: Bytes32, indices: Sequence[ValidatorIndex] +) -> Vector[ValidatorIndex, SLOTS_PER_EPOCH]: + """ + Return the proposer indices for the given ``epoch``. + """ + start_slot = compute_start_slot_at_epoch(epoch) + seeds = [hash(seed + uint_to_bytes(Slot(start_slot + i))) for i in range(SLOTS_PER_EPOCH)] + return [compute_proposer_index(state, indices, seed) for seed in seeds] +``` + +### Beacon state accessors + +#### Modified `get_beacon_proposer_index` + +*Note*: The function `get_beacon_proposer_index` is modified to use the +pre-calculated `current_proposer_lookahead` instead of calculating it on-demand. + +```python +def get_beacon_proposer_index(state: BeaconState) -> ValidatorIndex: + """ + Return the beacon proposer index at the current slot. + """ + return state.proposer_lookahead[state.slot % SLOTS_PER_EPOCH] +``` + +#### New `get_beacon_proposer_indices` + +```python +def get_beacon_proposer_indices( + state: BeaconState, epoch: Epoch +) -> Vector[ValidatorIndex, SLOTS_PER_EPOCH]: + """ + Return the proposer indices for the given ``epoch``. + """ + indices = get_active_validator_indices(state, epoch) + seed = get_seed(state, epoch, DOMAIN_BEACON_PROPOSER) + return compute_proposer_indices(state, epoch, seed, indices) +``` + +### Epoch processing + +#### Modified `process_epoch` + +*Note*: The function `process_epoch` is modified in Fulu to call +`process_proposer_lookahead` to update the `proposer_lookahead` in the beacon +state. + +```python +def process_epoch(state: BeaconState) -> None: + process_justification_and_finalization(state) + process_inactivity_updates(state) + process_rewards_and_penalties(state) + process_registry_updates(state) + process_slashings(state) + process_eth1_data_reset(state) + process_pending_deposits(state) + process_pending_consolidations(state) + process_effective_balance_updates(state) + process_slashings_reset(state) + process_randao_mixes_reset(state) + process_historical_summaries_update(state) + process_participation_flag_updates(state) + process_sync_committee_updates(state) + process_proposer_lookahead(state) # [New in Fulu:EIP7917] +``` + +#### New `process_proposer_lookahead` + +*Note*: This function updates the `proposer_lookahead` field in the beacon state +by shifting out proposer indices from the earliest epoch and appending new +proposer indices for the latest epoch. With `MIN_SEED_LOOKAHEAD` set to `1`, +this means that at the start of epoch `N`, the proposer lookahead for epoch +`N+1` will be computed and included in the beacon state's lookahead. + +```python +def process_proposer_lookahead(state: BeaconState) -> None: + last_epoch_start = len(state.proposer_lookahead) - SLOTS_PER_EPOCH + # Shift out proposers in the first epoch + state.proposer_lookahead[:last_epoch_start] = state.proposer_lookahead[SLOTS_PER_EPOCH:] + # Fill in the last epoch with new proposer indices + last_epoch_proposers = get_beacon_proposer_indices( + state, Epoch(get_current_epoch(state) + MIN_SEED_LOOKAHEAD + 1) + ) + state.proposer_lookahead[last_epoch_start:] = last_epoch_proposers +``` diff --git a/specs/fulu/das-core.md b/specs/fulu/das-core.md new file mode 100644 index 0000000000..d85b55ea96 --- /dev/null +++ b/specs/fulu/das-core.md @@ -0,0 +1,313 @@ +# Fulu -- Data Availability Sampling Core + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Constants](#constants) + - [Misc](#misc) +- [Custom types](#custom-types) +- [Configuration](#configuration) + - [Data size](#data-size) + - [Custody setting](#custody-setting) + - [Containers](#containers) + - [`DataColumnSidecar`](#datacolumnsidecar) + - [`MatrixEntry`](#matrixentry) +- [Helper functions](#helper-functions) + - [`get_custody_groups`](#get_custody_groups) + - [`compute_columns_for_custody_group`](#compute_columns_for_custody_group) + - [`compute_matrix`](#compute_matrix) + - [`recover_matrix`](#recover_matrix) +- [Custody](#custody) + - [Custody requirement](#custody-requirement) + - [Public, deterministic selection](#public-deterministic-selection) +- [Custody sampling](#custody-sampling) +- [Extended data](#extended-data) +- [Column gossip](#column-gossip) + - [Parameters](#parameters) +- [Reconstruction and cross-seeding](#reconstruction-and-cross-seeding) +- [FAQs](#faqs) + - [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) + + + +## Constants + +The following values are (non-configurable) constants used throughout the +specification. + +### Misc + +| Name | Value | +| ------------- | --------------------- | +| `UINT256_MAX` | `uint256(2**256 - 1)` | + +## Custom types + +| Name | SSZ equivalent | Description | +| -------------- | -------------- | ----------------------------------------------------- | +| `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 + +### Data size + +| Name | Value | Description | +| ------------------- | ------------------------------------ | --------------------------------------------- | +| `NUMBER_OF_COLUMNS` | `uint64(CELLS_PER_EXT_BLOB)` (= 128) | Number of columns in the extended data matrix | + +### Custody setting + +| Name | Value | Description | +| -------------------------- | ----- | --------------------------------------------------------------------------------- | +| `SAMPLES_PER_SLOT` | `8` | Minimum number of samples for an honest node | +| `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 + +#### `DataColumnSidecar` + +```python +class DataColumnSidecar(Container): + index: ColumnIndex + column: List[Cell, MAX_BLOB_COMMITMENTS_PER_BLOCK] + kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + kzg_proofs: List[KZGProof, MAX_BLOB_COMMITMENTS_PER_BLOCK] + signed_block_header: SignedBeaconBlockHeader + kzg_commitments_inclusion_proof: Vector[Bytes32, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH] +``` + +#### `MatrixEntry` + +```python +class MatrixEntry(Container): + cell: Cell + kzg_proof: KZGProof + column_index: ColumnIndex + row_index: RowIndex +``` + +## Helper functions + +### `get_custody_groups` + +```python +def get_custody_groups(node_id: NodeID, custody_group_count: uint64) -> Sequence[CustodyIndex]: + assert custody_group_count <= NUMBER_OF_CUSTODY_GROUPS + + current_id = uint256(node_id) + custody_groups: List[CustodyIndex] = [] + while len(custody_groups) < custody_group_count: + custody_group = CustodyIndex( + bytes_to_uint64(hash(uint_to_bytes(current_id))[0:8]) % NUMBER_OF_CUSTODY_GROUPS + ) + if custody_group not in custody_groups: + custody_groups.append(custody_group) + if current_id == UINT256_MAX: + # Overflow prevention + current_id = uint256(0) + else: + current_id += 1 + + assert len(custody_groups) == len(set(custody_groups)) + return sorted(custody_groups) +``` + +### `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 [ + ColumnIndex(NUMBER_OF_CUSTODY_GROUPS * i + custody_group) for i in range(columns_per_group) + ] +``` + +### `compute_matrix` + +```python +def compute_matrix(blobs: Sequence[Blob]) -> Sequence[MatrixEntry]: + """ + Return the full, flattened sequence of matrix entries. + + This helper demonstrates the relationship between blobs and the matrix of cells/proofs. + The data structure for storing cells/proofs is implementation-dependent. + """ + matrix = [] + for blob_index, blob in enumerate(blobs): + cells, proofs = compute_cells_and_kzg_proofs(blob) + for cell_index, (cell, proof) in enumerate(zip(cells, proofs)): + matrix.append( + MatrixEntry( + cell=cell, + kzg_proof=proof, + row_index=blob_index, + column_index=cell_index, + ) + ) + return matrix +``` + +### `recover_matrix` + +```python +def recover_matrix( + partial_matrix: Sequence[MatrixEntry], blob_count: uint64 +) -> Sequence[MatrixEntry]: + """ + Recover the full, flattened sequence of matrix entries. + + This helper demonstrates how to apply ``recover_cells_and_kzg_proofs``. + The data structure for storing cells/proofs is implementation-dependent. + """ + matrix = [] + for blob_index in range(blob_count): + cell_indices = [e.column_index for e in partial_matrix if e.row_index == blob_index] + cells = [e.cell for e in partial_matrix if e.row_index == blob_index] + recovered_cells, recovered_proofs = recover_cells_and_kzg_proofs(cell_indices, cells) + for cell_index, (cell, proof) in enumerate(zip(recovered_cells, recovered_proofs)): + matrix.append( + MatrixEntry( + cell=cell, + kzg_proof=proof, + row_index=blob_index, + column_index=cell_index, + ) + ) + return matrix +``` + +## Custody + +### Custody requirement + +Columns are grouped into custody groups. Nodes custodying a custody group MUST +custody all the columns in that group. When syncing, a node MUST backfill +columns from all of its custody groups. + +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/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. + +## Custody sampling + +At each slot, a node advertising `custody_group_count` downloads a minimum of +`sampling_size = max(SAMPLES_PER_SLOT, custody_group_count)` custody groups, +selected by `groups = get_custody_groups(node_id, sampling_size)`, to which +correspond the columns +`compute_columns_for_custody_group(group) for group in groups`. The custody +groups to custody, selected by +`get_custody_groups(node_id, custody_group_count)`, are then in particular a +subset of those to sample. Sampling is considered successful if the node manages +to retrieve all selected columns. + +## Extended data + +In this construction, we extend the blobs using a one-dimensional erasure coding +extension. The matrix comprises maximum `MAX_BLOBS_PER_BLOCK` rows and fixed +`NUMBER_OF_COLUMNS` columns, with each row containing a `Blob` and its +corresponding extension. `compute_matrix` demonstrates the relationship between +blobs and the matrix, a potential method of storing cells/proofs. + +## Column gossip + +### Parameters + +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 + +If the node obtains 50%+ of all the columns, it SHOULD reconstruct the full data +matrix via the `recover_matrix` helper. Nodes MAY delay this reconstruction +allowing time for other columns to arrive over the network. If delaying +reconstruction, nodes may use a random delay in order to desynchronize +reconstruction among nodes, thus reducing overall CPU load. + +Once the node obtains a column through reconstruction, the node MUST expose the +new column as if it had received it over the network. If the node is subscribed +to the subnet corresponding to the column, it MUST send the reconstructed +`DataColumnSidecar` to its topic mesh neighbors. If instead the node is not +subscribed to the corresponding subnet, it SHOULD still expose the availability +of the `DataColumnSidecar` as part of the gossip emission process. After +exposing the reconstructed `DataColumnSidecar` to the network, the node MAY +delete the `DataColumnSidecar` if it is not part of the node's custody +requirement. + +*Note*: A node always maintains a matrix view of the rows and columns they are +following, able to cross-reference and cross-seed in either direction. + +*Note*: There are timing considerations to analyze -- at what point does a node +consider samples missing and choose to reconstruct and cross-seed. + +*Note*: There may be anti-DoS and quality-of-service considerations around how +to send samples and consider samples -- is each individual sample a message or +are they sent in aggregate forms. + +## FAQs + +### 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. + +The potential benefits of having row custody could include: + +1. Allow for more "natural" distribution of data to consumers -- e.g., roll-ups + -- but honestly, they won't know a priori which row their blob is going to be + included in the block, so they would either need to listen to all rows or + download a particular row after seeing the block. The former looks just like + listening to column \[0, N) and the latter is req/resp instead of gossiping. +2. Help with some sort of distributed reconstruction. Those with full rows can + compute extensions and seed missing samples to the network. This would either + need to be able to send individual points on the gossip or would need some + sort of req/resp faculty, potentially similar to an `IHAVEPOINTBITFIELD` and + `IWANTSAMPLE`. + +However, for simplicity, we don't assign row custody assignments to nodes in the +current design. + +### 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? + +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/fulu/fork-choice.md b/specs/fulu/fork-choice.md new file mode 100644 index 0000000000..482b0f732e --- /dev/null +++ b/specs/fulu/fork-choice.md @@ -0,0 +1,97 @@ +# Fulu -- Fork Choice + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Helpers](#helpers) + - [Modified `is_data_available`](#modified-is_data_available) +- [Updated fork-choice handlers](#updated-fork-choice-handlers) + - [Modified `on_block`](#modified-on_block) + + + +## Introduction + +This is the modification of the fork choice accompanying Fulu. + +## Helpers + +### Modified `is_data_available` + +```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. + column_sidecars = retrieve_column_sidecars(beacon_block_root) + return all( + verify_data_column_sidecar(column_sidecar) + and verify_data_column_sidecar_kzg_proofs(column_sidecar) + for column_sidecar in column_sidecars + ) +``` + +## Updated fork-choice handlers + +### Modified `on_block` + +*Note*: The only modification is that `is_data_available` does not take +`blob_kzg_commitments` as input. + +```python +def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: + """ + Run ``on_block`` upon receiving a new block. + """ + block = signed_block.message + # Parent block must be known + assert block.parent_root in store.block_states + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[block.parent_root]) + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. + assert get_current_slot(store) >= block.slot + + # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) + finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert block.slot > finalized_slot + # Check block is a descendant of the finalized block at the checkpoint finalized slot + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block + + # [Modified in Fulu:EIP7594] + assert is_data_available(hash_tree_root(block)) + + # Check the block is valid and compute the post-state + block_root = hash_tree_root(block) + state_transition(state, signed_block, True) + + # Add new block to the store + store.blocks[block_root] = block + # Add new state for this block to the store + store.block_states[block_root] = state + + # Add block timeliness to the store + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely + + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) + + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) + + # Eagerly compute unrealized justification and finality. + compute_pulled_up_tip(store, block_root) +``` diff --git a/specs/fulu/fork.md b/specs/fulu/fork.md new file mode 100644 index 0000000000..9ff3f4ac02 --- /dev/null +++ b/specs/fulu/fork.md @@ -0,0 +1,141 @@ +# Fulu -- Fork Logic + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Helper functions](#helper-functions) + - [Misc](#misc) + - [Modified `compute_fork_version`](#modified-compute_fork_version) + - [New `initialize_proposer_lookahead`](#new-initialize_proposer_lookahead) +- [Fork to Fulu](#fork-to-fulu) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + + +## Introduction + +This document describes the process of Fulu upgrade. + +## Configuration + +Warning: this configuration is not definitive. + +| Name | Value | +| ------------------- | ------------------------------------- | +| `FULU_FORK_VERSION` | `Version('0x06000000')` | +| `FULU_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | + +## Helper functions + +### Misc + +#### Modified `compute_fork_version` + +```python +def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= FULU_FORK_EPOCH: + return FULU_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION +``` + +#### New `initialize_proposer_lookahead` + +```python +def initialize_proposer_lookahead( + state: electra.BeaconState, +) -> Vector[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH]: + """ + Return the proposer indices for the full available lookahead starting from current epoch. + Used to initialize the ``proposer_lookahead`` field in the beacon state at genesis and after forks. + """ + current_epoch = get_current_epoch(state) + lookahead = [] + for i in range(MIN_SEED_LOOKAHEAD + 1): + lookahead.extend(get_beacon_proposer_indices(state, Epoch(current_epoch + i))) + return lookahead +``` + +## Fork to Fulu + +### Fork trigger + +The fork is triggered at epoch `FULU_FORK_EPOCH`. + +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) == FULU_FORK_EPOCH`, an irregular state +change is made to upgrade to Fulu. + +```python +def upgrade_to_fulu(pre: electra.BeaconState) -> BeaconState: + epoch = electra.get_current_epoch(pre) + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in Fulu] + current_version=FULU_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + latest_execution_payload_header=pre.latest_execution_payload_header, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + # [New in Fulu:EIP7917] + proposer_lookahead=initialize_proposer_lookahead(pre), + ) + + return post +``` diff --git a/specs/fulu/p2p-interface.md b/specs/fulu/p2p-interface.md new file mode 100644 index 0000000000..8f96e0c5c8 --- /dev/null +++ b/specs/fulu/p2p-interface.md @@ -0,0 +1,624 @@ +# Fulu -- Networking + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Modifications in Fulu](#modifications-in-fulu) + - [Preset](#preset) + - [Configuration](#configuration) + - [Containers](#containers) + - [`DataColumnsByRootIdentifier`](#datacolumnsbyrootidentifier) + - [Helpers](#helpers) + - [`verify_data_column_sidecar`](#verify_data_column_sidecar) + - [`verify_data_column_sidecar_kzg_proofs`](#verify_data_column_sidecar_kzg_proofs) + - [`verify_data_column_sidecar_inclusion_proof`](#verify_data_column_sidecar_inclusion_proof) + - [`compute_subnet_for_data_column_sidecar`](#compute_subnet_for_data_column_sidecar) + - [MetaData](#metadata) + - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) + - [Topics and messages](#topics-and-messages) + - [Global topics](#global-topics) + - [`beacon_block`](#beacon_block) + - [Blob subnets](#blob-subnets) + - [Deprecated `blob_sidecar_{subnet_id}`](#deprecated-blob_sidecar_subnet_id) + - [`data_column_sidecar_{subnet_id}`](#data_column_sidecar_subnet_id) + - [Distributed Blob Publishing using blobs retrieved from local execution layer client](#distributed-blob-publishing-using-blobs-retrieved-from-local-execution-layer-client) + - [The Req/Resp domain](#the-reqresp-domain) + - [Messages](#messages) + - [Status v2](#status-v2) + - [BlobSidecarsByRange v1](#blobsidecarsbyrange-v1) + - [BlobSidecarsByRoot v1](#blobsidecarsbyroot-v1) + - [DataColumnSidecarsByRange v1](#datacolumnsidecarsbyrange-v1) + - [DataColumnSidecarsByRoot v1](#datacolumnsidecarsbyroot-v1) + - [GetMetaData v3](#getmetadata-v3) + - [The discovery domain: discv5](#the-discovery-domain-discv5) + - [ENR structure](#enr-structure) + - [`eth2` field](#eth2-field) + - [Custody group count](#custody-group-count) + - [Next fork digest](#next-fork-digest) +- [Peer Scoring](#peer-scoring) +- [Supernodes](#supernodes) + + + +## 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 + +| Name | Value | Description | +| --------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | +| `KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH` | `uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')))` (= 4) | Merkle proof index for `blob_kzg_commitments` | + +### Configuration + +*[New in Fulu:EIP7594]* + +| 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 | + +### Containers + +#### `DataColumnsByRootIdentifier` + +```python +class DataColumnsByRootIdentifier(Container): + block_root: Root + columns: List[ColumnIndex, NUMBER_OF_COLUMNS] +``` + +### Helpers + +##### `verify_data_column_sidecar` + +```python +def verify_data_column_sidecar(sidecar: DataColumnSidecar) -> bool: + """ + Verify if the data column sidecar is valid. + """ + # The sidecar index must be within the valid range + if sidecar.index >= NUMBER_OF_COLUMNS: + return False + + # A sidecar for zero blobs is invalid + if len(sidecar.kzg_commitments) == 0: + return False + + # The column length must be equal to the number of commitments/proofs + if len(sidecar.column) != len(sidecar.kzg_commitments) or len(sidecar.column) != len( + sidecar.kzg_proofs + ): + return False + + return True +``` + +##### `verify_data_column_sidecar_kzg_proofs` + +```python +def verify_data_column_sidecar_kzg_proofs(sidecar: DataColumnSidecar) -> bool: + """ + Verify if the KZG proofs are correct. + """ + # The column index also represents the cell index + cell_indices = [CellIndex(sidecar.index)] * len(sidecar.column) + + # Batch verify that the cells match the corresponding commitments and proofs + return verify_cell_kzg_proof_batch( + commitments_bytes=sidecar.kzg_commitments, + cell_indices=cell_indices, + cells=sidecar.column, + proofs_bytes=sidecar.kzg_proofs, + ) +``` + +##### `verify_data_column_sidecar_inclusion_proof` + +```python +def verify_data_column_sidecar_inclusion_proof(sidecar: DataColumnSidecar) -> bool: + """ + Verify if the given KZG commitments included in the given beacon block. + """ + gindex = get_subtree_index(get_generalized_index(BeaconBlockBody, "blob_kzg_commitments")) + return is_valid_merkle_branch( + leaf=hash_tree_root(sidecar.kzg_commitments), + branch=sidecar.kzg_commitments_inclusion_proof, + depth=KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH, + index=gindex, + root=sidecar.signed_block_header.message.body_root, + ) +``` + +##### `compute_subnet_for_data_column_sidecar` + +```python +def compute_subnet_for_data_column_sidecar(column_index: ColumnIndex) -> SubnetID: + return SubnetID(column_index % DATA_COLUMN_SIDECAR_SUBNET_COUNT) +``` + +### MetaData + +The `MetaData` stored locally by clients is updated with an additional field to +communicate the custody subnet count. + +``` +( + seq_number: uint64 + attnets: Bitvector[ATTESTATION_SUBNET_COUNT] + syncnets: Bitvector[SYNC_COMMITTEE_SUBNET_COUNT] + custody_group_count: uint64 # cgc +) +``` + +Where + +- `seq_number`, `attnets`, and `syncnets` have the same meaning defined in the + Altair document. +- `custody_group_count` represents the node's custody group count. Clients MAY + reject peers with a value less than `CUSTODY_REQUIREMENT`. + +### The gossip domain: gossipsub + +Some gossip meshes are upgraded in the Fulu fork to support upgraded types. + +#### Topics and messages + +##### Global topics + +###### `beacon_block` + +*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(signed_beacon_block.message.body.blob_kzg_commitments) <= get_blob_parameters(get_current_epoch(state)).max_blobs_per_block` + +##### Blob subnets + +###### Deprecated `blob_sidecar_{subnet_id}` + +`blob_sidecar_{subnet_id}` is deprecated. + +###### `data_column_sidecar_{subnet_id}` + +This topic is used to propagate column sidecars, where each column maps to some +`subnet_id`. + +The *type* of the payload of this topic is `DataColumnSidecar`. + +The following validations MUST pass before forwarding the +`sidecar: DataColumnSidecar` on the network, assuming the alias +`block_header = sidecar.signed_block_header.message`: + +- _[REJECT]_ The sidecar is valid as verified by + `verify_data_column_sidecar(sidecar)`. +- _[REJECT]_ The sidecar is for the correct subnet -- i.e. + `compute_subnet_for_data_column_sidecar(sidecar.index) == subnet_id`. +- _[IGNORE]_ The sidecar is not from a future slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `block_header.slot <= current_slot` (a client MAY queue future sidecars for + processing at the appropriate slot). +- _[IGNORE]_ The sidecar is from a slot greater than the latest finalized slot + -- i.e. validate that + `block_header.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` +- _[REJECT]_ The proposer signature of `sidecar.signed_block_header`, is valid + with respect to the `block_header.proposer_index` pubkey. +- _[IGNORE]_ The sidecar's block's parent (defined by + `block_header.parent_root`) has been seen (via gossip or non-gossip sources) + (a client MAY queue sidecars for processing once the parent block is + retrieved). +- _[REJECT]_ The sidecar's block's parent (defined by + `block_header.parent_root`) passes validation. +- _[REJECT]_ The sidecar is from a higher slot than the sidecar's block's parent + (defined by `block_header.parent_root`). +- _[REJECT]_ The current finalized_checkpoint is an ancestor of the sidecar's + block -- i.e. + `get_checkpoint_block(store, block_header.parent_root, store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root`. +- _[REJECT]_ The sidecar's `kzg_commitments` field inclusion proof is valid as + verified by `verify_data_column_sidecar_inclusion_proof(sidecar)`. +- _[REJECT]_ The sidecar's column data is valid as verified by + `verify_data_column_sidecar_kzg_proofs(sidecar)`. +- _[IGNORE]_ The sidecar is the first sidecar for the tuple + `(block_header.slot, block_header.proposer_index, sidecar.index)` with valid + header signature, sidecar inclusion proof, and kzg proof. +- _[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. + +*Note*: In the `verify_data_column_sidecar_inclusion_proof(sidecar)` check, for +all the sidecars of the same block, it verifies against the same set of +`kzg_commitments` of the given beacon block. Client can choose to cache the +result of the arguments tuple +`(sidecar.kzg_commitments, sidecar.kzg_commitments_inclusion_proof, sidecar.signed_block_header)`. + +###### Distributed Blob Publishing using blobs retrieved from local execution layer client + +Honest nodes SHOULD query `engine_getBlobsV2` as soon as they receive a valid +`beacon_block` or `data_column_sidecar` from gossip. If ALL blobs matching +`kzg_commitments` are retrieved, they should convert the response to data +columns, and import the result. + +Implementers are encouraged to leverage this method to increase the likelihood +of incorporating and attesting to the last block when its proposer is not able +to publish data columns on time. + +When clients use the local execution layer to retrieve blobs, they SHOULD skip +verification of those blobs. When subsequently importing the blobs as data +columns, they MUST behave as if the `data_column_sidecar` had been received via +gossip. In particular, clients MUST: + +- Publish the corresponding `data_column_sidecar` on the + `data_column_sidecar_{subnet_id}` topic **if and only if** they are + **subscribed** to it, either due to custody requirements or additional + sampling. +- Update gossip rule related data structures (i.e. update the anti-equivocation + cache). + +### The Req/Resp domain + +#### Messages + +##### Status v2 + +**Protocol ID:** `/eth2/beacon_chain/req/status/2/` + +Request, Response Content: + +``` +( + fork_digest: ForkDigest + finalized_root: Root + finalized_epoch: Epoch + head_root: Root + head_slot: Slot + earliest_available_slot: Slot # [New in Fulu:EIP7594] +) +``` + +The added fields are, as seen by the client at the time of sending the message: + +- `earliest_available_slot`: The slot of earliest available block + (`BeaconBlock`). + +*Note*: `fork_digest` is `compute_fork_digest(genesis_validators_root, epoch)` +where `genesis_validators_root` is the static `Root` found in +`state.genesis_validators_root` and `epoch` is the node's current epoch defined +by the wall-clock time (not necessarily the epoch to which the node is sync). + +##### BlobSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_range/1/` + +Deprecated as of `FULU_FORK_EPOCH + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS`. + +During the deprecation transition period: + +- Clients MUST respond with a list of blob sidecars from the range + `[min(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, FULU_FORK_EPOCH), FULU_FORK_EPOCH)` + if the requested range includes any epochs in this interval. +- Clients MAY respond with an empty list if the requested range lies entirely at + or after `FULU_FORK_EPOCH`. +- Clients SHOULD NOT penalize peers for requesting blob sidecars from + `FULU_FORK_EPOCH`. + +##### BlobSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/blob_sidecars_by_root/1/` + +Deprecated as of `FULU_FORK_EPOCH + MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS`. + +During the deprecation transition period: + +- Clients MUST respond with blob sidecars corresponding to block roots from the + range + `[min(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, FULU_FORK_EPOCH), FULU_FORK_EPOCH)` + if any of the requested roots correspond to blocks in this interval. +- Clients MAY respond with an empty list if all requested roots correspond to + blocks at or after `FULU_FORK_EPOCH`. +- Clients SHOULD NOT penalize peers for requesting blob sidecars from + `FULU_FORK_EPOCH`. + +##### DataColumnSidecarsByRange v1 + +**Protocol ID:** `/eth2/beacon_chain/req/data_column_sidecars_by_range/1/` + +The `` field is calculated as +`context = compute_fork_digest(genesis_validators_root, epoch)`: + + + +| `epoch` | Chunk SSZ type | +| -------------------- | ------------------------ | +| >= `FULU_FORK_EPOCH` | `fulu.DataColumnSidecar` | + +Request Content: + +``` +( + start_slot: Slot + count: uint64 + columns: List[ColumnIndex, NUMBER_OF_COLUMNS] +) +``` + +Response Content: + +``` +( + List[DataColumnSidecar, MAX_REQUEST_DATA_COLUMN_SIDECARS] +) +``` + +Requests data column sidecars in the slot range +`[start_slot, start_slot + count)` of the given `columns`, leading up to the +current head block as selected by fork choice. + +Before consuming the next response chunk, the response reader SHOULD verify the +data column sidecar is well-formatted through `verify_data_column_sidecar`, has +valid inclusion proof through `verify_data_column_sidecar_inclusion_proof`, and +is correct w.r.t. the expected KZG commitments through +`verify_data_column_sidecar_kzg_proofs`. + +`DataColumnSidecarsByRange` is primarily used to sync data columns that may have +been missed on gossip and to sync within the +`MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` window. + +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, 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. + +Peers that are unable to reply to data column sidecar requests within the range +`data_column_serve_range` SHOULD respond with error code +`3: ResourceUnavailable`. Such peers that are unable to successfully reply to +this range of requests MAY get descored or disconnected at any time. + +*Note*: The above requirement implies that nodes that start from a recent weak +subjectivity checkpoint MUST backfill the local data columns database to at +least the range `data_column_serve_range` to be fully compliant with +`DataColumnSidecarsByRange` requests. + +*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can +begin participating in the networking immediately, other peers MAY disconnect +and/or temporarily ban such an un-synced or semi-synced client. + +Clients MUST respond with at least the data column sidecars of the first +blob-carrying block that exists in the range, if they have it, and no more than +`MAX_REQUEST_DATA_COLUMN_SIDECARS` sidecars. + +Clients MUST include all data column sidecars of each block from which they +include data column sidecars. + +The following data column sidecars, where they exist, MUST be sent in +`(slot, column_index)` order. + +Slots that do not contain known data columns MUST be skipped, mimicking the +behaviour of the `BlocksByRange` request. Only response chunks with known data +columns should therefore be sent. + +Clients MAY limit the number of data column sidecars in the response. + +The response MUST contain no more than `count * NUMBER_OF_COLUMNS` data column +sidecars. + +Clients MUST respond with data columns sidecars from their view of the current +fork choice -- that is, data column sidecars as included by blocks from the +single chain defined by the current head. Of note, blocks from slots before the +finalization MUST lead to the finalized block reported in the `Status` +handshake. + +Clients MUST respond with data column sidecars that are consistent from a single +chain within the context of the request. + +After the initial data column 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. + +##### DataColumnSidecarsByRoot v1 + +**Protocol ID:** `/eth2/beacon_chain/req/data_column_sidecars_by_root/1/` + +*[New in Fulu:EIP7594]* + +The `` field is calculated as +`context = compute_fork_digest(genesis_validators_root, epoch)`: + + + +| `epoch` | Chunk SSZ type | +| -------------------- | ------------------------ | +| >= `FULU_FORK_EPOCH` | `fulu.DataColumnSidecar` | + +Request Content: + +``` +( + List[DataColumnsByRootIdentifier, MAX_REQUEST_BLOCKS_DENEB] +) +``` + +Response Content: + +``` +( + List[DataColumnSidecar, MAX_REQUEST_DATA_COLUMN_SIDECARS] +) +``` + +Requests data column sidecars by block root and column indices. The response is +a list of `DataColumnSidecar` whose length is less than or equal to +`requested_columns_count`, where +`requested_columns_count = sum(len(r.columns) for r in request)`. It may be less +in the case that the responding peer is missing blocks or sidecars. + +Before consuming the next response chunk, the response reader SHOULD verify the +data column sidecar is well-formatted through `verify_data_column_sidecar`, has +valid inclusion proof through `verify_data_column_sidecar_inclusion_proof`, and +is correct w.r.t. the expected KZG commitments through +`verify_data_column_sidecar_kzg_proofs`. + +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(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. + +Clients SHOULD include a sidecar in the response as soon as it passes the gossip +validation rules. 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 + +##### GetMetaData v3 + +**Protocol ID:** `/eth2/beacon_chain/req/metadata/3/` + +No Request Content. + +Response Content: + +``` +( + MetaData +) +``` + +Requests the MetaData of a peer, using the new `MetaData` definition given above +that is extended from Altair. Other conditions for the `GetMetaData` protocol +are unchanged from the Altair p2p networking document. + +### The discovery domain: discv5 + +#### ENR structure + +##### `eth2` field + +*[Updated in Fulu:EIP7892]* + +*Note*: The structure of `ENRForkID` has not changed but the field value +computations have changed. Unless explicitly mentioned here, all specifications +from [phase0/p2p-interface.md#eth2-field](../phase0/p2p-interface.md#eth2-field) +carry over. + +ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current +fork digest, next fork version, and next fork epoch to ensure connections are +made with peers on the intended Ethereum network. + +| Key | Value | +| :----- | :-------------- | +| `eth2` | SSZ `ENRForkID` | + +Specifically, the value of the `eth2` key MUST be the following SSZ encoded +object (`ENRForkID`): + +``` +( + fork_digest: ForkDigest + next_fork_version: Version + next_fork_epoch: Epoch +) +``` + +Where the fields of `ENRForkID` are defined as: + +- `fork_digest` is `compute_fork_digest(genesis_validators_root, epoch)` where: + - `genesis_validators_root` is the static `Root` found in + `state.genesis_validators_root`. + - `epoch` is the node's current epoch defined by the wall-clock time (not + necessarily the epoch to which the node is sync). +- `next_fork_version` is the fork version corresponding to the next planned fork + at a future epoch. The fork version will only change for regular forks, _not + BPO forks_. Note that it is possible for the blob schedule to define a change + at the same epoch as a regular fork; this situation would be considered a + regular fork. If no future fork is planned, set + `next_fork_version = current_fork_version` to signal this fact. +- `next_fork_epoch` is the epoch at which the next fork (whether a regular fork + _or a BPO fork_) is planned. If no future fork is planned, set + `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact. + +##### Custody group count + +A new field is added to the ENR under the key `cgc` to facilitate custody data +column discovery. This new field MUST be added once `FULU_FORK_EPOCH` is +assigned any value other than `FAR_FUTURE_EPOCH`. + +| Key | Value | +| ----- | ----------------------------------------------------------------------------------------------------------------- | +| `cgc` | Custody group count, `uint64` big endian integer with no leading zero bytes (`0` is encoded as empty byte string) | + +##### Next fork digest + +A new entry is added to the ENR under the key `nfd`, short for _next fork +digest_. This entry communicates the digest of the next scheduled fork, +regardless of whether it is a regular or a Blob-Parameters-Only fork. This new +entry MUST be added once `FULU_FORK_EPOCH` is assigned any value other than +`FAR_FUTURE_EPOCH`. Adding this entry prior to the Fulu fork will not impact +peering as nodes will ignore unknown ENR entries and `nfd` mismatches do not +cause disconnnects. + +If no next fork is scheduled, the `nfd` entry contains the default value for the +type (i.e., the SSZ representation of a zero-filled array). + +| Key | Value | +| :---- | :---------------------- | +| `nfd` | SSZ Bytes4 `ForkDigest` | + +When discovering and interfacing with peers, nodes MUST evaluate `nfd` alongside +their existing consideration of the `ENRForkID::next_*` fields under the `eth2` +key, to form a more accurate view of the peer's intended next fork for the +purposes of sustained peering. If there is a mismatch, the node MUST NOT +disconnect before the fork boundary, but it MAY disconnect at/after the fork +boundary. + +Nodes unprepared to follow the Fulu fork will be unaware of `nfd` entries. +However, their existing comparison of `eth2` entries (concretely +`next_fork_epoch`) is sufficient to detect upcoming divergence. + +## Peer Scoring + +Due to the deterministic custody functions, a node knows exactly what a peer +should be able to respond to. In the event that a peer does not respond to +samples of their custodied rows/columns, a node may downscore or disconnect from +a peer. + +## Supernodes + +A supernode is a node which subscribes to all data column sidecar subnets, +custodies all data column sidecars, and performs +[reconstruction and cross-seeding](./das-core.md#reconstruction-and-cross-seeding). +Being a supernode requires considerably higher bandwidth, storage, and +computation resources. In order to reconstruct missing data, there must be at +least one supernode on the network. Due to +[validator custody requirements](./validator.md#validator-custody), a node which +is connected to validator(s) with a combined balance greater than or equal to +4096 ETH must be a supernode. Moreover, any node with the necessary resources +may altruistically be a supernode. Therefore, there are expected to be many +(hundreds) of supernodes on mainnet and it is likely (though not necessary) for +a node to be connected to several of these by chance. diff --git a/specs/fulu/polynomial-commitments-sampling.md b/specs/fulu/polynomial-commitments-sampling.md new file mode 100644 index 0000000000..165008f0a1 --- /dev/null +++ b/specs/fulu/polynomial-commitments-sampling.md @@ -0,0 +1,817 @@ +# Fulu -- Polynomial Commitments Sampling + +*Note*: This document is a work-in-progress for researchers and implementers. + + + +- [Introduction](#introduction) +- [Public Methods](#public-methods) +- [Custom types](#custom-types) +- [Cryptographic types](#cryptographic-types) +- [Preset](#preset) + - [Blob](#blob) +- [Helper functions](#helper-functions) + - [BLS12-381 helpers](#bls12-381-helpers) + - [`cell_to_coset_evals`](#cell_to_coset_evals) + - [`coset_evals_to_cell`](#coset_evals_to_cell) + - [FFTs](#ffts) + - [`_fft_field`](#_fft_field) + - [`fft_field`](#fft_field) + - [`coset_fft_field`](#coset_fft_field) + - [`compute_verify_cell_kzg_proof_batch_challenge`](#compute_verify_cell_kzg_proof_batch_challenge) + - [Polynomials in coefficient form](#polynomials-in-coefficient-form) + - [`polynomial_eval_to_coeff`](#polynomial_eval_to_coeff) + - [`add_polynomialcoeff`](#add_polynomialcoeff) + - [`multiply_polynomialcoeff`](#multiply_polynomialcoeff) + - [`divide_polynomialcoeff`](#divide_polynomialcoeff) + - [`interpolate_polynomialcoeff`](#interpolate_polynomialcoeff) + - [`vanishing_polynomialcoeff`](#vanishing_polynomialcoeff) + - [`evaluate_polynomialcoeff`](#evaluate_polynomialcoeff) + - [KZG multiproofs](#kzg-multiproofs) + - [`compute_kzg_proof_multi_impl`](#compute_kzg_proof_multi_impl) + - [`verify_cell_kzg_proof_batch_impl`](#verify_cell_kzg_proof_batch_impl) + - [Cell cosets](#cell-cosets) + - [`coset_shift_for_cell`](#coset_shift_for_cell) + - [`coset_for_cell`](#coset_for_cell) +- [Cells](#cells) + - [Cell computation](#cell-computation) + - [`compute_cells`](#compute_cells) + - [`compute_cells_and_kzg_proofs_polynomialcoeff`](#compute_cells_and_kzg_proofs_polynomialcoeff) + - [`compute_cells_and_kzg_proofs`](#compute_cells_and_kzg_proofs) + - [Cell verification](#cell-verification) + - [`verify_cell_kzg_proof_batch`](#verify_cell_kzg_proof_batch) +- [Reconstruction](#reconstruction) + - [`construct_vanishing_polynomial`](#construct_vanishing_polynomial) + - [`recover_polynomialcoeff`](#recover_polynomialcoeff) + - [`recover_cells_and_kzg_proofs`](#recover_cells_and_kzg_proofs) + + + +## Introduction + +This document extends +[polynomial-commitments.md](../deneb/polynomial-commitments.md) with the +functions required for data availability sampling (DAS). It is not part of the +core Deneb spec but an extension that can be optionally implemented to allow +nodes to reduce their load using DAS. + +## Public Methods + +For any KZG library extended to support DAS, functions flagged as "Public +method" MUST be provided by the underlying KZG library as public functions. All +other functions are private functions used internally by the KZG library. + +Public functions MUST accept raw bytes as input and perform the required +cryptographic normalization before invoking any internal functions. + +The following is a list of the public methods: + +- [`compute_cells_and_kzg_proofs`](#compute_cells_and_kzg_proofs) +- [`verify_cell_kzg_proof_batch`](#verify_cell_kzg_proof_batch) +- [`recover_cells_and_kzg_proofs`](#recover_cells_and_kzg_proofs) + +## Custom types + +| Name | SSZ equivalent | Description | +| ----------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------- | +| `Cell` | `ByteVector[BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_CELL]` | The unit of blob data that can come with its own KZG proof | +| `CellIndex` | `uint64` | Validation: `x < CELLS_PER_EXT_BLOB` | +| `CommitmentIndex` | `uint64` | The type which represents the index of an element in the list of commitments | + +## Cryptographic types + +| Name | SSZ equivalent | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------ | +| [`PolynomialCoeff`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L20-L24) | `List[BLSFieldElement, FIELD_ELEMENTS_PER_EXT_BLOB]` | A polynomial in coefficient form | +| [`Coset`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L27-L33) | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_CELL]` | The evaluation domain of a cell | +| [`CosetEvals`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L36-L42) | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_CELL]` | A cell's evaluations over its coset | + +## Preset + +### Blob + +Cells are the smallest unit of blob data that can come with their own KZG +proofs. Samples can be constructed from one or several cells (e.g. an individual +cell or line). + +| Name | Value | Description | +| ---------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------- | +| `FIELD_ELEMENTS_PER_EXT_BLOB` | `2 * FIELD_ELEMENTS_PER_BLOB` | Number of field elements in a Reed-Solomon extended blob | +| `FIELD_ELEMENTS_PER_CELL` | `uint64(64)` | Number of field elements in a cell | +| `BYTES_PER_CELL` | `FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT` | The number of bytes in a cell | +| `CELLS_PER_EXT_BLOB` | `FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL` | The number of cells in an extended blob | +| `RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN` | `b'RCKZGCBATCH__V1_'` | | + +## Helper functions + +### BLS12-381 helpers + +#### `cell_to_coset_evals` + +```python +def cell_to_coset_evals(cell: Cell) -> CosetEvals: + """ + Convert an untrusted ``Cell`` into a trusted ``CosetEvals``. + """ + evals = CosetEvals() + for i in range(FIELD_ELEMENTS_PER_CELL): + start = i * BYTES_PER_FIELD_ELEMENT + end = (i + 1) * BYTES_PER_FIELD_ELEMENT + evals[i] = bytes_to_bls_field(cell[start:end]) + return evals +``` + +#### `coset_evals_to_cell` + +```python +def coset_evals_to_cell(coset_evals: CosetEvals) -> Cell: + """ + Convert a trusted ``CosetEval`` into an untrusted ``Cell``. + """ + cell = [] + for i in range(FIELD_ELEMENTS_PER_CELL): + cell += bls_field_to_bytes(coset_evals[i]) + return Cell(cell) +``` + +### FFTs + +#### `_fft_field` + +```python +def _fft_field( + vals: Sequence[BLSFieldElement], roots_of_unity: Sequence[BLSFieldElement] +) -> Sequence[BLSFieldElement]: + if len(vals) == 1: + return vals + L = _fft_field(vals[::2], roots_of_unity[::2]) + R = _fft_field(vals[1::2], roots_of_unity[::2]) + o = [BLSFieldElement(0) for _ in vals] + for i, (x, y) in enumerate(zip(L, R)): + y_times_root = y * roots_of_unity[i] + o[i] = x + y_times_root + o[i + len(L)] = x - y_times_root + return o +``` + +#### `fft_field` + +```python +def fft_field( + vals: Sequence[BLSFieldElement], roots_of_unity: Sequence[BLSFieldElement], inv: bool = False +) -> Sequence[BLSFieldElement]: + if inv: + # Inverse FFT + invlen = BLSFieldElement(len(vals)).pow(BLSFieldElement(BLS_MODULUS - 2)) + return [ + x * invlen + for x in _fft_field(vals, list(roots_of_unity[0:1]) + list(roots_of_unity[:0:-1])) + ] + else: + # Regular FFT + return _fft_field(vals, roots_of_unity) +``` + +#### `coset_fft_field` + +```python +def coset_fft_field( + vals: Sequence[BLSFieldElement], roots_of_unity: Sequence[BLSFieldElement], inv: bool = False +) -> Sequence[BLSFieldElement]: + """ + Computes an FFT/IFFT over a coset of the roots of unity. + This is useful for when one wants to divide by a polynomial which + vanishes on one or more elements in the domain. + """ + vals = [v for v in vals] # copy + + def shift_vals( + vals: Sequence[BLSFieldElement], factor: BLSFieldElement + ) -> Sequence[BLSFieldElement]: + """ + Multiply each entry in `vals` by succeeding powers of `factor` + i.e., [vals[0] * factor^0, vals[1] * factor^1, ..., vals[n] * factor^n] + """ + updated_vals: List[BLSFieldElement] = [] + shift = BLSFieldElement(1) + for i in range(len(vals)): + updated_vals.append(vals[i] * shift) + shift = shift * factor + return updated_vals + + # This is the coset generator; it is used to compute a FFT/IFFT over a coset of + # the roots of unity. + shift_factor = BLSFieldElement(PRIMITIVE_ROOT_OF_UNITY) + if inv: + vals = fft_field(vals, roots_of_unity, inv) + return shift_vals(vals, shift_factor.inverse()) + else: + vals = shift_vals(vals, shift_factor) + return fft_field(vals, roots_of_unity, inv) +``` + +#### `compute_verify_cell_kzg_proof_batch_challenge` + +```python +def compute_verify_cell_kzg_proof_batch_challenge( + commitments: Sequence[KZGCommitment], + commitment_indices: Sequence[CommitmentIndex], + cell_indices: Sequence[CellIndex], + cosets_evals: Sequence[CosetEvals], + proofs: Sequence[KZGProof], +) -> BLSFieldElement: + """ + Compute a random challenge ``r`` used in the universal verification equation. To compute the + challenge, ``RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN`` and all data that can influence the + verification is hashed together to deterministically generate a "random" field element via + the Fiat-Shamir heuristic. + """ + hashinput = RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN + hashinput += int.to_bytes(FIELD_ELEMENTS_PER_BLOB, 8, KZG_ENDIANNESS) + hashinput += int.to_bytes(FIELD_ELEMENTS_PER_CELL, 8, KZG_ENDIANNESS) + hashinput += int.to_bytes(len(commitments), 8, KZG_ENDIANNESS) + hashinput += int.to_bytes(len(cell_indices), 8, KZG_ENDIANNESS) + for commitment in commitments: + hashinput += commitment + for k, coset_evals in enumerate(cosets_evals): + hashinput += int.to_bytes(commitment_indices[k], 8, KZG_ENDIANNESS) + hashinput += int.to_bytes(cell_indices[k], 8, KZG_ENDIANNESS) + for coset_eval in coset_evals: + hashinput += bls_field_to_bytes(coset_eval) + hashinput += proofs[k] + return hash_to_bls_field(hashinput) +``` + +### Polynomials in coefficient form + +#### `polynomial_eval_to_coeff` + +```python +def polynomial_eval_to_coeff(polynomial: Polynomial) -> PolynomialCoeff: + """ + Interpolates a polynomial (given in evaluation form) to a polynomial in coefficient form. + """ + roots_of_unity = compute_roots_of_unity(FIELD_ELEMENTS_PER_BLOB) + return PolynomialCoeff( + fft_field(bit_reversal_permutation(polynomial), roots_of_unity, inv=True) + ) +``` + +#### `add_polynomialcoeff` + +```python +def add_polynomialcoeff(a: PolynomialCoeff, b: PolynomialCoeff) -> PolynomialCoeff: + """ + Sum the coefficient form polynomials ``a`` and ``b``. + """ + a, b = (a, b) if len(a) >= len(b) else (b, a) + length_a, length_b = len(a), len(b) + return PolynomialCoeff( + [a[i] + (b[i] if i < length_b else BLSFieldElement(0)) for i in range(length_a)] + ) +``` + +#### `multiply_polynomialcoeff` + +```python +def multiply_polynomialcoeff(a: PolynomialCoeff, b: PolynomialCoeff) -> PolynomialCoeff: + """ + Multiplies the coefficient form polynomials ``a`` and ``b``. + """ + assert len(a) + len(b) <= FIELD_ELEMENTS_PER_EXT_BLOB + + r = PolynomialCoeff([BLSFieldElement(0)]) + for power, coef in enumerate(a): + summand = PolynomialCoeff([BLSFieldElement(0)] * power + [coef * x for x in b]) + r = add_polynomialcoeff(r, summand) + return r +``` + +#### `divide_polynomialcoeff` + +```python +def divide_polynomialcoeff(a: PolynomialCoeff, b: PolynomialCoeff) -> PolynomialCoeff: + """ + Long polynomial division for two coefficient form polynomials ``a`` and ``b``. + """ + a = PolynomialCoeff(a[:]) # copy + o = PolynomialCoeff([]) + apos = len(a) - 1 + bpos = len(b) - 1 + diff = apos - bpos + while diff >= 0: + quot = a[apos] / b[bpos] + o.insert(0, quot) + for i in range(bpos, -1, -1): + a[diff + i] = a[diff + i] - b[i] * quot + apos -= 1 + diff -= 1 + return o +``` + +#### `interpolate_polynomialcoeff` + +```python +def interpolate_polynomialcoeff( + xs: Sequence[BLSFieldElement], ys: Sequence[BLSFieldElement] +) -> PolynomialCoeff: + """ + Lagrange interpolation: Finds the lowest degree polynomial that takes the value ``ys[i]`` at ``x[i]`` for all i. + Outputs a coefficient form polynomial. Leading coefficients may be zero. + """ + assert len(xs) == len(ys) + + r = PolynomialCoeff([BLSFieldElement(0)]) + for i in range(len(xs)): + summand = PolynomialCoeff([ys[i]]) + for j in range(len(ys)): + if j != i: + weight_adjustment = (xs[i] - xs[j]).inverse() + summand = multiply_polynomialcoeff( + summand, PolynomialCoeff([-weight_adjustment * xs[j], weight_adjustment]) + ) + r = add_polynomialcoeff(r, summand) + return r +``` + +#### `vanishing_polynomialcoeff` + +```python +def vanishing_polynomialcoeff(xs: Sequence[BLSFieldElement]) -> PolynomialCoeff: + """ + Compute the vanishing polynomial on ``xs`` (in coefficient form). + """ + p = PolynomialCoeff([BLSFieldElement(1)]) + for x in xs: + p = multiply_polynomialcoeff(p, PolynomialCoeff([-x, BLSFieldElement(1)])) + return p +``` + +#### `evaluate_polynomialcoeff` + +```python +def evaluate_polynomialcoeff( + polynomial_coeff: PolynomialCoeff, z: BLSFieldElement +) -> BLSFieldElement: + """ + Evaluate a coefficient form polynomial at ``z`` using Horner's schema. + """ + y = BLSFieldElement(0) + for coef in polynomial_coeff[::-1]: + y = y * z + coef + return y +``` + +### KZG multiproofs + +Extended KZG functions for multiproofs + +#### `compute_kzg_proof_multi_impl` + +```python +def compute_kzg_proof_multi_impl( + polynomial_coeff: PolynomialCoeff, zs: Coset +) -> Tuple[KZGProof, CosetEvals]: + """ + Compute a KZG multi-evaluation proof for a set of `k` points. + + This is done by committing to the following quotient polynomial: + Q(X) = f(X) - I(X) / Z(X) + Where: + - I(X) is the degree `k-1` polynomial that agrees with f(x) at all `k` points + - Z(X) is the degree `k` polynomial that evaluates to zero on all `k` points + + We further note that since the degree of I(X) is less than the degree of Z(X), + the computation can be simplified in monomial form to Q(X) = f(X) / Z(X). + """ + + # For all points, compute the evaluation of those points + ys = CosetEvals([evaluate_polynomialcoeff(polynomial_coeff, z) for z in zs]) + + # Compute Z(X) + denominator_poly = vanishing_polynomialcoeff(zs) + + # Compute the quotient polynomial directly in monomial form + quotient_polynomial = divide_polynomialcoeff(polynomial_coeff, denominator_poly) + + return KZGProof( + g1_lincomb(KZG_SETUP_G1_MONOMIAL[: len(quotient_polynomial)], quotient_polynomial) + ), ys +``` + +#### `verify_cell_kzg_proof_batch_impl` + +```python +def verify_cell_kzg_proof_batch_impl( + commitments: Sequence[KZGCommitment], + commitment_indices: Sequence[CommitmentIndex], + cell_indices: Sequence[CellIndex], + cosets_evals: Sequence[CosetEvals], + proofs: Sequence[KZGProof], +) -> bool: + """ + Helper: Verify that a set of cells belong to their corresponding commitment. + + Given a list of ``commitments`` (which contains no duplicates) and four lists representing + tuples of (``commitment_index``, ``cell_index``, ``evals``, ``proof``), the function + verifies ``proof`` which shows that ``evals`` are the evaluations of the polynomial associated + with ``commitments[commitment_index]``, evaluated over the domain specified by ``cell_index``. + + This function is the internal implementation of ``verify_cell_kzg_proof_batch``. + """ + assert len(commitment_indices) == len(cell_indices) == len(cosets_evals) == len(proofs) + assert len(commitments) == len(set(commitments)) + for commitment_index in commitment_indices: + assert commitment_index < len(commitments) + + # The verification equation that we will check is pairing (LL, LR) = pairing (RL, [1]), where + # LL = sum_k r^k proofs[k], + # LR = [s^n] + # RL = RLC - RLI + RLP, where + # RLC = sum_i weights[i] commitments[i] + # RLI = [sum_k r^k interpolation_poly_k(s)] + # RLP = sum_k (r^k * h_k^n) proofs[k] + # + # Here, the variables have the following meaning: + # - k < len(cell_indices) is an index iterating over all cells in the input + # - r is a random coefficient, derived from hashing all data provided by the prover + # - s is the secret embedded in the KZG setup + # - n = FIELD_ELEMENTS_PER_CELL is the size of the evaluation domain + # - i ranges over all provided commitments + # - weights[i] is a weight computed for commitment i + # - It depends on r and on which cells are associated with commitment i + # - interpolation_poly_k is the interpolation polynomial for the kth cell + # - h_k is the coset shift specifying the evaluation domain of the kth cell + + # Preparation + num_cells = len(cell_indices) + n = FIELD_ELEMENTS_PER_CELL + num_commitments = len(commitments) + + # Step 1: Compute a challenge r and its powers r^0, ..., r^{num_cells-1} + r = compute_verify_cell_kzg_proof_batch_challenge( + commitments, commitment_indices, cell_indices, cosets_evals, proofs + ) + r_powers = compute_powers(r, num_cells) + + # Step 2: Compute LL = sum_k r^k proofs[k] + ll = bls.bytes48_to_G1(g1_lincomb(proofs, r_powers)) + + # Step 3: Compute LR = [s^n] + lr = bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[n]) + + # Step 4: Compute RL = RLC - RLI + RLP + # Step 4.1: Compute RLC = sum_i weights[i] commitments[i] + # Step 4.1a: Compute weights[i]: the sum of all r^k for which cell k is associated with commitment i. + # Note: we do that by iterating over all k and updating the correct weights[i] accordingly + weights = [BLSFieldElement(0)] * num_commitments + for k in range(num_cells): + i = commitment_indices[k] + weights[i] += r_powers[k] + # Step 4.1b: Linearly combine the weights with the commitments to get RLC + rlc = bls.bytes48_to_G1(g1_lincomb(commitments, weights)) + + # Step 4.2: Compute RLI = [sum_k r^k interpolation_poly_k(s)] + # Note: an efficient implementation would use the IDFT based method explained in the blog post + sum_interp_polys_coeff = PolynomialCoeff([BLSFieldElement(0)] * n) + for k in range(num_cells): + interp_poly_coeff = interpolate_polynomialcoeff( + coset_for_cell(cell_indices[k]), cosets_evals[k] + ) + interp_poly_scaled_coeff = multiply_polynomialcoeff( + PolynomialCoeff([r_powers[k]]), interp_poly_coeff + ) + sum_interp_polys_coeff = add_polynomialcoeff( + sum_interp_polys_coeff, interp_poly_scaled_coeff + ) + rli = bls.bytes48_to_G1(g1_lincomb(KZG_SETUP_G1_MONOMIAL[:n], sum_interp_polys_coeff)) + + # Step 4.3: Compute RLP = sum_k (r^k * h_k^n) proofs[k] + weighted_r_powers = [] + for k in range(num_cells): + h_k = coset_shift_for_cell(cell_indices[k]) + h_k_pow = h_k.pow(BLSFieldElement(n)) + wrp = r_powers[k] * h_k_pow + weighted_r_powers.append(wrp) + rlp = bls.bytes48_to_G1(g1_lincomb(proofs, weighted_r_powers)) + + # Step 4.4: Compute RL = RLC - RLI + RLP + rl = bls.add(rlc, bls.neg(rli)) + rl = bls.add(rl, rlp) + + # Step 5: Check pairing (LL, LR) = pairing (RL, [1]) + return bls.pairing_check( + [ + [ll, lr], + [rl, bls.neg(bls.bytes96_to_G2(KZG_SETUP_G2_MONOMIAL[0]))], + ] + ) +``` + +### Cell cosets + +#### `coset_shift_for_cell` + +```python +def coset_shift_for_cell(cell_index: CellIndex) -> BLSFieldElement: + """ + Get the shift that determines the coset for a given ``cell_index``. + Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB. + Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL. + Then, the coset is defined as h * G = {h, hg, hg^2, ...} for an element h. + This function returns h. + """ + assert cell_index < CELLS_PER_EXT_BLOB + roots_of_unity_brp = bit_reversal_permutation( + compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB) + ) + return roots_of_unity_brp[FIELD_ELEMENTS_PER_CELL * cell_index] +``` + +#### `coset_for_cell` + +```python +def coset_for_cell(cell_index: CellIndex) -> Coset: + """ + Get the coset for a given ``cell_index``. + Precisely, consider the group of roots of unity of order FIELD_ELEMENTS_PER_CELL * CELLS_PER_EXT_BLOB. + Let G = {1, g, g^2, ...} denote its subgroup of order FIELD_ELEMENTS_PER_CELL. + Then, the coset is defined as h * G = {h, hg, hg^2, ...}. + This function, returns the coset. + """ + assert cell_index < CELLS_PER_EXT_BLOB + roots_of_unity_brp = bit_reversal_permutation( + compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB) + ) + return Coset( + roots_of_unity_brp[ + FIELD_ELEMENTS_PER_CELL * cell_index : FIELD_ELEMENTS_PER_CELL * (cell_index + 1) + ] + ) +``` + +## Cells + +### Cell computation + +#### `compute_cells` + +```python +def compute_cells(blob: Blob) -> Vector[Cell, CELLS_PER_EXT_BLOB]: + """ + Given a blob, extend it and return all the cells of the extended blob. + + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + + polynomial = blob_to_polynomial(blob) + polynomial_coeff = polynomial_eval_to_coeff(polynomial) + + cells = [] + for i in range(CELLS_PER_EXT_BLOB): + coset = coset_for_cell(CellIndex(i)) + ys = CosetEvals([evaluate_polynomialcoeff(polynomial_coeff, z) for z in coset]) + cells.append(coset_evals_to_cell(CosetEvals(ys))) + return cells +``` + +#### `compute_cells_and_kzg_proofs_polynomialcoeff` + +```python +def compute_cells_and_kzg_proofs_polynomialcoeff( + polynomial_coeff: PolynomialCoeff, +) -> Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]]: + """ + Helper function which computes cells/proofs for a polynomial in coefficient form. + """ + cells, proofs = [], [] + for i in range(CELLS_PER_EXT_BLOB): + coset = coset_for_cell(CellIndex(i)) + proof, ys = compute_kzg_proof_multi_impl(polynomial_coeff, coset) + cells.append(coset_evals_to_cell(CosetEvals(ys))) + proofs.append(proof) + return cells, proofs +``` + +#### `compute_cells_and_kzg_proofs` + +```python +def compute_cells_and_kzg_proofs( + blob: Blob, +) -> Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]]: + """ + Compute all the cell proofs for an extended blob. This is an inefficient O(n^2) algorithm, + for performant implementation the FK20 algorithm that runs in O(n log n) should be + used instead. + + Public method. + """ + assert len(blob) == BYTES_PER_BLOB + + polynomial = blob_to_polynomial(blob) + polynomial_coeff = polynomial_eval_to_coeff(polynomial) + return compute_cells_and_kzg_proofs_polynomialcoeff(polynomial_coeff) +``` + +### Cell verification + +#### `verify_cell_kzg_proof_batch` + +```python +def verify_cell_kzg_proof_batch( + commitments_bytes: Sequence[Bytes48], + cell_indices: Sequence[CellIndex], + cells: Sequence[Cell], + proofs_bytes: Sequence[Bytes48], +) -> bool: + """ + Verify that a set of cells belong to their corresponding commitments. + + Given four lists representing tuples of (``commitment``, ``cell_index``, ``cell``, ``proof``), + the function verifies ``proof`` which shows that ``cell`` are the evaluations of the polynomial + associated with ``commitment``, evaluated over the domain specified by ``cell_index``. + + This function implements the universal verification equation that has been introduced here: + https://ethresear.ch/t/a-universal-verification-equation-for-data-availability-sampling/13240 + + Public method. + """ + + assert len(commitments_bytes) == len(cells) == len(proofs_bytes) == len(cell_indices) + for commitment_bytes in commitments_bytes: + assert len(commitment_bytes) == BYTES_PER_COMMITMENT + for cell_index in cell_indices: + assert cell_index < CELLS_PER_EXT_BLOB + for cell in cells: + assert len(cell) == BYTES_PER_CELL + for proof_bytes in proofs_bytes: + assert len(proof_bytes) == BYTES_PER_PROOF + + # Create the list of deduplicated commitments we are dealing with + deduplicated_commitments = [ + bytes_to_kzg_commitment(commitment_bytes) for commitment_bytes in set(commitments_bytes) + ] + # Create indices list mapping initial commitments (that may contain duplicates) to the deduplicated commitments + commitment_indices = [ + CommitmentIndex(deduplicated_commitments.index(commitment_bytes)) + for commitment_bytes in commitments_bytes + ] + + cosets_evals = [cell_to_coset_evals(cell) for cell in cells] + proofs = [bytes_to_kzg_proof(proof_bytes) for proof_bytes in proofs_bytes] + + # Do the actual verification + return verify_cell_kzg_proof_batch_impl( + deduplicated_commitments, commitment_indices, cell_indices, cosets_evals, proofs + ) +``` + +## Reconstruction + +### `construct_vanishing_polynomial` + +```python +def construct_vanishing_polynomial( + missing_cell_indices: Sequence[CellIndex], +) -> Sequence[BLSFieldElement]: + """ + Given the cells indices that are missing from the data, compute the polynomial that vanishes at every point that + corresponds to a missing field element. + + This method assumes that all of the cells cannot be missing. In this case the vanishing polynomial + could be computed as Z(x) = x^n - 1, where `n` is FIELD_ELEMENTS_PER_EXT_BLOB. + + We never encounter this case however because this method is used solely for recovery and recovery only + works if at least half of the cells are available. + """ + # Get the small domain + roots_of_unity_reduced = compute_roots_of_unity(CELLS_PER_EXT_BLOB) + + # Compute polynomial that vanishes at all the missing cells (over the small domain) + short_zero_poly = vanishing_polynomialcoeff( + [ + roots_of_unity_reduced[reverse_bits(missing_cell_index, CELLS_PER_EXT_BLOB)] + for missing_cell_index in missing_cell_indices + ] + ) + + # Extend vanishing polynomial to full domain using the closed form of the vanishing polynomial over a coset + zero_poly_coeff = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_EXT_BLOB + for i, coeff in enumerate(short_zero_poly): + zero_poly_coeff[i * FIELD_ELEMENTS_PER_CELL] = coeff + + return zero_poly_coeff +``` + +### `recover_polynomialcoeff` + +```python +def recover_polynomialcoeff( + cell_indices: Sequence[CellIndex], cosets_evals: Sequence[CosetEvals] +) -> PolynomialCoeff: + """ + Recover the polynomial in coefficient form that when evaluated at the roots of unity will give the extended blob. + """ + # Get the extended domain. This will be referred to as the FFT domain. + roots_of_unity_extended = compute_roots_of_unity(FIELD_ELEMENTS_PER_EXT_BLOB) + + # Flatten the cosets evaluations. + # If a cell is missing, then its evaluation is zero. + # We let E(x) be a polynomial of degree FIELD_ELEMENTS_PER_EXT_BLOB - 1 + # that interpolates the evaluations including the zeros for missing ones. + extended_evaluation_rbo = [BLSFieldElement(0)] * FIELD_ELEMENTS_PER_EXT_BLOB + for cell_index, cell in zip(cell_indices, cosets_evals): + start = cell_index * FIELD_ELEMENTS_PER_CELL + end = (cell_index + 1) * FIELD_ELEMENTS_PER_CELL + extended_evaluation_rbo[start:end] = cell + extended_evaluation = bit_reversal_permutation(extended_evaluation_rbo) + + # Compute the vanishing polynomial Z(x) in coefficient form. + # Z(x) is the polynomial which vanishes on all of the evaluations which are missing. + missing_cell_indices = [ + CellIndex(cell_index) + for cell_index in range(CELLS_PER_EXT_BLOB) + if cell_index not in cell_indices + ] + zero_poly_coeff = construct_vanishing_polynomial(missing_cell_indices) + + # Convert Z(x) to evaluation form over the FFT domain + zero_poly_eval = fft_field(zero_poly_coeff, roots_of_unity_extended) + + # Compute (E*Z)(x) = E(x) * Z(x) in evaluation form over the FFT domain + # Note: over the FFT domain, the polynomials (E*Z)(x) and (P*Z)(x) agree, where + # P(x) is the polynomial we want to reconstruct (degree FIELD_ELEMENTS_PER_BLOB - 1). + extended_evaluation_times_zero = [a * b for a, b in zip(zero_poly_eval, extended_evaluation)] + + # We know that (E*Z)(x) and (P*Z)(x) agree over the FFT domain, + # and we know that (P*Z)(x) has degree at most FIELD_ELEMENTS_PER_EXT_BLOB - 1. + # Thus, an inverse FFT of the evaluations of (E*Z)(x) (= evaluations of (P*Z)(x)) + # yields the coefficient form of (P*Z)(x). + extended_evaluation_times_zero_coeffs = fft_field( + extended_evaluation_times_zero, roots_of_unity_extended, inv=True + ) + + # Next step is to divide the polynomial (P*Z)(x) by polynomial Z(x) to get P(x). + # We do this in evaluation form over a coset of the FFT domain to avoid division by 0. + + # Convert (P*Z)(x) to evaluation form over a coset of the FFT domain + extended_evaluations_over_coset = coset_fft_field( + extended_evaluation_times_zero_coeffs, roots_of_unity_extended + ) + + # Convert Z(x) to evaluation form over a coset of the FFT domain + zero_poly_over_coset = coset_fft_field(zero_poly_coeff, roots_of_unity_extended) + + # Compute P(x) = (P*Z)(x) / Z(x) in evaluation form over a coset of the FFT domain + reconstructed_poly_over_coset = [ + a / b for a, b in zip(extended_evaluations_over_coset, zero_poly_over_coset) + ] + + # Convert P(x) to coefficient form + reconstructed_poly_coeff = coset_fft_field( + reconstructed_poly_over_coset, roots_of_unity_extended, inv=True + ) + + return PolynomialCoeff(reconstructed_poly_coeff[:FIELD_ELEMENTS_PER_BLOB]) +``` + +### `recover_cells_and_kzg_proofs` + +```python +def recover_cells_and_kzg_proofs( + cell_indices: Sequence[CellIndex], cells: Sequence[Cell] +) -> Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]]: + """ + Given at least 50% of cells for a blob, recover all the cells/proofs. + This algorithm uses FFTs to recover cells faster than using Lagrange + implementation, as can be seen here: + https://ethresear.ch/t/reed-solomon-erasure-code-recovery-in-n-log-2-n-time-with-ffts/3039 + + A faster version thanks to Qi Zhou can be found here: + https://github.com/ethereum/research/blob/51b530a53bd4147d123ab3e390a9d08605c2cdb8/polynomial_reconstruction/polynomial_reconstruction_danksharding.py + + Public method. + """ + # Check we have the same number of cells and indices + assert len(cell_indices) == len(cells) + # Check we have enough cells to be able to perform the reconstruction + assert CELLS_PER_EXT_BLOB // 2 <= len(cell_indices) <= CELLS_PER_EXT_BLOB + # Check for duplicates + assert len(cell_indices) == len(set(cell_indices)) + # Check that the cell indices are within bounds + for cell_index in cell_indices: + assert cell_index < CELLS_PER_EXT_BLOB + # Check that each cell is the correct length + for cell in cells: + assert len(cell) == BYTES_PER_CELL + + # Convert cells to coset evaluations + cosets_evals = [cell_to_coset_evals(cell) for cell in cells] + + # Given the coset evaluations, recover the polynomial in coefficient form + polynomial_coeff = recover_polynomialcoeff(cell_indices, cosets_evals) + + # Recompute all cells/proofs + return compute_cells_and_kzg_proofs_polynomialcoeff(polynomial_coeff) +``` diff --git a/specs/fulu/validator.md b/specs/fulu/validator.md new file mode 100644 index 0000000000..42cf06a365 --- /dev/null +++ b/specs/fulu/validator.md @@ -0,0 +1,307 @@ +# Fulu -- Honest Validator + + + +- [Introduction](#introduction) +- [Prerequisites](#prerequisites) +- [Configuration](#configuration) + - [Custody setting](#custody-setting) +- [Helpers](#helpers) + - [`BlobsBundle`](#blobsbundle) + - [Modified `GetPayloadResponse`](#modified-getpayloadresponse) +- [Protocol](#protocol) + - [`ExecutionEngine`](#executionengine) + - [Modified `get_payload`](#modified-get_payload) +- [Beacon chain responsibilities](#beacon-chain-responsibilities) + - [Validator custody](#validator-custody) + - [Block and sidecar proposal](#block-and-sidecar-proposal) + - [Constructing the sidecars](#constructing-the-sidecars) + - [`get_data_column_sidecars`](#get_data_column_sidecars) + - [`get_data_column_sidecars_from_block`](#get_data_column_sidecars_from_block) + - [`get_data_column_sidecars_from_column_sidecar`](#get_data_column_sidecars_from_column_sidecar) + - [Sidecar publishing](#sidecar-publishing) + - [Sidecar retention](#sidecar-retention) + + + +## Introduction + +This document represents the changes to be made in the code of an "honest +validator" to implement Fulu. + +## Prerequisites + +This document is an extension of the +[Electra -- Honest Validator](../electra/validator.md) guide. All behaviors and +definitions defined in this document, and documents it extends, carry over +unless explicitly noted or overridden. + +All terminology, constants, functions, and protocol mechanics defined in +[Fulu -- Beacon Chain](./beacon-chain.md) and +[Fulu -- Data Availability Sampling Core](./das-core.md) are requisite for this +document and used throughout. + +## Configuration + +### Custody setting + +| Name | Value | Description | +| -------------------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------- | +| `VALIDATOR_CUSTODY_REQUIREMENT` | `8` | Minimum number of custody groups an honest node with validators attached custodies and serves samples from | +| `BALANCE_PER_ADDITIONAL_CUSTODY_GROUP` | `Gwei(32 * 10**9)` | Effective balance increment corresponding to one additional group to custody | + +## Helpers + +### `BlobsBundle` + +*[Modified in Fulu:EIP7594]* + +The `BlobsBundle` object is modified to include cell KZG proofs instead of blob +KZG proofs. + +```python +@dataclass +class BlobsBundle(object): + commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + # [Modified in Fulu:EIP7594] + proofs: List[KZGProof, FIELD_ELEMENTS_PER_EXT_BLOB * MAX_BLOB_COMMITMENTS_PER_BLOCK] + blobs: List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK] +``` + +### Modified `GetPayloadResponse` + +*[Modified in Fulu:EIP7594]* + +The `GetPayloadResponse` object is modified to use the updated `BlobsBundle` +object. + +```python +@dataclass +class GetPayloadResponse(object): + execution_payload: ExecutionPayload + block_value: uint256 + blobs_bundle: BlobsBundle # [Modified in Fulu:EIP7594] + execution_requests: Sequence[bytes] +``` + +## Protocol + +### `ExecutionEngine` + +#### Modified `get_payload` + +The `get_payload` method is modified to return the updated `GetPayloadResponse` +object. + +```python +def get_payload(self: ExecutionEngine, payload_id: PayloadId) -> GetPayloadResponse: + """ + Return ExecutionPayload, uint256, BlobsBundle objects. + """ + # pylint: disable=unused-argument + ... +``` + +## Beacon chain responsibilities + +### Validator custody + +*[New in Fulu:EIP7594]* + +A node with validators attached downloads and custodies a higher minimum of +custody groups per slot, determined by +`get_validators_custody_requirement(state, validator_indices)`. Here, `state` is +the latest finalized `BeaconState` and `validator_indices` is the list of +indices corresponding to validators attached to the node. Any node with at least +one validator attached, and with the sum of the effective balances of all +attached validators being `total_node_balance`, downloads and custodies +`total_node_balance // BALANCE_PER_ADDITIONAL_CUSTODY_GROUP` custody groups per +slot, with a minimum of `VALIDATOR_CUSTODY_REQUIREMENT` and of course a maximum +of `NUMBER_OF_CUSTODY_GROUPS`. + +```python +def get_validators_custody_requirement( + state: BeaconState, validator_indices: Sequence[ValidatorIndex] +) -> uint64: + total_node_balance = sum( + state.validators[index].effective_balance for index in validator_indices + ) + count = total_node_balance // BALANCE_PER_ADDITIONAL_CUSTODY_GROUP + return min(max(count, VALIDATOR_CUSTODY_REQUIREMENT), NUMBER_OF_CUSTODY_GROUPS) +``` + +This higher custody is advertised in the node's Metadata by setting a higher +`custody_group_count` and in the node's ENR by setting a higher +`custody_group_count`. As with the regular custody requirement, a node with +validators MAY still choose to custody, advertise and serve more than this +minimum. As with the regular custody requirement, a node MUST backfill columns +when syncing. + +A node SHOULD dynamically adjust its custody groups (without any input from the +user) following any changes to the total effective balances of attached +validators. + +If the node's custody requirements are increased, it SHOULD immediately +advertise the updated `custody_group_count`. It MAY backfill custody groups as a +result of this change. + +If a node's custody requirements decrease, it SHOULD NOT update the +`custody_group_count` to reflect this reduction. The node SHOULD continue to +custody and advertise the previous (highest) `custody_group_count`. The node +SHOULD continue to respond to any `DataColumnSidecar` request corresponding to +the previous (highest) `custody_group_count`. The previous (highest) +`custody_group_count` SHOULD persist across node restarts. + +Nodes SHOULD be capable of handling multiple changes to custody requirements +within the same retention period (e.g., an increase in one epoch followed by a +decrease in the next). + +When a value for `custody_group_count` is set, the `earliest_available_slot` +field in the status RPC message SHOULD reflect the slot at which the +`custody_group_count` was updated. + +If the node decides to backfill due to the `custody_group_count` change, the +`earliest_available_slot` field in the status RPC message MAY be updated with +progressively lower values as the backfill process advances. + +### Block and sidecar proposal + +#### Constructing the sidecars + +*[New in Fulu:EIP7594]* + +For a block proposal, blobs associated with a block are packaged into many +`DataColumnSidecar` objects for distribution to the associated sidecar topic, +the `data_column_sidecar_{subnet_id}` pubsub topic. A `DataColumnSidecar` can be +viewed as vertical slice of all blobs stacked on top of each other, with extra +fields for the necessary context. + +##### `get_data_column_sidecars` + +The sidecars associated with a block can be created by calling +[`engine_getPayloadV5`](https://github.com/ethereum/execution-apis/blob/main/src/engine/osaka.md#engine_getpayloadv5), +then constructing the list of cells and proofs for each blob (as defined in the +example below) using the blobs bundle in the response, and finally by calling +`get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs)`. + + + +```python +cells_and_kzg_proofs = [] +for i, blob in enumerate(blobs_bundle.blobs): + start = i * CELLS_PER_EXT_BLOB + end = (i + 1) * CELLS_PER_EXT_BLOB + cell_proofs = zip(compute_cells(blob), blobs_bundle.proofs[start:end]) + cells_and_kzg_proofs.extend(cell_proofs) +``` + +Moreover, the full sequence of sidecars can also be computed from +`cells_and_kzg_proofs` and any single `sidecar` by calling +`get_data_column_sidecars_from_column_sidecar(sidecar, cells_and_kzg_proofs)`. +This can be used in distributed blob publishing, to reconstruct all sidecars +from any sidecar received on the wire, assuming all cells and kzg proofs could +be retrieved from the local execution layer client. + +```python +def get_data_column_sidecars( + signed_block_header: SignedBeaconBlockHeader, + kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK], + kzg_commitments_inclusion_proof: Vector[Bytes32, KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH], + cells_and_kzg_proofs: Sequence[ + Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]] + ], +) -> Sequence[DataColumnSidecar]: + """ + Given a signed block header and the commitments, inclusion proof, cells/proofs associated with + each blob in the block, assemble the sidecars which can be distributed to peers. + """ + assert len(cells_and_kzg_proofs) == len(kzg_commitments) + + sidecars = [] + for column_index in range(NUMBER_OF_COLUMNS): + column_cells, column_proofs = [], [] + for cells, proofs in cells_and_kzg_proofs: + column_cells.append(cells[column_index]) + column_proofs.append(proofs[column_index]) + sidecars.append( + DataColumnSidecar( + index=column_index, + column=column_cells, + kzg_commitments=kzg_commitments, + kzg_proofs=column_proofs, + signed_block_header=signed_block_header, + kzg_commitments_inclusion_proof=kzg_commitments_inclusion_proof, + ) + ) + return sidecars +``` + +##### `get_data_column_sidecars_from_block` + +```python +def get_data_column_sidecars_from_block( + signed_block: SignedBeaconBlock, + cells_and_kzg_proofs: Sequence[ + Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]] + ], +) -> Sequence[DataColumnSidecar]: + """ + Given a signed block and the cells/proofs associated with each blob in the + block, assemble the sidecars which can be distributed to peers. + """ + blob_kzg_commitments = signed_block.message.body.blob_kzg_commitments + signed_block_header = compute_signed_block_header(signed_block) + kzg_commitments_inclusion_proof = compute_merkle_proof( + signed_block.message.body, + get_generalized_index(BeaconBlockBody, "blob_kzg_commitments"), + ) + return get_data_column_sidecars( + signed_block_header, + blob_kzg_commitments, + kzg_commitments_inclusion_proof, + cells_and_kzg_proofs, + ) +``` + +##### `get_data_column_sidecars_from_column_sidecar` + +```python +def get_data_column_sidecars_from_column_sidecar( + sidecar: DataColumnSidecar, + cells_and_kzg_proofs: Sequence[ + Tuple[Vector[Cell, CELLS_PER_EXT_BLOB], Vector[KZGProof, CELLS_PER_EXT_BLOB]] + ], +) -> Sequence[DataColumnSidecar]: + """ + Given a DataColumnSidecar and the cells/proofs associated with each blob corresponding + to the commitments it contains, assemble all sidecars for distribution to peers. + """ + assert len(cells_and_kzg_proofs) == len(sidecar.kzg_commitments) + + return get_data_column_sidecars( + sidecar.signed_block_header, + sidecar.kzg_commitments, + sidecar.kzg_commitments_inclusion_proof, + cells_and_kzg_proofs, + ) +``` + +#### Sidecar publishing + +The `subnet_id` for the `data_column_sidecar` is calculated with: + +- Let `column_index = data_column_sidecar.index`. +- Let `subnet_id = compute_subnet_for_data_column_sidecar(column_index)`. + +After publishing all columns to their respective subnets, peers on the network +may request the sidecar through sync-requests, or a local user may be +interested. + +#### Sidecar retention + +The validator MUST hold on to sidecars for +`MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` epochs and serve when capable, to +ensure the data-availability of these blobs throughout the network. + +After `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` nodes MAY prune the +sidecars and/or stop serving them. diff --git a/specs/merge/beacon-chain.md b/specs/merge/beacon-chain.md deleted file mode 100644 index ed12d3eb6c..0000000000 --- a/specs/merge/beacon-chain.md +++ /dev/null @@ -1,373 +0,0 @@ -# The Merge -- The Beacon Chain - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Custom types](#custom-types) -- [Constants](#constants) - - [Execution](#execution) -- [Configuration](#configuration) - - [Genesis testing settings](#genesis-testing-settings) -- [Containers](#containers) - - [Extended containers](#extended-containers) - - [`BeaconBlockBody`](#beaconblockbody) - - [`BeaconState`](#beaconstate) - - [New containers](#new-containers) - - [`ExecutionPayload`](#executionpayload) - - [`ExecutionPayloadHeader`](#executionpayloadheader) -- [Helper functions](#helper-functions) - - [Predicates](#predicates) - - [`is_merge_complete`](#is_merge_complete) - - [`is_merge_block`](#is_merge_block) - - [`is_execution_enabled`](#is_execution_enabled) - - [Misc](#misc) - - [`compute_timestamp_at_slot`](#compute_timestamp_at_slot) -- [Beacon chain state transition function](#beacon-chain-state-transition-function) - - [Execution engine](#execution-engine) - - [`on_payload`](#on_payload) - - [Block processing](#block-processing) - - [Execution payload processing](#execution-payload-processing) - - [`is_valid_gas_limit`](#is_valid_gas_limit) - - [`process_execution_payload`](#process_execution_payload) -- [Testing](#testing) - - - - -## Introduction - -This patch adds transaction execution to the beacon chain as part of the Merge fork. - -## Custom types - -*Note*: The `Transaction` type is a stub which is not final. - -| Name | SSZ equivalent | Description | -| - | - | - | -| `OpaqueTransaction` | `ByteList[MAX_BYTES_PER_OPAQUE_TRANSACTION]` | a [typed transaction envelope](https://eips.ethereum.org/EIPS/eip-2718#opaque-byte-array-rather-than-an-rlp-array) structured as `TransactionType \|\| TransactionPayload` | -| `Transaction` | `Union[OpaqueTransaction]` | a transaction | - -## Constants - -### Execution - -| Name | Value | -| - | - | -| `MAX_BYTES_PER_OPAQUE_TRANSACTION` | `uint64(2**20)` (= 1,048,576) | -| `MAX_TRANSACTIONS_PER_PAYLOAD` | `uint64(2**14)` (= 16,384) | -| `BYTES_PER_LOGS_BLOOM` | `uint64(2**8)` (= 256) | -| `GAS_LIMIT_DENOMINATOR` | `uint64(2**10)` (= 1,024) | -| `MIN_GAS_LIMIT` | `uint64(5000)` (= 5,000) | - -## Configuration - -### Genesis testing settings - -*Note*: These configuration settings do not apply to the mainnet and are utilized only by pure Merge testing. - -| Name | Value | -| - | - | -| `GENESIS_GAS_LIMIT` | `uint64(30000000)` (= 30,000,000) | -| `GENESIS_BASE_FEE_PER_GAS` | `Bytes32('0x00ca9a3b00000000000000000000000000000000000000000000000000000000')` (= 1,000,000,000) | - -## Containers - -### Extended containers - -#### `BeaconBlockBody` - -```python -class BeaconBlockBody(Container): - randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations - proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] - attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] - attestations: List[Attestation, MAX_ATTESTATIONS] - deposits: List[Deposit, MAX_DEPOSITS] - voluntary_exits: List[SignedVoluntaryExit, MAX_VOLUNTARY_EXITS] - sync_aggregate: SyncAggregate - # Execution - execution_payload: ExecutionPayload # [New in Merge] -``` - -#### `BeaconState` - -```python -class BeaconState(Container): - # Versioning - genesis_time: uint64 - genesis_validators_root: Root - slot: Slot - fork: Fork - # History - latest_block_header: BeaconBlockHeader - block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] - historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] - # Eth1 - eth1_data: Eth1Data - eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] - eth1_deposit_index: uint64 - # Registry - validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] - balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] - # Randomness - randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] - # Slashings - slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances - # Participation - previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] - # Finality - justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch - previous_justified_checkpoint: Checkpoint - current_justified_checkpoint: Checkpoint - finalized_checkpoint: Checkpoint - # Inactivity - inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] - # Sync - current_sync_committee: SyncCommittee - next_sync_committee: SyncCommittee - # Execution - latest_execution_payload_header: ExecutionPayloadHeader # [New in Merge] -``` - -### New containers - -#### `ExecutionPayload` - -*Note*: The `base_fee_per_gas` field is serialized in little-endian. - -```python -class ExecutionPayload(Container): - # Execution block header fields - parent_hash: Hash32 - coinbase: Bytes20 # 'beneficiary' in the yellow paper - state_root: Bytes32 - receipt_root: Bytes32 # 'receipts root' in the yellow paper - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 # 'difficulty' in the yellow paper - block_number: uint64 # 'number' in the yellow paper - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - base_fee_per_gas: Bytes32 # base fee introduced in EIP-1559, little-endian serialized - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] -``` - -#### `ExecutionPayloadHeader` - -```python -class ExecutionPayloadHeader(Container): - # Execution block header fields - parent_hash: Hash32 - coinbase: Bytes20 - state_root: Bytes32 - receipt_root: Bytes32 - logs_bloom: ByteVector[BYTES_PER_LOGS_BLOOM] - random: Bytes32 - block_number: uint64 - gas_limit: uint64 - gas_used: uint64 - timestamp: uint64 - base_fee_per_gas: Bytes32 - # Extra payload fields - block_hash: Hash32 # Hash of execution block - transactions_root: Root -``` - -## Helper functions - -### Predicates - -#### `is_merge_complete` - -```python -def is_merge_complete(state: BeaconState) -> bool: - return state.latest_execution_payload_header != ExecutionPayloadHeader() -``` - -#### `is_merge_block` - -```python -def is_merge_block(state: BeaconState, body: BeaconBlockBody) -> bool: - return not is_merge_complete(state) and body.execution_payload != ExecutionPayload() -``` - -#### `is_execution_enabled` - -```python -def is_execution_enabled(state: BeaconState, body: BeaconBlockBody) -> bool: - return is_merge_block(state, body) or is_merge_complete(state) -``` - -### Misc - -#### `compute_timestamp_at_slot` - -*Note*: This function is unsafe with respect to overflows and underflows. - -```python -def compute_timestamp_at_slot(state: BeaconState, slot: Slot) -> uint64: - slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) -``` - -## Beacon chain state transition function - -### Execution engine - -The implementation-dependent `ExecutionEngine` protocol encapsulates the execution sub-system logic via: - -* a state object `self.execution_state` of type `ExecutionState` -* a state transition function `self.on_payload` which mutates `self.execution_state` - -#### `on_payload` - -```python -def on_payload(self: ExecutionEngine, execution_payload: ExecutionPayload) -> bool: - """ - Returns ``True`` iff ``execution_payload`` is valid with respect to ``self.execution_state``. - """ - ... -``` - -The above function is accessed through the `EXECUTION_ENGINE` module which instantiates the `ExecutionEngine` protocol. - -### Block processing - -```python -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) - process_sync_aggregate(state, block.body.sync_aggregate) - if is_execution_enabled(state, block.body): - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) # [New in Merge] -``` - -### Execution payload processing - -#### `is_valid_gas_limit` - -```python -def is_valid_gas_limit(payload: ExecutionPayload, parent: ExecutionPayloadHeader) -> bool: - parent_gas_limit = parent.gas_limit - - # Check if the payload used too much gas - if payload.gas_used > payload.gas_limit: - return False - - # Check if the payload changed the gas limit too much - if payload.gas_limit >= parent_gas_limit + parent_gas_limit // GAS_LIMIT_DENOMINATOR: - return False - if payload.gas_limit <= parent_gas_limit - parent_gas_limit // GAS_LIMIT_DENOMINATOR: - return False - - # Check if the gas limit is at least the minimum gas limit - if payload.gas_limit < MIN_GAS_LIMIT: - return False - - return True -``` - -#### `process_execution_payload` - -*Note:* This function depends on `process_randao` function call as it retrieves the most recent randao mix from the `state`. Implementations that are considering parallel processing of execution payload with respect to beacon chain state transition function should work around this dependency. - -```python -def process_execution_payload(state: BeaconState, payload: ExecutionPayload, execution_engine: ExecutionEngine) -> None: - # Verify consistency of the parent hash, block number, random, base fee per gas and gas limit - if is_merge_complete(state): - assert payload.parent_hash == state.latest_execution_payload_header.block_hash - assert payload.block_number == state.latest_execution_payload_header.block_number + uint64(1) - assert payload.random == get_randao_mix(state, get_current_epoch(state)) - assert is_valid_gas_limit(payload, state.latest_execution_payload_header) - # Verify timestamp - assert payload.timestamp == compute_timestamp_at_slot(state, state.slot) - # Verify the execution payload is valid - assert execution_engine.on_payload(payload) - # Cache execution payload header - state.latest_execution_payload_header = ExecutionPayloadHeader( - parent_hash=payload.parent_hash, - coinbase=payload.coinbase, - state_root=payload.state_root, - receipt_root=payload.receipt_root, - logs_bloom=payload.logs_bloom, - random=payload.random, - block_number=payload.block_number, - gas_limit=payload.gas_limit, - gas_used=payload.gas_used, - timestamp=payload.timestamp, - base_fee_per_gas=payload.base_fee_per_gas, - block_hash=payload.block_hash, - transactions_root=hash_tree_root(payload.transactions), - ) -``` - -## Testing - -*Note*: The function `initialize_beacon_state_from_eth1` is modified for pure Merge testing only. - -*Note*: The function `initialize_beacon_state_from_eth1` is modified: (1) using `MERGE_FORK_VERSION` as the current fork version, (2) utilizing the Merge `BeaconBlockBody` when constructing the initial `latest_block_header`, and (3) initialize `latest_execution_payload_header`. - -```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit]) -> BeaconState: - fork = Fork( - previous_version=GENESIS_FORK_VERSION, - current_version=MERGE_FORK_VERSION, # [Modified in Merge] - 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 Merge] Initialize the execution payload header (with block number set to 0) - state.latest_execution_payload_header.block_hash = eth1_block_hash - state.latest_execution_payload_header.timestamp = eth1_timestamp - state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = GENESIS_GAS_LIMIT - state.latest_execution_payload_header.base_fee_per_gas = GENESIS_BASE_FEE_PER_GAS - - return state -``` diff --git a/specs/merge/client-settings.md b/specs/merge/client-settings.md deleted file mode 100644 index a8ca633ff3..0000000000 --- a/specs/merge/client-settings.md +++ /dev/null @@ -1,26 +0,0 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [The Merge -- Client Settings](#the-merge----client-settings) - - [Override terminal total difficulty](#override-terminal-total-difficulty) - - - -# The Merge -- Client Settings - -**Notice**: This document is a work-in-progress for researchers and implementers. - -This document specifies configurable settings that clients must implement for the Merge. - -### Override terminal total difficulty - -To coordinate manual overrides to [`terminal_total_difficulty`](fork-choice.md#transitionstore), clients -must provide `--terminal-total-difficulty-override` as a configurable setting. - -If `TransitionStore` has already [been initialized](./fork.md#initializing-transition-store), this alters the previously initialized value of -`TransitionStore.terminal_total_difficulty`, otherwise this setting initializes `TransitionStore` with the specified, bypassing `compute_terminal_total_difficulty` and the use of an `anchor_pow_block`. -`terminal_total_difficulty`. - -Except under exceptional scenarios, this setting is expected to not be used, and `terminal_total_difficulty` will operate with [default functionality](./fork.md#initializing-transition-store). Sufficient warning to the user about this exceptional configurable setting should be provided. -[here](fork.md#initializing-transition-store). diff --git a/specs/merge/fork-choice.md b/specs/merge/fork-choice.md deleted file mode 100644 index 84be0f1ce7..0000000000 --- a/specs/merge/fork-choice.md +++ /dev/null @@ -1,167 +0,0 @@ -# The Merge -- Fork Choice - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - -- [Introduction](#introduction) -- [Protocols](#protocols) - - [`ExecutionEngine`](#executionengine) - - [`set_head`](#set_head) - - [`finalize_block`](#finalize_block) -- [Helpers](#helpers) - - [`TransitionStore`](#transitionstore) - - [`PowBlock`](#powblock) - - [`get_pow_block`](#get_pow_block) - - [`is_valid_terminal_pow_block`](#is_valid_terminal_pow_block) -- [Updated fork-choice handlers](#updated-fork-choice-handlers) - - [`on_block`](#on_block) - - - - -## Introduction - -This is the modification of the fork choice according to the executable beacon chain proposal. - -*Note*: It introduces the process of transition from the last PoW block to the first PoS block. - -## Protocols - -### `ExecutionEngine` - -The following methods are added to the `ExecutionEngine` protocol for use in the fork choice: - -#### `set_head` - -Re-organizes the execution payload chain and corresponding state to make `block_hash` the head. - -The body of this function is implementation dependent. -The Consensus API may be used to implement this with an external execution engine. - -```python -def set_head(self: ExecutionEngine, block_hash: Hash32) -> bool: - """ - Returns True if the ``block_hash`` was successfully set as head of the execution payload chain. - """ - ... -``` - -#### `finalize_block` - -Applies finality to the execution state: it irreversibly persists the chain of all execution payloads -and corresponding state, up to and including `block_hash`. - -The body of this function is implementation dependent. -The Consensus API may be used to implement this with an external execution engine. - -```python -def finalize_block(self: ExecutionEngine, block_hash: Hash32) -> bool: - """ - Returns True if the data up to and including ``block_hash`` was successfully finalized. - """ - ... -``` - -## Helpers - -### `TransitionStore` - -```python -@dataclass -class TransitionStore(object): - terminal_total_difficulty: uint256 -``` - -### `PowBlock` - -```python -@dataclass -class PowBlock(object): - block_hash: Hash32 - parent_hash: Hash32 - total_difficulty: uint256 - difficulty: uint256 -``` - -### `get_pow_block` - -Let `get_pow_block(block_hash: Hash32) -> PowBlock` be the function that given the hash of the PoW block returns its data. - -*Note*: The `eth_getBlockByHash` JSON-RPC method may be used to pull this information from an execution client. - -### `is_valid_terminal_pow_block` - -Used by fork-choice handler, `on_block`. - -```python -def is_valid_terminal_pow_block(transition_store: TransitionStore, block: PowBlock, parent: PowBlock) -> bool: - is_total_difficulty_reached = block.total_difficulty >= transition_store.terminal_total_difficulty - is_parent_total_difficulty_valid = parent.total_difficulty < transition_store.terminal_total_difficulty - return is_total_difficulty_reached and is_parent_total_difficulty_valid -``` - -## Updated fork-choice handlers - -### `on_block` - -*Note*: The only modification is the addition of the verification of transition block conditions. - -```python -def on_block(store: Store, signed_block: SignedBeaconBlock, transition_store: TransitionStore=None) -> None: - block = signed_block.message - # Parent block must be known - assert block.parent_root in store.block_states - # Make a copy of the state to avoid mutability issues - pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. - assert get_current_slot(store) >= block.slot - - # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - assert block.slot > finalized_slot - # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root - - # Check the block is valid and compute the post-state - state = pre_state.copy() - state_transition(state, signed_block, True) - - # [New in Merge] - if (transition_store is not None) and is_merge_block(pre_state, block.body): - pow_block = get_pow_block(block.body.execution_payload.parent_hash) - pow_parent = get_pow_block(pow_block.parent_hash) - assert is_valid_terminal_pow_block(transition_store, pow_block, pow_parent) - - # Add new block to the store - store.blocks[hash_tree_root(block)] = block - # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state - - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint - - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint - - # Potentially update justified if different from store - if store.justified_checkpoint != state.current_justified_checkpoint: - # Update justified if new justified is later than store justified - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - store.justified_checkpoint = state.current_justified_checkpoint - return - - # Update justified if store justified is not in chain with finalized checkpoint - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot != store.finalized_checkpoint.root: - store.justified_checkpoint = state.current_justified_checkpoint -``` diff --git a/specs/merge/fork.md b/specs/merge/fork.md deleted file mode 100644 index f2547758da..0000000000 --- a/specs/merge/fork.md +++ /dev/null @@ -1,132 +0,0 @@ -# The Merge -- Fork Logic - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - -- [Introduction](#introduction) -- [Configuration](#configuration) -- [Fork to Merge](#fork-to-merge) - - [Fork trigger](#fork-trigger) - - [Upgrading the state](#upgrading-the-state) - - [Initializing transition store](#initializing-transition-store) - - - -## Introduction - -This document describes the process of the Merge upgrade. - -## Configuration - -Warning: this configuration is not definitive. - -| Name | Value | -| - | - | -| `MERGE_FORK_VERSION` | `Version('0x02000000')` | -| `MERGE_FORK_EPOCH` | `Epoch(18446744073709551615)` **TBD** | -| `MIN_ANCHOR_POW_BLOCK_DIFFICULTY` | **TBD** | -| `TARGET_SECONDS_TO_MERGE` | `uint64(7 * 86400)` = (604,800) | - -## Fork to Merge - -### Fork trigger - -TBD. Social consensus, along with state conditions such as epoch boundary, finality, deposits, active validator count, etc. may be part of the decision process to trigger the fork. For now we assume the condition will be triggered at epoch `MERGE_FORK_EPOCH`. - -Since the Merge transition process relies on `Eth1Data` in the beacon state we do want to make sure that this data is fresh. This is achieved by forcing `MERGE_FORK_EPOCH` to point to eth1 voting period boundary, i.e. `MERGE_FORK_EPOCH` should satisfy the following condition `MERGE_FORK_EPOCH % EPOCHS_PER_ETH1_VOTING_PERIOD == 0`. - -Note that for the pure Merge networks, we don't apply `upgrade_to_merge` since it starts with Merge version logic. - -### Upgrading the state - -As with the Phase0-to-Altair upgrade, the `state_transition` is modified to upgrade the `BeaconState`. -The `BeaconState` upgrade runs as part of `process_slots`, slots with missing block proposals do not affect the upgrade time. - -If `state.slot % SLOTS_PER_EPOCH == 0` and `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, an irregular state change is made to upgrade to Merge. -The upgrade occurs after the completion of the inner loop of `process_slots` that sets `state.slot` equal to `MERGE_FORK_EPOCH * SLOTS_PER_EPOCH`. - -When multiple upgrades are scheduled for the same epoch (common for test-networks), -all the upgrades run in sequence before resuming the regular state transition. - -```python -def upgrade_to_merge(pre: altair.BeaconState) -> BeaconState: - epoch = altair.get_current_epoch(pre) - post = BeaconState( - # Versioning - genesis_time=pre.genesis_time, - genesis_validators_root=pre.genesis_validators_root, - slot=pre.slot, - fork=Fork( - previous_version=pre.fork.current_version, - current_version=MERGE_FORK_VERSION, - epoch=epoch, - ), - # History - latest_block_header=pre.latest_block_header, - block_roots=pre.block_roots, - state_roots=pre.state_roots, - historical_roots=pre.historical_roots, - # Eth1 - eth1_data=pre.eth1_data, - eth1_data_votes=pre.eth1_data_votes, - eth1_deposit_index=pre.eth1_deposit_index, - # Registry - validators=pre.validators, - balances=pre.balances, - # Randomness - randao_mixes=pre.randao_mixes, - # Slashings - slashings=pre.slashings, - # Participation - previous_epoch_participation=pre.previous_epoch_participation, - current_epoch_participation=pre.current_epoch_participation, - # Finality - justification_bits=pre.justification_bits, - previous_justified_checkpoint=pre.previous_justified_checkpoint, - current_justified_checkpoint=pre.current_justified_checkpoint, - finalized_checkpoint=pre.finalized_checkpoint, - # Inactivity - inactivity_scores=pre.inactivity_scores, - # Sync - current_sync_committee=pre.current_sync_committee, - next_sync_committee=pre.next_sync_committee, - # Execution-layer - latest_execution_payload_header=ExecutionPayloadHeader(), - ) - - return post -``` - -### Initializing transition store - -If `state.slot % SLOTS_PER_EPOCH == 0`, `compute_epoch_at_slot(state.slot) == MERGE_FORK_EPOCH`, and the transition store has not already been initialized, a transition store is initialized to be further utilized by the transition process of the Merge. - -Transition store initialization occurs after the state has been modified by corresponding `upgrade_to_merge` function. - -```python -def compute_terminal_total_difficulty(anchor_pow_block: PowBlock) -> uint256: - seconds_per_voting_period = EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH * SECONDS_PER_SLOT - pow_blocks_per_voting_period = seconds_per_voting_period // SECONDS_PER_ETH1_BLOCK - pow_blocks_to_merge = TARGET_SECONDS_TO_MERGE // SECONDS_PER_ETH1_BLOCK - pow_blocks_after_anchor_block = ETH1_FOLLOW_DISTANCE + pow_blocks_per_voting_period + pow_blocks_to_merge - anchor_difficulty = max(MIN_ANCHOR_POW_BLOCK_DIFFICULTY, anchor_pow_block.difficulty) - - return anchor_pow_block.total_difficulty + anchor_difficulty * pow_blocks_after_anchor_block - - -def get_transition_store(anchor_pow_block: PowBlock) -> TransitionStore: - terminal_total_difficulty = compute_terminal_total_difficulty(anchor_pow_block) - return TransitionStore(terminal_total_difficulty=terminal_total_difficulty) - - -def initialize_transition_store(state: BeaconState) -> TransitionStore: - pow_block = get_pow_block(state.eth1_data.block_hash) - return get_transition_store(pow_block) -``` - -*Note*: Transition store can also be initialized at client startup by [overriding terminal total -difficulty](client_settings.md#override-terminal-total-difficulty). diff --git a/specs/merge/p2p-interface.md b/specs/merge/p2p-interface.md deleted file mode 100644 index 85a96c31eb..0000000000 --- a/specs/merge/p2p-interface.md +++ /dev/null @@ -1,131 +0,0 @@ -# The Merge -- Networking - -This document contains the networking specification for the Merge. - -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 - - - - - - - [Warning](#warning) -- [Modifications in the Merge](#modifications-in-the-merge) - - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - - [Topics and messages](#topics-and-messages) - - [Global topics](#global-topics) - - [`beacon_block`](#beacon_block) - - [Transitioning the gossip](#transitioning-the-gossip) - - [The Req/Resp domain](#the-reqresp-domain) - - [Messages](#messages) - - [BeaconBlocksByRange v2](#beaconblocksbyrange-v2) - - [BeaconBlocksByRoot v2](#beaconblocksbyroot-v2) - - - - -## Warning - -This document is currently illustrative for early Merge testnets and some parts are subject to change. -Refer to the note in the [validator guide](./validator.md) for further details. - -# Modifications in the Merge - -## The gossip domain: gossipsub - -Some gossip meshes are upgraded in the Merge to support upgraded types. - -### Topics and messages - -Topics follow the same specification as in prior upgrades. -All topics remain stable except the beacon block topic which is updated with the modified type. - -The specification around the creation, validation, and dissemination of messages has not changed from the Phase 0 and Altair documents. - -The derivation of the `message-id` remains stable. - -The new topics along with the type of the `data` field of a gossipsub message are given in this table: - -| Name | Message Type | -| - | - | -| `beacon_block` | `SignedBeaconBlock` (modified) | - -Note that the `ForkDigestValue` path segment of the topic separates the old and the new `beacon_block` topics. - -#### Global topics - -The Merge changes the type of the global beacon block topic. - -##### `beacon_block` - -The *type* of the payload of this topic changes to the (modified) `SignedBeaconBlock` found in the Merge. -Specifically, this type changes with the addition of `execution_payload` to the inner `BeaconBlockBody`. -See the Merge [state transition document](./beacon-chain.md#beaconblockbody) for further details. - -In addition to the gossip validations for this topic from prior specifications, -the following validations MUST pass before forwarding the `signed_beacon_block` on the network. -Alias `block = signed_beacon_block.message`, `execution_payload = block.body.execution_payload`. -- If the merge is complete with respect to the head state -- i.e. `is_merge_complete(state)` -- - then validate the following: - - _[REJECT]_ The block's execution payload must be non-empty -- - i.e. `execution_payload != ExecutionPayload()` -- If the execution is enabled for the block -- i.e. `is_execution_enabled(state, block.body)` - then validate the following: - - _[REJECT]_ The block's execution payload timestamp is correct with respect to the slot - -- i.e. `execution_payload.timestamp == compute_time_at_slot(state, block.slot)`. - - _[REJECT]_ Gas used is less than the gas limit -- - i.e. `execution_payload.gas_used <= execution_payload.gas_limit`. - - _[REJECT]_ The execution payload block hash is not equal to the parent hash -- - i.e. `execution_payload.block_hash != execution_payload.parent_hash`. - - _[REJECT]_ The execution payload transaction list data is within expected size limits, - the data MUST NOT be larger than the SSZ list-limit, - and a client MAY be more strict. - -*Note*: Additional [gossip validations](https://github.com/ethereum/devp2p/blob/master/caps/eth.md#block-encoding-and-validity) -(see block "data validity" conditions) that rely more heavily on execution-layer state and logic are currently under consideration. - -### Transitioning the gossip - -See gossip transition details found in the [Altair document](../altair/p2p) for -details on how to handle transitioning gossip topics for the Merge. - -## The Req/Resp domain - -### Messages - -#### BeaconBlocksByRange v2 - -**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/2/` - -Request and Response remain unchanged. -The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. - -Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: - -[0]: # (eth2spec: skip) - -| `fork_version` | Chunk SSZ type | -| ------------------------ | -------------------------- | -| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | -| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | - -#### BeaconBlocksByRoot v2 - -**Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/2/` - -Request and Response remain unchanged. -The Merge fork-digest is introduced to the `context` enum to specify the Merge block type. - -Per `context = compute_fork_digest(fork_version, genesis_validators_root)`: - -[1]: # (eth2spec: skip) - -| `fork_version` | Chunk SSZ type | -| ------------------------ | -------------------------- | -| `GENESIS_FORK_VERSION` | `phase0.SignedBeaconBlock` | -| `ALTAIR_FORK_VERSION` | `altair.SignedBeaconBlock` | -| `MERGE_FORK_VERSION` | `merge.SignedBeaconBlock` | diff --git a/specs/merge/validator.md b/specs/merge/validator.md deleted file mode 100644 index efeeca0610..0000000000 --- a/specs/merge/validator.md +++ /dev/null @@ -1,109 +0,0 @@ -# The Merge -- Honest Validator - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Prerequisites](#prerequisites) -- [Protocols](#protocols) - - [`ExecutionEngine`](#executionengine) - - [`assemble_block`](#assemble_block) -- [Beacon chain responsibilities](#beacon-chain-responsibilities) - - [Block proposal](#block-proposal) - - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - - [Execution Payload](#execution-payload) - - - - -## Introduction - -This document represents the changes to be made in the code of an "honest validator" to implement executable beacon chain proposal. - -## Prerequisites - -This document is an extension of the [Altair -- Honest Validator](../altair/validator.md) guide. -All behaviors and definitions defined in this document, and documents it extends, carry over unless explicitly noted or overridden. - -All terminology, constants, functions, and protocol mechanics defined in the updated Beacon Chain doc of [The Merge](./beacon-chain.md) are requisite for this document and used throughout. -Please see related Beacon Chain doc before continuing and use them as a reference throughout. - -## Protocols - -### `ExecutionEngine` - -The following methods are added to the `ExecutionEngine` protocol for use as a validator: - -#### `assemble_block` - -Produces a new instance of an execution payload, with the specified `timestamp`, -on top of the execution payload chain tip identified by `block_hash`. - -The body of this function is implementation dependent. -The Consensus API may be used to implement this with an external execution engine. - -```python -def assemble_block(self: ExecutionEngine, block_hash: Hash32, timestamp: uint64, random: Bytes32) -> ExecutionPayload: - ... -``` - -## Beacon chain responsibilities - -All validator responsibilities remain unchanged other than those noted below. Namely, the transition block handling and the addition of `ExecutionPayload`. - -### Block proposal - -#### Constructing the `BeaconBlockBody` - -##### Execution Payload - -* Set `block.body.execution_payload = get_execution_payload(state, transition_store, execution_engine, pow_chain)` where: - -```python -def get_pow_block_at_total_difficulty(total_difficulty: uint256, pow_chain: Sequence[PowBlock]) -> Optional[PowBlock]: - # `pow_chain` abstractly represents all blocks in the PoW chain - for block in pow_chain: - parent = get_pow_block(block.parent_hash) - if block.total_difficulty >= total_difficulty and parent.total_difficulty < total_difficulty: - return block - - return None - - -def compute_randao_mix(state: BeaconState, randao_reveal: BLSSignature) -> Bytes32: - epoch = get_current_epoch(state) - return xor(get_randao_mix(state, epoch), hash(randao_reveal)) - - -def produce_execution_payload(state: BeaconState, - parent_hash: Hash32, - randao_reveal: BLSSignature, - execution_engine: ExecutionEngine) -> ExecutionPayload: - timestamp = compute_timestamp_at_slot(state, state.slot) - randao_mix = compute_randao_mix(state, randao_reveal) - return execution_engine.assemble_block(parent_hash, timestamp, randao_mix) - - -def get_execution_payload(state: BeaconState, - transition_store: TransitionStore, - randao_reveal: BLSSignature, - execution_engine: ExecutionEngine, - pow_chain: Sequence[PowBlock]) -> ExecutionPayload: - if not is_merge_complete(state): - terminal_pow_block = get_pow_block_at_total_difficulty(transition_store.terminal_total_difficulty, pow_chain) - if terminal_pow_block is None: - # Pre-merge, empty payload - return ExecutionPayload() - else: - # Signify merge via producing on top of the last PoW block - return produce_execution_payload(state, terminal_pow_block.block_hash, randao_reveal, execution_engine) - - # Post-merge, normal payload - parent_hash = state.latest_execution_payload_header.block_hash - return produce_execution_payload(state, parent_hash, randao_reveal, execution_engine) -``` diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index e2e235acf1..d9ef7dfe0c 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1,9 +1,6 @@ # Phase 0 -- The Beacon Chain -## Table of contents - - - + - [Introduction](#introduction) - [Notation](#notation) @@ -59,6 +56,7 @@ - [`xor`](#xor) - [`uint_to_bytes`](#uint_to_bytes) - [`bytes_to_uint64`](#bytes_to_uint64) + - [`saturating_sub`](#saturating_sub) - [Crypto](#crypto) - [`hash`](#hash) - [`hash_tree_root`](#hash_tree_root) @@ -75,6 +73,7 @@ - [`compute_shuffled_index`](#compute_shuffled_index) - [`compute_proposer_index`](#compute_proposer_index) - [`compute_committee`](#compute_committee) + - [`compute_time_at_slot`](#compute_time_at_slot) - [`compute_epoch_at_slot`](#compute_epoch_at_slot) - [`compute_start_slot_at_epoch`](#compute_start_slot_at_epoch) - [`compute_activation_exit_epoch`](#compute_activation_exit_epoch) @@ -135,15 +134,23 @@ - [Deposits](#deposits) - [Voluntary exits](#voluntary-exits) - - + ## Introduction This document represents the specification for Phase 0 -- The Beacon Chain. -At the core of Ethereum proof-of-stake is a system chain called the "beacon chain". The beacon chain stores and manages the registry of validators. In the initial deployment phases of proof-of-stake, the only mechanism to become a validator is to make a one-way ETH transaction to a deposit contract on the Ethereum proof-of-work chain. Activation as a validator happens when deposit receipts are processed by the beacon chain, the activation balance is reached, and a queuing process is completed. Exit is either voluntary or done forcibly as a penalty for misbehavior. -The primary source of load on the beacon chain is "attestations". Attestations are simultaneously availability votes for a shard block (in a later upgrade) and proof-of-stake votes for a beacon block (Phase 0). +At the core of Ethereum proof-of-stake is a system chain called the "beacon +chain". The beacon chain stores and manages the registry of validators. In the +initial deployment phases of proof-of-stake, the only mechanism to become a +validator is to make a one-way ETH transaction to a deposit contract on the +Ethereum proof-of-work chain. Activation as a validator happens when deposit +receipts are processed by the beacon chain, the activation balance is reached, +and a queuing process is completed. Exit is either voluntary or done forcibly as +a penalty for misbehavior. The primary source of load on the beacon chain is +"attestations". Attestations are simultaneously availability votes for a shard +block (in a later upgrade) and proof-of-stake votes for a beacon block (Phase +0). ## Notation @@ -153,50 +160,52 @@ Code snippets appearing in `this style` are to be interpreted as Python 3 code. We define the following Python custom types for type hinting and readability: -| Name | SSZ equivalent | Description | -| - | - | - | -| `Slot` | `uint64` | a slot number | -| `Epoch` | `uint64` | an epoch number | -| `CommitteeIndex` | `uint64` | a committee index at a slot | -| `ValidatorIndex` | `uint64` | a validator registry index | -| `Gwei` | `uint64` | an amount in Gwei | -| `Root` | `Bytes32` | a Merkle root | -| `Hash32` | `Bytes32` | a 256-bit hash | -| `Version` | `Bytes4` | a fork version number | -| `DomainType` | `Bytes4` | a domain type | -| `ForkDigest` | `Bytes4` | a digest of the current fork data | -| `Domain` | `Bytes32` | a signature domain | -| `BLSPubkey` | `Bytes48` | a BLS12-381 public key | -| `BLSSignature` | `Bytes96` | a BLS12-381 signature | - +| Name | SSZ equivalent | Description | +| ---------------- | -------------- | --------------------------------- | +| `Slot` | `uint64` | a slot number | +| `Epoch` | `uint64` | an epoch number | +| `CommitteeIndex` | `uint64` | a committee index at a slot | +| `ValidatorIndex` | `uint64` | a validator registry index | +| `Gwei` | `uint64` | an amount in Gwei | +| `Root` | `Bytes32` | a Merkle root | +| `Hash32` | `Bytes32` | a 256-bit hash | +| `Version` | `Bytes4` | a fork version number | +| `DomainType` | `Bytes4` | a domain type | +| `ForkDigest` | `Bytes4` | a digest of the current fork data | +| `Domain` | `Bytes32` | a signature domain | +| `BLSPubkey` | `Bytes48` | a BLS12-381 public key | +| `BLSSignature` | `Bytes96` | a BLS12-381 signature | ## Constants -The following values are (non-configurable) constants used throughout the specification. +The following values are (non-configurable) constants used throughout the +specification. ### Misc -| Name | Value | -| - | - | -| `GENESIS_SLOT` | `Slot(0)` | -| `GENESIS_EPOCH` | `Epoch(0)` | -| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` | -| `BASE_REWARDS_PER_EPOCH` | `uint64(4)` | +| Name | Value | +| ----------------------------- | --------------------- | +| `UINT64_MAX` | `uint64(2**64 - 1)` | +| `UINT64_MAX_SQRT` | `uint64(4294967295)` | +| `GENESIS_SLOT` | `Slot(0)` | +| `GENESIS_EPOCH` | `Epoch(0)` | +| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` | +| `BASE_REWARDS_PER_EPOCH` | `uint64(4)` | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `uint64(2**5)` (= 32) | -| `JUSTIFICATION_BITS_LENGTH` | `uint64(4)` | -| `ENDIANNESS` | `'little'` | +| `JUSTIFICATION_BITS_LENGTH` | `uint64(4)` | +| `ENDIANNESS` | `'little'` | ### Withdrawal prefixes -| Name | Value | -| - | - | -| `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` | +| Name | Value | +| -------------------------------- | ---------------- | +| `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` | | `ETH1_ADDRESS_WITHDRAWAL_PREFIX` | `Bytes1('0x01')` | ### Domain types -| Name | Value | -| - | - | +| Name | Value | +| ---------------------------- | -------------------------- | | `DOMAIN_BEACON_PROPOSER` | `DomainType('0x00000000')` | | `DOMAIN_BEACON_ATTESTER` | `DomainType('0x01000000')` | | `DOMAIN_RANDAO` | `DomainType('0x02000000')` | @@ -204,119 +213,149 @@ The following values are (non-configurable) constants used throughout the specif | `DOMAIN_VOLUNTARY_EXIT` | `DomainType('0x04000000')` | | `DOMAIN_SELECTION_PROOF` | `DomainType('0x05000000')` | | `DOMAIN_AGGREGATE_AND_PROOF` | `DomainType('0x06000000')` | +| `DOMAIN_APPLICATION_MASK` | `DomainType('0x00000001')` | + +*Note*: `DOMAIN_APPLICATION_MASK` reserves the rest of the bitspace in +`DomainType` for application usage. This means for some `DomainType` +`DOMAIN_SOME_APPLICATION`, `DOMAIN_SOME_APPLICATION & DOMAIN_APPLICATION_MASK` +**MUST** be non-zero. This expression for any other `DomainType` in the +consensus specs **MUST** be zero. ## Preset -*Note*: The below configuration is bundled as a preset: a bundle of configuration variables which are expected to differ -between different modes of operation, e.g. testing, but not generally between different networks. -Additional preset configurations can be found in the [`configs`](../../configs) directory. +*Note*: The below configuration is bundled as a preset: a bundle of +configuration variables which are expected to differ between different modes of +operation, e.g. testing, but not generally between different networks. +Additional preset configurations can be found in the [`configs`](../../configs) +directory. ### Misc -| Name | Value | -| - | - | -| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) | -| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | -| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) | -| `SHUFFLE_ROUND_COUNT` | `uint64(90)` | -| `HYSTERESIS_QUOTIENT` | `uint64(4)` | -| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` | -| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` | - -- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) +| Name | Value | +| -------------------------------- | ------------------------- | +| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) | +| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | +| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) | +| `SHUFFLE_ROUND_COUNT` | `uint64(90)` | +| `HYSTERESIS_QUOTIENT` | `uint64(4)` | +| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` | +| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` | + +- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds + [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); + with sufficient active validators (at least + `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures + committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness + with a Verifiable Delay Function (VDF) will improve committee robustness and + lower the safe minimum committee size.) ### Gwei values -| Name | Value | -| - | - | -| `MIN_DEPOSIT_AMOUNT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) | -| `MAX_EFFECTIVE_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | -| `EFFECTIVE_BALANCE_INCREMENT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) | +| Name | Value | +| ----------------------------- | --------------------------------------- | +| `MIN_DEPOSIT_AMOUNT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) | +| `MAX_EFFECTIVE_BALANCE` | `Gwei(2**5 * 10**9)` (= 32,000,000,000) | +| `EFFECTIVE_BALANCE_INCREMENT` | `Gwei(2**0 * 10**9)` (= 1,000,000,000) | ### Time parameters -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | -| `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes | -| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | -| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | -| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | -| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**6)` (= 64) | epochs | ~6.8 hours | -| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | +| Name | Value | Unit | Duration | +| ---------------------------------- | ------------------------- | :----: | :----------: | +| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | +| `SLOTS_PER_EPOCH` | `uint64(2**5)` (= 32) | slots | 6.4 minutes | +| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | +| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | +| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | +| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**6)` (= 64) | epochs | ~6.8 hours | +| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | ### State list lengths -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `EPOCHS_PER_HISTORICAL_VECTOR` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 years | -| `EPOCHS_PER_SLASHINGS_VECTOR` | `uint64(2**13)` (= 8,192) | epochs | ~36 days | -| `HISTORICAL_ROOTS_LIMIT` | `uint64(2**24)` (= 16,777,216) | historical roots | ~52,262 years | -| `VALIDATOR_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | validators | +| Name | Value | Unit | Duration | +| ------------------------------ | ------------------------------------- | :--------------: | :-----------: | +| `EPOCHS_PER_HISTORICAL_VECTOR` | `uint64(2**16)` (= 65,536) | epochs | ~0.8 years | +| `EPOCHS_PER_SLASHINGS_VECTOR` | `uint64(2**13)` (= 8,192) | epochs | ~36 days | +| `HISTORICAL_ROOTS_LIMIT` | `uint64(2**24)` (= 16,777,216) | historical roots | ~52,262 years | +| `VALIDATOR_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | validators | | ### Rewards and penalties -| Name | Value | -| - | - | -| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) | -| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | -| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | -| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) | -| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (= 128) | -| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` | - -- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak. - -- The `PROPORTIONAL_SLASHING_MULTIPLIER` is set to `1` at initial mainnet launch, resulting in one-third of the minimum accountable safety margin in the event of a finality attack. After Phase 0 mainnet stablizes, this value will be upgraded to `3` to provide the maximal minimum accountable safety margin. +| Name | Value | +| ---------------------------------- | ------------------------------ | +| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) | +| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | +| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | +| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) | +| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (= 128) | +| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` | + +- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where + `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it + takes the inactivity penalty to reduce the balance of non-participating + validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by + offline validators after `n` epochs is about + `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after + `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly + `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. + Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes + to provide a faster recovery in the event of an inactivity leak. + +- The `PROPORTIONAL_SLASHING_MULTIPLIER` is set to `1` at initial mainnet + launch, resulting in one-third of the minimum accountable safety margin in the + event of a finality attack. After Phase 0 mainnet stabilizes, this value will + be upgraded to `3` to provide the maximal minimum accountable safety margin. ### Max operations per block -| Name | Value | -| - | - | -| `MAX_PROPOSER_SLASHINGS` | `2**4` (= 16) | -| `MAX_ATTESTER_SLASHINGS` | `2**1` (= 2) | -| `MAX_ATTESTATIONS` | `2**7` (= 128) | -| `MAX_DEPOSITS` | `2**4` (= 16) | -| `MAX_VOLUNTARY_EXITS` | `2**4` (= 16) | +| Name | Value | +| ------------------------ | -------------- | +| `MAX_PROPOSER_SLASHINGS` | `2**4` (= 16) | +| `MAX_ATTESTER_SLASHINGS` | `2**1` (= 2) | +| `MAX_ATTESTATIONS` | `2**7` (= 128) | +| `MAX_DEPOSITS` | `2**4` (= 16) | +| `MAX_VOLUNTARY_EXITS` | `2**4` (= 16) | ## Configuration -*Note*: The default mainnet configuration values are included here for illustrative purposes. -Defaults for this more dynamic type of configuration are available with the presets in the [`configs`](../../configs) directory. -Testnets and other types of chain instances may use a different configuration. +*Note*: The default mainnet configuration values are included here for +illustrative purposes. Defaults for this more dynamic type of configuration are +available with the presets in the [`configs`](../../configs) directory. Testnets +and other types of chain instances may use a different configuration. ### Genesis settings -| Name | Value | -| - | - | -| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) | -| `MIN_GENESIS_TIME` | `uint64(1606824000)` (Dec 1, 2020, 12pm UTC) | -| `GENESIS_FORK_VERSION` | `Version('0x00000000')` | -| `GENESIS_DELAY` | `uint64(604800)` (7 days) | +| Name | Value | +| ------------------------------------ | -------------------------------------------- | +| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) | +| `MIN_GENESIS_TIME` | `uint64(1606824000)` (Dec 1, 2020, 12pm UTC) | +| `GENESIS_FORK_VERSION` | `Version('0x00000000')` | +| `GENESIS_DELAY` | `uint64(604800)` (7 days) | ### Time parameters -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | -| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds | -| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | -| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | -| `ETH1_FOLLOW_DISTANCE` | `uint64(2**11)` (= 2,048) | Eth1 blocks | ~8 hours | +| Name | Value | Unit | Duration | +| ------------------------------------- | ------------------------- | :---------: | :--------: | +| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | +| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds | +| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | +| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | +| `ETH1_FOLLOW_DISTANCE` | `uint64(2**11)` (= 2,048) | Eth1 blocks | ~8 hours | ### Validator cycle -| Name | Value | -| - | - | -| `EJECTION_BALANCE` | `Gwei(2**4 * 10**9)` (= 16,000,000,000) | -| `MIN_PER_EPOCH_CHURN_LIMIT` | `uint64(2**2)` (= 4) | -| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) | +| Name | Value | +| --------------------------- | --------------------------------------- | +| `EJECTION_BALANCE` | `Gwei(2**4 * 10**9)` (= 16,000,000,000) | +| `MIN_PER_EPOCH_CHURN_LIMIT` | `uint64(2**2)` (= 4) | +| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) | ## Containers -The following types are [SimpleSerialize (SSZ)](../../ssz/simple-serialize.md) containers. +The following types are [SimpleSerialize (SSZ)](../../ssz/simple-serialize.md) +containers. -*Note*: The definitions are ordered topologically to facilitate execution of the spec. +*Note*: The definitions are ordered topologically to facilitate execution of the +spec. *Note*: Fields missing in container instantiations default to their zero value. @@ -328,7 +367,7 @@ The following types are [SimpleSerialize (SSZ)](../../ssz/simple-serialize.md) c class Fork(Container): previous_version: Version current_version: Version - epoch: Epoch # Epoch of latest fork + epoch: Epoch ``` #### `ForkData` @@ -352,14 +391,13 @@ class Checkpoint(Container): ```python class Validator(Container): pubkey: BLSPubkey - withdrawal_credentials: Bytes32 # Commitment to pubkey for withdrawals - effective_balance: Gwei # Balance at stake + withdrawal_credentials: Bytes32 + effective_balance: Gwei slashed: boolean - # Status epochs - activation_eligibility_epoch: Epoch # When criteria for activation were met + activation_eligibility_epoch: Epoch activation_epoch: Epoch exit_epoch: Epoch - withdrawable_epoch: Epoch # When validator can withdraw funds + withdrawable_epoch: Epoch ``` #### `AttestationData` @@ -368,9 +406,7 @@ class Validator(Container): class AttestationData(Container): slot: Slot index: CommitteeIndex - # LMD GHOST vote beacon_block_root: Root - # FFG vote source: Checkpoint target: Checkpoint ``` @@ -422,12 +458,14 @@ class DepositMessage(Container): #### `DepositData` +*Note*: `signature` is over `DepositMessage`. + ```python class DepositData(Container): pubkey: BLSPubkey withdrawal_credentials: Bytes32 amount: Gwei - signature: BLSSignature # Signing over DepositMessage + signature: BLSSignature ``` #### `BeaconBlockHeader` @@ -478,9 +516,11 @@ class Attestation(Container): #### `Deposit` +*Note*: `proof` is the Merkle path to the deposit root. + ```python class Deposit(Container): - proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] # Merkle path to deposit root + proof: Vector[Bytes32, DEPOSIT_CONTRACT_TREE_DEPTH + 1] data: DepositData ``` @@ -488,7 +528,7 @@ class Deposit(Container): ```python class VoluntaryExit(Container): - epoch: Epoch # Earliest epoch when voluntary exit can be processed + epoch: Epoch validator_index: ValidatorIndex ``` @@ -499,9 +539,8 @@ class VoluntaryExit(Container): ```python class BeaconBlockBody(Container): randao_reveal: BLSSignature - eth1_data: Eth1Data # Eth1 data vote - graffiti: Bytes32 # Arbitrary data - # Operations + eth1_data: Eth1Data + graffiti: Bytes32 proposer_slashings: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS] attester_slashings: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS] attestations: List[Attestation, MAX_ATTESTATIONS] @@ -526,33 +565,25 @@ class BeaconBlock(Container): ```python class BeaconState(Container): - # Versioning genesis_time: uint64 genesis_validators_root: Root slot: Slot fork: Fork - # History latest_block_header: BeaconBlockHeader block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] - # Eth1 eth1_data: Eth1Data eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] eth1_deposit_index: uint64 - # Registry validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] - # Randomness randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] - # Slashings - slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances - # Attestations + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH] - # Finality - justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch - previous_justified_checkpoint: Checkpoint # Previous epoch snapshot + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint current_justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint ``` @@ -585,7 +616,8 @@ class SignedBeaconBlockHeader(Container): ## Helper functions -*Note*: The definitions below are for specification purposes and are not necessarily optimal implementations. +*Note*: The definitions below are for specification purposes and are not +necessarily optimal implementations. ### Math @@ -596,6 +628,8 @@ def integer_squareroot(n: uint64) -> uint64: """ Return the largest integer ``x`` such that ``x**2 <= n``. """ + if n == UINT64_MAX: + return UINT64_MAX_SQRT x = n y = (x + 1) // 2 while y < x: @@ -616,7 +650,9 @@ def xor(bytes_1: Bytes32, bytes_2: Bytes32) -> Bytes32: #### `uint_to_bytes` -`def uint_to_bytes(n: uint) -> bytes` is a function for serializing the `uint` type object to bytes in ``ENDIANNESS``-endian. The expected length of the output is the byte-length of the `uint` type. +`def uint_to_bytes(n: uint) -> bytes` is a function for serializing the `uint` +type object to bytes in `ENDIANNESS`-endian. The expected length of the output +is the byte-length of the `uint` type. #### `bytes_to_uint64` @@ -628,6 +664,16 @@ def bytes_to_uint64(data: bytes) -> uint64: return uint64(int.from_bytes(data, ENDIANNESS)) ``` +#### `saturating_sub` + +```python +def saturating_sub(a: int, b: int) -> int: + """ + Computes a - b, saturating at numeric bounds. + """ + return a - b if a > b else 0 +``` + ### Crypto #### `hash` @@ -636,11 +682,16 @@ def bytes_to_uint64(data: bytes) -> uint64: #### `hash_tree_root` -`def hash_tree_root(object: SSZSerializable) -> Root` is a function for hashing objects into a single root by utilizing a hash tree structure, as defined in the [SSZ spec](../../ssz/simple-serialize.md#merkleization). +`def hash_tree_root(object: SSZSerializable) -> Root` is a function for hashing +objects into a single root by utilizing a hash tree structure, as defined in the +[SSZ spec](../../ssz/simple-serialize.md#merkleization). #### BLS signatures -The [IETF BLS signature draft standard v4](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04) with ciphersuite `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` defines the following functions: +The +[IETF BLS signature draft standard v4](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04) +with ciphersuite `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` defines the +following functions: - `def Sign(privkey: int, message: Bytes) -> BLSSignature` - `def Verify(pubkey: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool` @@ -698,7 +749,9 @@ def is_slashable_validator(validator: Validator, epoch: Epoch) -> bool: """ Check if ``validator`` is slashable. """ - return (not validator.slashed) and (validator.activation_epoch <= epoch < validator.withdrawable_epoch) + return (not validator.slashed) and ( + validator.activation_epoch <= epoch < validator.withdrawable_epoch + ) ``` #### `is_slashable_attestation_data` @@ -710,7 +763,8 @@ def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationDa """ return ( # Double vote - (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) or + (data_1 != data_2 and data_1.target.epoch == data_2.target.epoch) + or # Surround vote (data_1.source.epoch < data_2.source.epoch and data_2.target.epoch < data_1.target.epoch) ) @@ -719,7 +773,9 @@ def is_slashable_attestation_data(data_1: AttestationData, data_2: AttestationDa #### `is_valid_indexed_attestation` ```python -def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: IndexedAttestation) -> bool: +def is_valid_indexed_attestation( + state: BeaconState, indexed_attestation: IndexedAttestation +) -> bool: """ Check if ``indexed_attestation`` is not empty, has sorted and unique indices and has a valid aggregate signature. """ @@ -737,7 +793,9 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe #### `is_valid_merkle_branch` ```python -def is_valid_merkle_branch(leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root) -> bool: +def is_valid_merkle_branch( + leaf: Bytes32, branch: Sequence[Bytes32], depth: uint64, index: uint64, root: Root +) -> bool: """ Check if ``leaf`` at ``index`` verifies against the Merkle ``root`` and ``branch``. """ @@ -768,9 +826,7 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> flip = (pivot + index_count - index) % index_count position = max(index, flip) source = hash( - seed - + uint_to_bytes(uint8(current_round)) - + uint_to_bytes(uint32(position // 256)) + seed + uint_to_bytes(uint8(current_round)) + uint_to_bytes(uint32(position // 256)) ) byte = uint8(source[(position % 256) // 8]) bit = (byte >> (position % 8)) % 2 @@ -782,7 +838,9 @@ def compute_shuffled_index(index: uint64, index_count: uint64, seed: Bytes32) -> #### `compute_proposer_index` ```python -def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32) -> ValidatorIndex: +def compute_proposer_index( + state: BeaconState, indices: Sequence[ValidatorIndex], seed: Bytes32 +) -> ValidatorIndex: """ Return from ``indices`` a random index sampled by effective balance. """ @@ -802,16 +860,28 @@ def compute_proposer_index(state: BeaconState, indices: Sequence[ValidatorIndex] #### `compute_committee` ```python -def compute_committee(indices: Sequence[ValidatorIndex], - seed: Bytes32, - index: uint64, - count: uint64) -> Sequence[ValidatorIndex]: +def compute_committee( + indices: Sequence[ValidatorIndex], seed: Bytes32, index: uint64, count: uint64 +) -> Sequence[ValidatorIndex]: """ Return the committee corresponding to ``indices``, ``seed``, ``index``, and committee ``count``. """ start = (len(indices) * index) // count end = (len(indices) * uint64(index + 1)) // count - return [indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] for i in range(start, end)] + return [ + indices[compute_shuffled_index(uint64(i), uint64(len(indices)), seed)] + for i in range(start, end) + ] +``` + +#### `compute_time_at_slot` + +*Note*: This function is unsafe with respect to overflows and underflows. + +```python +def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: + slots_since_genesis = slot - GENESIS_SLOT + return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) ``` #### `compute_epoch_at_slot` @@ -852,10 +922,12 @@ def compute_fork_data_root(current_version: Version, genesis_validators_root: Ro Return the 32-byte fork data root for the ``current_version`` and ``genesis_validators_root``. This is used primarily in signature domains to avoid collisions across forks/chains. """ - return hash_tree_root(ForkData( - current_version=current_version, - genesis_validators_root=genesis_validators_root, - )) + return hash_tree_root( + ForkData( + current_version=current_version, + genesis_validators_root=genesis_validators_root, + ) + ) ``` #### `compute_fork_digest` @@ -873,7 +945,9 @@ def compute_fork_digest(current_version: Version, genesis_validators_root: Root) #### `compute_domain` ```python -def compute_domain(domain_type: DomainType, fork_version: Version=None, genesis_validators_root: Root=None) -> Domain: +def compute_domain( + domain_type: DomainType, fork_version: Version = None, genesis_validators_root: Root = None +) -> Domain: """ Return the domain for the ``domain_type`` and ``fork_version``. """ @@ -892,10 +966,12 @@ def compute_signing_root(ssz_object: SSZObject, domain: Domain) -> Root: """ Return the signing root for the corresponding signing data. """ - return hash_tree_root(SigningData( - object_root=hash_tree_root(ssz_object), - domain=domain, - )) + return hash_tree_root( + SigningData( + object_root=hash_tree_root(ssz_object), + domain=domain, + ) + ) ``` ### Beacon state accessors @@ -959,7 +1035,9 @@ def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[V """ Return the sequence of active validator indices at ``epoch``. """ - return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)] + return [ + ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch) + ] ``` #### `get_validator_churn_limit` @@ -970,7 +1048,9 @@ def get_validator_churn_limit(state: BeaconState) -> uint64: Return the validator churn limit for the current epoch. """ active_validator_indices = get_active_validator_indices(state, get_current_epoch(state)) - return max(MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT) + return max( + MIN_PER_EPOCH_CHURN_LIMIT, uint64(len(active_validator_indices)) // CHURN_LIMIT_QUOTIENT + ) ``` #### `get_seed` @@ -980,7 +1060,9 @@ def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes """ Return the seed at ``epoch``. """ - mix = get_randao_mix(state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1)) # Avoid underflow + mix = get_randao_mix( + state, Epoch(epoch + EPOCHS_PER_HISTORICAL_VECTOR - MIN_SEED_LOOKAHEAD - 1) + ) # Avoid underflow return hash(domain_type + uint_to_bytes(epoch) + mix) ``` @@ -991,16 +1073,23 @@ def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: """ Return the number of committees in each slot for the given ``epoch``. """ - return max(uint64(1), min( - MAX_COMMITTEES_PER_SLOT, - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) + return max( + uint64(1), + min( + MAX_COMMITTEES_PER_SLOT, + uint64(len(get_active_validator_indices(state, epoch))) + // SLOTS_PER_EPOCH + // TARGET_COMMITTEE_SIZE, + ), + ) ``` #### `get_beacon_committee` ```python -def get_beacon_committee(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Sequence[ValidatorIndex]: +def get_beacon_committee( + state: BeaconState, slot: Slot, index: CommitteeIndex +) -> Sequence[ValidatorIndex]: """ Return the beacon committee at ``slot`` for ``index``. """ @@ -1034,9 +1123,14 @@ def get_total_balance(state: BeaconState, indices: Set[ValidatorIndex]) -> Gwei: """ Return the combined effective balance of the ``indices``. ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. - Math safe up to ~10B ETH, afterwhich this overflows uint64. + Math safe up to ~10B ETH, after which this overflows uint64. """ - return Gwei(max(EFFECTIVE_BALANCE_INCREMENT, sum([state.validators[index].effective_balance for index in indices]))) + return Gwei( + max( + EFFECTIVE_BALANCE_INCREMENT, + sum([state.validators[index].effective_balance for index in indices]), + ) + ) ``` #### `get_total_active_balance` @@ -1047,18 +1141,22 @@ def get_total_active_balance(state: BeaconState) -> Gwei: Return the combined effective balance of the active validators. Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero. """ - return get_total_balance(state, set(get_active_validator_indices(state, get_current_epoch(state)))) + return get_total_balance( + state, set(get_active_validator_indices(state, get_current_epoch(state))) + ) ``` #### `get_domain` ```python -def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch=None) -> Domain: +def get_domain(state: BeaconState, domain_type: DomainType, epoch: Epoch = None) -> Domain: """ Return the signature domain (fork version concatenated with domain type) of a message. """ epoch = get_current_epoch(state) if epoch is None else epoch - fork_version = state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + fork_version = ( + state.fork.previous_version if epoch < state.fork.epoch else state.fork.current_version + ) return compute_domain(domain_type, fork_version, state.genesis_validators_root) ``` @@ -1069,7 +1167,7 @@ def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> Ind """ Return the indexed attestation corresponding to ``attestation``. """ - attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bits) + attesting_indices = get_attesting_indices(state, attestation) return IndexedAttestation( attesting_indices=sorted(attesting_indices), @@ -1081,14 +1179,12 @@ def get_indexed_attestation(state: BeaconState, attestation: Attestation) -> Ind #### `get_attesting_indices` ```python -def get_attesting_indices(state: BeaconState, - data: AttestationData, - bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]) -> Set[ValidatorIndex]: +def get_attesting_indices(state: BeaconState, attestation: Attestation) -> Set[ValidatorIndex]: """ Return the set of attesting indices corresponding to ``data`` and ``bits``. """ - committee = get_beacon_committee(state, data.slot, data.index) - return set(index for i, index in enumerate(committee) if bits[i]) + committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) + return set(index for i, index in enumerate(committee) if attestation.aggregation_bits[i]) ``` ### Beacon state mutators @@ -1140,9 +1236,9 @@ def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None: #### `slash_validator` ```python -def slash_validator(state: BeaconState, - slashed_index: ValidatorIndex, - whistleblower_index: ValidatorIndex=None) -> None: +def slash_validator( + state: BeaconState, slashed_index: ValidatorIndex, whistleblower_index: ValidatorIndex = None +) -> None: """ Slash the validator with index ``slashed_index``. """ @@ -1150,9 +1246,13 @@ def slash_validator(state: BeaconState, initiate_validator_exit(state, slashed_index) validator = state.validators[slashed_index] validator.slashed = True - validator.withdrawable_epoch = max(validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR)) + validator.withdrawable_epoch = max( + validator.withdrawable_epoch, Epoch(epoch + EPOCHS_PER_SLASHINGS_VECTOR) + ) state.slashings[epoch % EPOCHS_PER_SLASHINGS_VECTOR] += validator.effective_balance - decrease_balance(state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT) + decrease_balance( + state, slashed_index, validator.effective_balance // MIN_SLASHING_PENALTY_QUOTIENT + ) # Apply proposer and whistleblower rewards proposer_index = get_beacon_proposer_index(state) @@ -1166,18 +1266,28 @@ def slash_validator(state: BeaconState, ## Genesis -Before the Ethereum beacon chain genesis has been triggered, and for every Ethereum proof-of-work block, let `candidate_state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` where: +Before the Ethereum beacon chain genesis has been triggered, and for every +Ethereum proof-of-work block, let +`candidate_state = initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)` +where: - `eth1_block_hash` is the hash of the Ethereum proof-of-work block - `eth1_timestamp` is the Unix timestamp corresponding to `eth1_block_hash` -- `deposits` is the sequence of all deposits, ordered chronologically, up to (and including) the block with hash `eth1_block_hash` +- `deposits` is the sequence of all deposits, ordered chronologically, up to + (and including) the block with hash `eth1_block_hash` -Proof-of-work blocks must only be considered once they are at least `SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. `eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). Due to this constraint, if `GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the `genesis_time` can happen before the time/state is first known. Values should be configured to avoid this case. +Proof-of-work blocks must only be considered once they are at least +`SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE` seconds old (i.e. +`eth1_timestamp + SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE <= current_unix_time`). +Due to this constraint, if +`GENESIS_DELAY < SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE`, then the +`genesis_time` can happen before the time/state is first known. Values should be +configured to avoid this case. ```python -def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, - eth1_timestamp: uint64, - deposits: Sequence[Deposit]) -> BeaconState: +def initialize_beacon_state_from_eth1( + eth1_block_hash: Hash32, eth1_timestamp: uint64, deposits: Sequence[Deposit] +) -> BeaconState: fork = Fork( previous_version=GENESIS_FORK_VERSION, current_version=GENESIS_FORK_VERSION, @@ -1188,20 +1298,23 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, 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 + 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]) + 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) + 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 @@ -1212,11 +1325,13 @@ def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, return state ``` -*Note*: The ETH1 block with `eth1_timestamp` meeting the minimum genesis active validator count criteria can also occur before `MIN_GENESIS_TIME`. +*Note*: The ETH1 block with `eth1_timestamp` meeting the minimum genesis active +validator count criteria can also occur before `MIN_GENESIS_TIME`. ### Genesis state -Let `genesis_state = candidate_state` whenever `is_valid_genesis_state(candidate_state) is True` for the first time. +Let `genesis_state = candidate_state` whenever +`is_valid_genesis_state(candidate_state) is True` for the first time. ```python def is_valid_genesis_state(state: BeaconState) -> bool: @@ -1233,10 +1348,16 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. ## Beacon chain state transition function -The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. +The post-state corresponding to a pre-state `state` and a signed block +`signed_block` is defined as `state_transition(state, signed_block)`. State +transitions that trigger an unhandled exception (e.g. a failed `assert` or an +out-of-range list access) are considered invalid. State transitions that cause a +`uint64` overflow or underflow are also considered invalid. ```python -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: +def state_transition( + state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool = True +) -> None: block = signed_block.message # Process slots (including those with no blocks) since block process_slots(state, block.slot) @@ -1253,7 +1374,9 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida ```python def verify_block_signature(state: BeaconState, signed_block: SignedBeaconBlock) -> bool: proposer = state.validators[signed_block.message.proposer_index] - signing_root = compute_signing_root(signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER)) + signing_root = compute_signing_root( + signed_block.message, get_domain(state, DOMAIN_BEACON_PROPOSER) + ) return bls.Verify(proposer.pubkey, signing_root, signed_block.signature) ``` @@ -1300,33 +1423,46 @@ def process_epoch(state: BeaconState) -> None: #### Helper functions ```python -def get_matching_source_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: +def get_matching_source_attestations( + state: BeaconState, epoch: Epoch +) -> Sequence[PendingAttestation]: assert epoch in (get_previous_epoch(state), get_current_epoch(state)) - return state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations + return ( + state.current_epoch_attestations + if epoch == get_current_epoch(state) + else state.previous_epoch_attestations + ) ``` ```python -def get_matching_target_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: +def get_matching_target_attestations( + state: BeaconState, epoch: Epoch +) -> Sequence[PendingAttestation]: return [ - a for a in get_matching_source_attestations(state, epoch) + a + for a in get_matching_source_attestations(state, epoch) if a.data.target.root == get_block_root(state, epoch) ] ``` ```python -def get_matching_head_attestations(state: BeaconState, epoch: Epoch) -> Sequence[PendingAttestation]: +def get_matching_head_attestations( + state: BeaconState, epoch: Epoch +) -> Sequence[PendingAttestation]: return [ - a for a in get_matching_target_attestations(state, epoch) + a + for a in get_matching_target_attestations(state, epoch) if a.data.beacon_block_root == get_block_root_at_slot(state, a.data.slot) ] ``` ```python -def get_unslashed_attesting_indices(state: BeaconState, - attestations: Sequence[PendingAttestation]) -> Set[ValidatorIndex]: - output = set() # type: Set[ValidatorIndex] +def get_unslashed_attesting_indices( + state: BeaconState, attestations: Sequence[PendingAttestation] +) -> Set[ValidatorIndex]: + output: Set[ValidatorIndex] = set() for a in attestations: - output = output.union(get_attesting_indices(state, a.data, a.aggregation_bits)) + output = output.union(get_attesting_indices(state, a)) return set(filter(lambda index: not state.validators[index].slashed, output)) ``` @@ -1352,14 +1488,18 @@ def process_justification_and_finalization(state: BeaconState) -> None: total_active_balance = get_total_active_balance(state) previous_target_balance = get_attesting_balance(state, previous_attestations) current_target_balance = get_attesting_balance(state, current_attestations) - weigh_justification_and_finalization(state, total_active_balance, previous_target_balance, current_target_balance) + weigh_justification_and_finalization( + state, total_active_balance, previous_target_balance, current_target_balance + ) ``` ```python -def weigh_justification_and_finalization(state: BeaconState, - total_active_balance: Gwei, - previous_epoch_target_balance: Gwei, - current_epoch_target_balance: Gwei) -> None: +def weigh_justification_and_finalization( + state: BeaconState, + total_active_balance: Gwei, + previous_epoch_target_balance: Gwei, + current_epoch_target_balance: Gwei, +) -> None: previous_epoch = get_previous_epoch(state) current_epoch = get_current_epoch(state) old_previous_justified_checkpoint = state.previous_justified_checkpoint @@ -1367,15 +1507,17 @@ def weigh_justification_and_finalization(state: BeaconState, # Process justifications state.previous_justified_checkpoint = state.current_justified_checkpoint - state.justification_bits[1:] = state.justification_bits[:JUSTIFICATION_BITS_LENGTH - 1] + state.justification_bits[1:] = state.justification_bits[: JUSTIFICATION_BITS_LENGTH - 1] state.justification_bits[0] = 0b0 if previous_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=previous_epoch, - root=get_block_root(state, previous_epoch)) + state.current_justified_checkpoint = Checkpoint( + epoch=previous_epoch, root=get_block_root(state, previous_epoch) + ) state.justification_bits[1] = 0b1 if current_epoch_target_balance * 3 >= total_active_balance * 2: - state.current_justified_checkpoint = Checkpoint(epoch=current_epoch, - root=get_block_root(state, current_epoch)) + state.current_justified_checkpoint = Checkpoint( + epoch=current_epoch, root=get_block_root(state, current_epoch) + ) state.justification_bits[0] = 0b1 # Process finalizations @@ -1402,41 +1544,44 @@ def weigh_justification_and_finalization(state: BeaconState, def get_base_reward(state: BeaconState, index: ValidatorIndex) -> Gwei: total_balance = get_total_active_balance(state) effective_balance = state.validators[index].effective_balance - return Gwei(effective_balance * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH) + return Gwei( + effective_balance + * BASE_REWARD_FACTOR + // integer_squareroot(total_balance) + // BASE_REWARDS_PER_EPOCH + ) ``` - ```python def get_proposer_reward(state: BeaconState, attesting_index: ValidatorIndex) -> Gwei: return Gwei(get_base_reward(state, attesting_index) // PROPOSER_REWARD_QUOTIENT) ``` - ```python def get_finality_delay(state: BeaconState) -> uint64: return get_previous_epoch(state) - state.finalized_checkpoint.epoch ``` - ```python def is_in_inactivity_leak(state: BeaconState) -> bool: return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY ``` - ```python def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]: previous_epoch = get_previous_epoch(state) return [ - ValidatorIndex(index) for index, v in enumerate(state.validators) - if is_active_validator(v, previous_epoch) or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) + ValidatorIndex(index) + for index, v in enumerate(state.validators) + if is_active_validator(v, previous_epoch) + or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch) ] ``` ```python -def get_attestation_component_deltas(state: BeaconState, - attestations: Sequence[PendingAttestation] - ) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: +def get_attestation_component_deltas( + state: BeaconState, attestations: Sequence[PendingAttestation] +) -> Tuple[Sequence[Gwei], Sequence[Gwei]]: """ Helper with shared logic for use by get source, target, and head deltas functions """ @@ -1467,7 +1612,9 @@ def get_source_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei """ Return attester micro-rewards/penalties for source-vote for each validator. """ - matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_source_attestations = get_matching_source_attestations( + state, get_previous_epoch(state) + ) return get_attestation_component_deltas(state, matching_source_attestations) ``` @@ -1476,7 +1623,9 @@ def get_target_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequence[Gwei """ Return attester micro-rewards/penalties for target-vote for each validator. """ - matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) + matching_target_attestations = get_matching_target_attestations( + state, get_previous_epoch(state) + ) return get_attestation_component_deltas(state, matching_target_attestations) ``` @@ -1495,14 +1644,18 @@ def get_inclusion_delay_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], Sequ Return proposer and inclusion delay micro-rewards/penalties for each validator. """ rewards = [Gwei(0) for _ in range(len(state.validators))] - matching_source_attestations = get_matching_source_attestations(state, get_previous_epoch(state)) + matching_source_attestations = get_matching_source_attestations( + state, get_previous_epoch(state) + ) for index in get_unslashed_attesting_indices(state, matching_source_attestations): - attestation = min([ - a for a in matching_source_attestations - if index in get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) + attestation = min( + [a for a in matching_source_attestations if index in get_attesting_indices(state, a)], + key=lambda a: a.inclusion_delay, + ) rewards[attestation.proposer_index] += get_proposer_reward(state, index) - max_attester_reward = Gwei(get_base_reward(state, index) - get_proposer_reward(state, index)) + max_attester_reward = Gwei( + get_base_reward(state, index) - get_proposer_reward(state, index) + ) rewards[index] += Gwei(max_attester_reward // attestation.inclusion_delay) # No penalties associated with inclusion delay @@ -1517,15 +1670,23 @@ def get_inactivity_penalty_deltas(state: BeaconState) -> Tuple[Sequence[Gwei], S """ penalties = [Gwei(0) for _ in range(len(state.validators))] if is_in_inactivity_leak(state): - matching_target_attestations = get_matching_target_attestations(state, get_previous_epoch(state)) - matching_target_attesting_indices = get_unslashed_attesting_indices(state, matching_target_attestations) + matching_target_attestations = get_matching_target_attestations( + state, get_previous_epoch(state) + ) + matching_target_attesting_indices = get_unslashed_attesting_indices( + state, matching_target_attestations + ) for index in get_eligible_validator_indices(state): # If validator is performing optimally this cancels all rewards for a neutral balance base_reward = get_base_reward(state, index) - penalties[index] += Gwei(BASE_REWARDS_PER_EPOCH * base_reward - get_proposer_reward(state, index)) + penalties[index] += Gwei( + BASE_REWARDS_PER_EPOCH * base_reward - get_proposer_reward(state, index) + ) if index not in matching_target_attesting_indices: effective_balance = state.validators[index].effective_balance - penalties[index] += Gwei(effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT) + penalties[index] += Gwei( + effective_balance * get_finality_delay(state) // INACTIVITY_PENALTY_QUOTIENT + ) # No rewards associated with inactivity penalties rewards = [Gwei(0) for _ in range(len(state.validators))] @@ -1588,13 +1749,17 @@ def process_registry_updates(state: BeaconState) -> None: initiate_validator_exit(state, ValidatorIndex(index)) # Queue validators eligible for activation and not yet dequeued for activation - activation_queue = sorted([ - index for index, validator in enumerate(state.validators) - if is_eligible_for_activation(state, validator) + activation_queue = sorted( + [ + index + for index, validator in enumerate(state.validators) + if is_eligible_for_activation(state, validator) + ], # Order by the sequence of activation_eligibility_epoch setting and then index - ], key=lambda index: (state.validators[index].activation_eligibility_epoch, index)) + key=lambda index: (state.validators[index].activation_eligibility_epoch, index), + ) # Dequeued validators for activation up to churn limit - for index in activation_queue[:get_validator_churn_limit(state)]: + for index in activation_queue[: get_validator_churn_limit(state)]: validator = state.validators[index] validator.activation_epoch = compute_activation_exit_epoch(get_current_epoch(state)) ``` @@ -1605,16 +1770,24 @@ def process_registry_updates(state: BeaconState) -> None: def process_slashings(state: BeaconState) -> None: epoch = get_current_epoch(state) total_balance = get_total_active_balance(state) - adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance) + adjusted_total_slashing_balance = min( + sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance + ) for index, validator in enumerate(state.validators): - if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch: + if ( + validator.slashed + and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch + ): increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow - penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance + penalty_numerator = ( + validator.effective_balance // increment * adjusted_total_slashing_balance + ) penalty = penalty_numerator // total_balance * increment decrease_balance(state, ValidatorIndex(index), penalty) ``` #### Eth1 data votes updates + ```python def process_eth1_data_reset(state: BeaconState) -> None: next_epoch = Epoch(get_current_epoch(state) + 1) @@ -1637,7 +1810,9 @@ def process_effective_balance_updates(state: BeaconState) -> None: balance + DOWNWARD_THRESHOLD < validator.effective_balance or validator.effective_balance + UPWARD_THRESHOLD < balance ): - validator.effective_balance = min(balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) + validator.effective_balance = min( + balance - balance % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE + ) ``` #### Slashings balances updates @@ -1656,16 +1831,21 @@ def process_randao_mixes_reset(state: BeaconState) -> None: current_epoch = get_current_epoch(state) next_epoch = Epoch(current_epoch + 1) # Set randao mix - state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix(state, current_epoch) + state.randao_mixes[next_epoch % EPOCHS_PER_HISTORICAL_VECTOR] = get_randao_mix( + state, current_epoch + ) ``` #### Historical roots updates + ```python def process_historical_roots_update(state: BeaconState) -> None: # Set historical root accumulator next_epoch = Epoch(get_current_epoch(state) + 1) if next_epoch % (SLOTS_PER_HISTORICAL_ROOT // SLOTS_PER_EPOCH) == 0: - historical_batch = HistoricalBatch(block_roots=state.block_roots, state_roots=state.state_roots) + historical_batch = HistoricalBatch( + block_roots=state.block_roots, state_roots=state.state_roots + ) state.historical_roots.append(hash_tree_root(historical_batch)) ``` @@ -1733,7 +1913,10 @@ def process_randao(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: state.eth1_data_votes.append(body.eth1_data) - if state.eth1_data_votes.count(body.eth1_data) * 2 > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH: + if ( + state.eth1_data_votes.count(body.eth1_data) * 2 + > EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH + ): state.eth1_data = body.eth1_data ``` @@ -1742,7 +1925,9 @@ def process_eth1_data(state: BeaconState, body: BeaconBlockBody) -> None: ```python def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) + assert len(body.deposits) == min( + MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index + ) def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: for operation in operations: @@ -1773,7 +1958,9 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla assert is_slashable_validator(proposer, get_current_epoch(state)) # Verify signatures for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2): - domain = get_domain(state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot)) + domain = get_domain( + state, DOMAIN_BEACON_PROPOSER, compute_epoch_at_slot(signed_header.message.slot) + ) signing_root = compute_signing_root(signed_header.message, domain) assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature) @@ -1833,28 +2020,66 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None: ##### Deposits ```python -def get_validator_from_deposit(state: BeaconState, deposit: Deposit) -> Validator: - amount = deposit.data.amount +def get_validator_from_deposit( + pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> Validator: effective_balance = min(amount - amount % EFFECTIVE_BALANCE_INCREMENT, MAX_EFFECTIVE_BALANCE) return Validator( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + effective_balance=effective_balance, + slashed=False, activation_eligibility_epoch=FAR_FUTURE_EPOCH, activation_epoch=FAR_FUTURE_EPOCH, exit_epoch=FAR_FUTURE_EPOCH, withdrawable_epoch=FAR_FUTURE_EPOCH, - effective_balance=effective_balance, ) ``` +```python +def add_validator_to_registry( + state: BeaconState, pubkey: BLSPubkey, withdrawal_credentials: Bytes32, amount: uint64 +) -> None: + state.validators.append(get_validator_from_deposit(pubkey, withdrawal_credentials, amount)) + state.balances.append(amount) +``` + +```python +def apply_deposit( + state: BeaconState, + pubkey: BLSPubkey, + withdrawal_credentials: Bytes32, + amount: uint64, + signature: BLSSignature, +) -> None: + validator_pubkeys = [v.pubkey for v in state.validators] + if pubkey not in validator_pubkeys: + # Verify the deposit signature (proof of possession) which is not checked by the deposit contract + deposit_message = DepositMessage( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + ) + # Fork-agnostic domain since deposits are valid across forks + domain = compute_domain(DOMAIN_DEPOSIT) + signing_root = compute_signing_root(deposit_message, domain) + if bls.Verify(pubkey, signing_root, signature): + add_validator_to_registry(state, pubkey, withdrawal_credentials, amount) + else: + # Increase balance by deposit amount + index = ValidatorIndex(validator_pubkeys.index(pubkey)) + increase_balance(state, index, amount) +``` + ```python def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Verify the Merkle branch assert is_valid_merkle_branch( leaf=hash_tree_root(deposit.data), branch=deposit.proof, - depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in + # Add 1 for the List length mix-in + depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, index=state.eth1_deposit_index, root=state.eth1_data.deposit_root, ) @@ -1862,28 +2087,13 @@ def process_deposit(state: BeaconState, deposit: Deposit) -> None: # Deposits must be processed in order state.eth1_deposit_index += 1 - pubkey = deposit.data.pubkey - amount = deposit.data.amount - validator_pubkeys = [v.pubkey for v in state.validators] - if pubkey not in validator_pubkeys: - # Verify the deposit signature (proof of possession) which is not checked by the deposit contract - deposit_message = DepositMessage( - pubkey=deposit.data.pubkey, - withdrawal_credentials=deposit.data.withdrawal_credentials, - amount=deposit.data.amount, - ) - domain = compute_domain(DOMAIN_DEPOSIT) # Fork-agnostic domain since deposits are valid across forks - signing_root = compute_signing_root(deposit_message, domain) - if not bls.Verify(pubkey, signing_root, deposit.data.signature): - return - - # Add validator and balance entries - state.validators.append(get_validator_from_deposit(state, deposit)) - state.balances.append(amount) - else: - # Increase balance by deposit amount - index = ValidatorIndex(validator_pubkeys.index(pubkey)) - increase_balance(state, index, amount) + apply_deposit( + state=state, + pubkey=deposit.data.pubkey, + withdrawal_credentials=deposit.data.withdrawal_credentials, + amount=deposit.data.amount, + signature=deposit.data.signature, + ) ``` ##### Voluntary exits diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 51786129c0..c6fe54ed98 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -1,9 +1,6 @@ # Phase 0 -- Deposit Contract -## Table of contents - - - + - [Introduction](#introduction) - [Constants](#constants) @@ -15,63 +12,94 @@ - [`DepositEvent` log](#depositevent-log) - [Solidity code](#solidity-code) - - + ## Introduction -This document represents the specification for the beacon chain deposit contract, part of Phase 0. +This document represents the specification for the beacon chain deposit +contract, part of Phase 0. ## Constants -The following values are (non-configurable) constants used throughout the specification. +The following values are (non-configurable) constants used throughout the +specification. -| Name | Value | -| - | - | +| Name | Value | +| ----------------------------- | ------------- | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | ## Configuration -*Note*: The default mainnet configuration values are included here for spec-design purposes. -The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory. -These configurations are updated for releases and may be out of sync during `dev` changes. +*Note*: The default mainnet configuration values are included here for +spec-design purposes. The different configurations for mainnet, testnets, and +YAML-based testing can be found in the +[`configs/constant_presets`](../../configs) directory. These configurations are +updated for releases and may be out of sync during `dev` changes. -| Name | Value | -| - | - | -| `DEPOSIT_CHAIN_ID` | `1` | -| `DEPOSIT_NETWORK_ID` | `1` | +| Name | Value | +| -------------------------- | -------------------------------------------- | +| `DEPOSIT_CHAIN_ID` | `1` | +| `DEPOSIT_NETWORK_ID` | `1` | | `DEPOSIT_CONTRACT_ADDRESS` | `0x00000000219ab540356cBB839Cbe05303d7705Fa` | ## Staking deposit contract -The initial deployment phases of Ethereum proof-of-stake are implemented without consensus changes to the existing Ethereum proof-of-work chain. A deposit contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum proof-of-work chain defined by the [chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon chain. Validator balances will be withdrawable to the execution-layer in a followup fork after the Merge. +The initial deployment phases of Ethereum proof-of-stake are implemented without +consensus changes to the existing Ethereum proof-of-work chain. A deposit +contract at address `DEPOSIT_CONTRACT_ADDRESS` is added to the Ethereum +proof-of-work chain defined by the +[chain-id](https://eips.ethereum.org/EIPS/eip-155) -- `DEPOSIT_CHAIN_ID` -- and +the network-id -- `DEPOSIT_NETWORK_ID` -- for deposits of ETH to the beacon +chain. Validator balances will be withdrawable to the execution-layer in a +followup fork after Bellatrix upgrade. -_Note_: See [here](https://chainid.network/) for a comprehensive list of public Ethereum chain chain-id's and network-id's. +_Note_: See [here](https://chainid.network/) for a comprehensive list of public +Ethereum chain chain-id's and network-id's. ### `deposit` function -The deposit contract has a public `deposit` function to make deposits. It takes as arguments `bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata. +The deposit contract has a public `deposit` function to make deposits. It takes +as arguments +`bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root`. +The first three arguments populate a +[`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` +is the expected `DepositData` root as a protection against malformed calldata. #### Deposit amount -The amount of ETH (rounded down to the closest Gwei) sent to the deposit contract is the deposit amount, which must be of size at least `MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no longer usable on the execution-layer until sometime after the Merge. +The amount of ETH (rounded down to the closest Gwei) sent to the deposit +contract is the deposit amount, which must be of size at least +`MIN_DEPOSIT_AMOUNT` Gwei. Note that ETH consumed by the deposit contract is no +longer usable on the execution-layer until sometime after Bellatrix upgrade. #### Withdrawal credentials -One of the `DepositData` fields is `withdrawal_credentials` which constrains validator withdrawals. -The first byte of this 32-byte field is a withdrawal prefix which defines the semantics of the remaining 31 bytes. -The withdrawal prefixes currently supported are `BLS_WITHDRAWAL_PREFIX` and `ETH1_ADDRESS_WITHDRAWAL_PREFIX`. -Read more in the [validator guide](./validator.md#withdrawal-credentials). +One of the `DepositData` fields is `withdrawal_credentials` which constrains +validator withdrawals. The first byte of this 32-byte field is a withdrawal +prefix which defines the semantics of the remaining 31 bytes. The withdrawal +prefixes currently supported are `BLS_WITHDRAWAL_PREFIX` and +`ETH1_ADDRESS_WITHDRAWAL_PREFIX`. Read more in the +[validator guide](./validator.md#withdrawal-credentials). -*Note*: The deposit contract does not validate the `withdrawal_credentials` field. -Support for new withdrawal prefixes can be added without modifying the deposit contract. +*Note*: The deposit contract does not validate the `withdrawal_credentials` +field. Support for new withdrawal prefixes can be added without modifying the +deposit contract. #### `DepositEvent` log -Every deposit emits a `DepositEvent` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract. +Every deposit emits a `DepositEvent` log for consumption by the beacon chain. +The deposit contract does little validation, pushing most of the validator +onboarding logic to the beacon chain. In particular, the proof of possession (a +BLS12-381 signature) is not verified by the deposit contract. ## Solidity code -The deposit contract source code, written in Solidity, is available [here](../../solidity_deposit_contract/deposit_contract.sol). +The deposit contract source code, written in Solidity, is available +[here](../../solidity_deposit_contract/deposit_contract.sol). -*Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof. +*Note*: To save on gas, the deposit contract uses a progressive Merkle root +calculation algorithm that requires only O(log(n)) storage. See +[here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) +for a Python implementation, and +[here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) +for a formal correctness proof. diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 276aa8029a..c426aca39e 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -1,27 +1,45 @@ # Phase 0 -- Beacon Chain Fork Choice -## Table of contents - - - + - [Introduction](#introduction) - [Fork choice](#fork-choice) - - [Preset](#preset) + - [Constant](#constant) + - [Configuration](#configuration) - [Helpers](#helpers) - [`LatestMessage`](#latestmessage) - [`Store`](#store) - [`get_forkchoice_store`](#get_forkchoice_store) - [`get_slots_since_genesis`](#get_slots_since_genesis) - [`get_current_slot`](#get_current_slot) + - [`get_current_store_epoch`](#get_current_store_epoch) - [`compute_slots_since_epoch_start`](#compute_slots_since_epoch_start) - [`get_ancestor`](#get_ancestor) - - [`get_latest_attesting_balance`](#get_latest_attesting_balance) + - [`calculate_committee_fraction`](#calculate_committee_fraction) + - [`get_checkpoint_block`](#get_checkpoint_block) + - [`get_proposer_score`](#get_proposer_score) + - [`get_weight`](#get_weight) + - [`get_voting_source`](#get_voting_source) - [`filter_block_tree`](#filter_block_tree) - [`get_filtered_block_tree`](#get_filtered_block_tree) - [`get_head`](#get_head) - - [`should_update_justified_checkpoint`](#should_update_justified_checkpoint) + - [`update_checkpoints`](#update_checkpoints) + - [`update_unrealized_checkpoints`](#update_unrealized_checkpoints) + - [Proposer head and reorg helpers](#proposer-head-and-reorg-helpers) + - [`is_head_late`](#is_head_late) + - [`is_shuffling_stable`](#is_shuffling_stable) + - [`is_ffg_competitive`](#is_ffg_competitive) + - [`is_finalization_ok`](#is_finalization_ok) + - [`is_proposing_on_time`](#is_proposing_on_time) + - [`is_head_weak`](#is_head_weak) + - [`is_parent_strong`](#is_parent_strong) + - [`get_proposer_head`](#get_proposer_head) + - [Pull-up tip helpers](#pull-up-tip-helpers) + - [`compute_pulled_up_tip`](#compute_pulled_up_tip) + - [`on_tick` helpers](#on_tick-helpers) + - [`on_tick_per_slot`](#on_tick_per_slot) - [`on_attestation` helpers](#on_attestation-helpers) + - [`validate_target_epoch_against_current_time`](#validate_target_epoch_against_current_time) - [`validate_on_attestation`](#validate_on_attestation) - [`store_target_checkpoint_state`](#store_target_checkpoint_state) - [`update_latest_messages`](#update_latest_messages) @@ -29,37 +47,72 @@ - [`on_tick`](#on_tick) - [`on_block`](#on_block) - [`on_attestation`](#on_attestation) + - [`on_attester_slashing`](#on_attester_slashing) - - + ## Introduction -This document is the beacon chain fork choice spec, part of Phase 0. It assumes the [beacon chain state transition function spec](./beacon-chain.md). +This document is the beacon chain fork choice spec, part of Phase 0. It assumes +the [beacon chain state transition function spec](./beacon-chain.md). ## Fork choice -The head block root associated with a `store` is defined as `get_head(store)`. At genesis, let `store = get_forkchoice_store(genesis_state)` and update `store` by running: +The head block root associated with a `store` is defined as `get_head(store)`. +At genesis, let `store = get_forkchoice_store(genesis_state, genesis_block)` and +update `store` by running: -- `on_tick(store, time)` whenever `time > store.time` where `time` is the current Unix time -- `on_block(store, block)` whenever a block `block: SignedBeaconBlock` is received -- `on_attestation(store, attestation)` whenever an attestation `attestation` is received +- `on_tick(store, time)` whenever `time > store.time` where `time` is the + current Unix time +- `on_block(store, block)` whenever a block `block: SignedBeaconBlock` is + received +- `on_attestation(store, attestation)` whenever an attestation `attestation` is + received +- `on_attester_slashing(store, attester_slashing)` whenever an attester slashing + `attester_slashing` is received -Any of the above handlers that trigger an unhandled exception (e.g. a failed assert or an out-of-range list access) are considered invalid. Invalid calls to handlers must not modify `store`. +Any of the above handlers that trigger an unhandled exception (e.g. a failed +assert or an out-of-range list access) are considered invalid. Invalid calls to +handlers must not modify `store`. *Notes*: -1) **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time). -2) **Honest clocks**: Honest nodes are assumed to have clocks synchronized within `SECONDS_PER_SLOT` seconds of each other. -3) **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the [honest validator document](./validator.md) should ensure that `state.latest_eth1_data` of the canonical beacon chain remains consistent with the canonical Ethereum proof-of-work chain. If not, emergency manual intervention will be required. -4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`. -5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost). - -### Preset - -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds | +1. **Leap seconds**: Slots will last `SECONDS_PER_SLOT + 1` or + `SECONDS_PER_SLOT - 1` seconds around leap seconds. This is automatically + handled by [UNIX time](https://en.wikipedia.org/wiki/Unix_time). +2. **Honest clocks**: Honest nodes are assumed to have clocks synchronized + within `SECONDS_PER_SLOT` seconds of each other. +3. **Eth1 data**: The large `ETH1_FOLLOW_DISTANCE` specified in the + [honest validator document](./validator.md) should ensure that + `state.latest_eth1_data` of the canonical beacon chain remains consistent + with the canonical Ethereum proof-of-work chain. If not, emergency manual + intervention will be required. +4. **Manual forks**: Manual forks may arbitrarily change the fork choice rule + but are expected to be enacted at epoch transitions, with the fork details + reflected in `state.fork`. +5. **Implementation**: The implementation found in this specification is + constructed for ease of understanding rather than for optimization in + computation, space, or any other resource. A number of optimized alternatives + can be found [here](https://github.com/protolambda/lmd-ghost). + +### Constant + +| Name | Value | +| -------------------- | ----------- | +| `INTERVALS_PER_SLOT` | `uint64(3)` | + +### Configuration + +| Name | Value | +| ------------------------------------- | ------------- | +| `PROPOSER_SCORE_BOOST` | `uint64(40)` | +| `REORG_HEAD_WEIGHT_THRESHOLD` | `uint64(20)` | +| `REORG_PARENT_WEIGHT_THRESHOLD` | `uint64(160)` | +| `REORG_MAX_EPOCHS_SINCE_FINALIZATION` | `Epoch(2)` | + +- The proposer score boost and re-org weight threshold are percentage values + that are measured with respect to the weight of a single committee. See + `calculate_committee_fraction`. ### Helpers @@ -74,6 +127,23 @@ class LatestMessage(object): #### `Store` +The `Store` is responsible for tracking information required for the fork choice +algorithm. The important fields being tracked are described below: + +- `justified_checkpoint`: the justified checkpoint used as the starting point + for the LMD GHOST fork choice algorithm. +- `finalized_checkpoint`: the highest known finalized checkpoint. The fork + choice only considers blocks that are not conflicting with this checkpoint. +- `unrealized_justified_checkpoint` & `unrealized_finalized_checkpoint`: these + track the highest justified & finalized checkpoints resp., without regard to + whether on-chain ***realization*** has occurred, i.e. FFG processing of new + attestations within the state transition function. This is an important + distinction from `justified_checkpoint` & `finalized_checkpoint`, because they + will only track the checkpoints that are realized on-chain. Note that on-chain + processing of FFG information only happens at epoch boundaries. +- `unrealized_justifications`: stores a map of block root to the unrealized + justified checkpoint observed in that block. + ```python @dataclass class Store(object): @@ -81,19 +151,28 @@ class Store(object): genesis_time: uint64 justified_checkpoint: Checkpoint finalized_checkpoint: Checkpoint - best_justified_checkpoint: Checkpoint + unrealized_justified_checkpoint: Checkpoint + unrealized_finalized_checkpoint: Checkpoint + proposer_boost_root: Root + equivocating_indices: Set[ValidatorIndex] blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) block_states: Dict[Root, BeaconState] = field(default_factory=dict) + block_timeliness: Dict[Root, boolean] = field(default_factory=dict) checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) ``` #### `get_forkchoice_store` -The provided anchor-state will be regarded as a trusted state, to not roll back beyond. -This should be the genesis state for a full client. +The provided anchor-state will be regarded as a trusted state, to not roll back +beyond. This should be the genesis state for a full client. -*Note* With regards to fork choice, block headers are interchangeable with blocks. The spec is likely to move to headers for reduced overhead in test vectors and better encapsulation. Full implementations store blocks as part of their database and will often use full blocks when dealing with production fork choice. +*Note* With regards to fork choice, block headers are interchangeable with +blocks. The spec is likely to move to headers for reduced overhead in test +vectors and better encapsulation. Full implementations store blocks as part of +their database and will often use full blocks when dealing with production fork +choice. ```python def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: @@ -102,15 +181,20 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) - anchor_epoch = get_current_epoch(anchor_state) justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() return Store( time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, - best_justified_checkpoint=justified_checkpoint, + unrealized_justified_checkpoint=justified_checkpoint, + unrealized_finalized_checkpoint=finalized_checkpoint, + proposer_boost_root=proposer_boost_root, + equivocating_indices=set(), blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, + unrealized_justifications={anchor_root: justified_checkpoint}, ) ``` @@ -128,6 +212,13 @@ def get_current_slot(store: Store) -> Slot: return Slot(GENESIS_SLOT + get_slots_since_genesis(store)) ``` +#### `get_current_store_epoch` + +```python +def get_current_store_epoch(store: Store) -> Epoch: + return compute_epoch_at_slot(get_current_slot(store)) +``` + #### `compute_slots_since_epoch_start` ```python @@ -142,34 +233,101 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root: block = store.blocks[root] if block.slot > slot: return get_ancestor(store, block.parent_root, slot) - elif block.slot == slot: - return root - else: - # root is older than queried slot, thus a skip slot. Return most recent root prior to slot - return root + return root +``` + +#### `calculate_committee_fraction` + +```python +def calculate_committee_fraction(state: BeaconState, committee_percent: uint64) -> Gwei: + committee_weight = get_total_active_balance(state) // SLOTS_PER_EPOCH + return Gwei((committee_weight * committee_percent) // 100) +``` + +#### `get_checkpoint_block` + +```python +def get_checkpoint_block(store: Store, root: Root, epoch: Epoch) -> Root: + """ + Compute the checkpoint block for epoch ``epoch`` in the chain of block ``root`` + """ + epoch_first_slot = compute_start_slot_at_epoch(epoch) + return get_ancestor(store, root, epoch_first_slot) +``` + +#### `get_proposer_score` + +```python +def get_proposer_score(store: Store) -> Gwei: + justified_checkpoint_state = store.checkpoint_states[store.justified_checkpoint] + committee_weight = get_total_active_balance(justified_checkpoint_state) // SLOTS_PER_EPOCH + return (committee_weight * PROPOSER_SCORE_BOOST) // 100 ``` -#### `get_latest_attesting_balance` +#### `get_weight` ```python -def get_latest_attesting_balance(store: Store, root: Root) -> Gwei: +def get_weight(store: Store, root: Root) -> Gwei: state = store.checkpoint_states[store.justified_checkpoint] - active_indices = get_active_validator_indices(state, get_current_epoch(state)) - return Gwei(sum( - state.validators[i].effective_balance for i in active_indices - if (i in store.latest_messages - and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root) - )) + unslashed_and_active_indices = [ + i + for i in get_active_validator_indices(state, get_current_epoch(state)) + if not state.validators[i].slashed + ] + attestation_score = Gwei( + sum( + state.validators[i].effective_balance + for i in unslashed_and_active_indices + if ( + i in store.latest_messages + and i not in store.equivocating_indices + and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) + == root + ) + ) + ) + if store.proposer_boost_root == Root(): + # Return only attestation score if ``proposer_boost_root`` is not set + return attestation_score + + # Calculate proposer score if ``proposer_boost_root`` is set + proposer_score = Gwei(0) + # Boost is applied if ``root`` is an ancestor of ``proposer_boost_root`` + if get_ancestor(store, store.proposer_boost_root, store.blocks[root].slot) == root: + proposer_score = get_proposer_score(store) + return attestation_score + proposer_score +``` + +#### `get_voting_source` + +```python +def get_voting_source(store: Store, block_root: Root) -> Checkpoint: + """ + Compute the voting source checkpoint in event that block with root ``block_root`` is the head block + """ + block = store.blocks[block_root] + current_epoch = get_current_store_epoch(store) + block_epoch = compute_epoch_at_slot(block.slot) + if current_epoch > block_epoch: + # The block is from a prior epoch, the voting source will be pulled-up + return store.unrealized_justifications[block_root] + else: + # The block is not from a prior epoch, therefore the voting source is not pulled up + head_state = store.block_states[block_root] + return head_state.current_justified_checkpoint ``` #### `filter_block_tree` +*Note*: External calls to `filter_block_tree` (i.e., any calls that are not made +by the recursive logic in this function) MUST set `block_root` to +`store.justified_checkpoint`. + ```python def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconBlock]) -> bool: block = store.blocks[block_root] children = [ - root for root in store.blocks.keys() - if store.blocks[root].parent_root == block_root + root for root in store.blocks.keys() if store.blocks[root].parent_root == block_root ] # If any children branches contain expected finalized/justified checkpoints, @@ -181,17 +339,28 @@ def filter_block_tree(store: Store, block_root: Root, blocks: Dict[Root, BeaconB return True return False - # If leaf block, check finalized/justified checkpoints as matching latest. - head_state = store.block_states[block_root] + current_epoch = get_current_store_epoch(store) + voting_source = get_voting_source(store, block_root) + # The voting source should be either at the same height as the store's justified checkpoint or + # not more than two epochs ago correct_justified = ( store.justified_checkpoint.epoch == GENESIS_EPOCH - or head_state.current_justified_checkpoint == store.justified_checkpoint + or voting_source.epoch == store.justified_checkpoint.epoch + or voting_source.epoch + 2 >= current_epoch ) + + finalized_checkpoint_block = get_checkpoint_block( + store, + block_root, + store.finalized_checkpoint.epoch, + ) + correct_finalized = ( store.finalized_checkpoint.epoch == GENESIS_EPOCH - or head_state.finalized_checkpoint == store.finalized_checkpoint + or store.finalized_checkpoint.root == finalized_checkpoint_block ) + # If expected finalized/justified, add to viable block-tree and signal viability to parent. if correct_justified and correct_finalized: blocks[block_root] = block @@ -224,54 +393,250 @@ def get_head(store: Store) -> Root: # Execute the LMD-GHOST fork choice head = store.justified_checkpoint.root while True: - children = [ - root for root in blocks.keys() - if blocks[root].parent_root == head - ] + children = [root for root in blocks.keys() if blocks[root].parent_root == head] if len(children) == 0: return head # Sort by latest attesting balance with ties broken lexicographically - head = max(children, key=lambda root: (get_latest_attesting_balance(store, root), root)) + # Ties broken by favoring block with lexicographically higher root + head = max(children, key=lambda root: (get_weight(store, root), root)) ``` -#### `should_update_justified_checkpoint` +#### `update_checkpoints` ```python -def should_update_justified_checkpoint(store: Store, new_justified_checkpoint: Checkpoint) -> bool: +def update_checkpoints( + store: Store, justified_checkpoint: Checkpoint, finalized_checkpoint: Checkpoint +) -> None: + """ + Update checkpoints in store if necessary """ - To address the bouncing attack, only update conflicting justified - checkpoints in the fork choice if in the early slots of the epoch. - Otherwise, delay incorporation of new justified checkpoint until next epoch boundary. + # Update justified checkpoint + if justified_checkpoint.epoch > store.justified_checkpoint.epoch: + store.justified_checkpoint = justified_checkpoint + + # Update finalized checkpoint + if finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: + store.finalized_checkpoint = finalized_checkpoint +``` - See https://ethresear.ch/t/prevention-of-bouncing-attack-on-ffg/6114 for more detailed analysis and discussion. +#### `update_unrealized_checkpoints` + +```python +def update_unrealized_checkpoints( + store: Store, + unrealized_justified_checkpoint: Checkpoint, + unrealized_finalized_checkpoint: Checkpoint, +) -> None: """ - if compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED: - return True + Update unrealized checkpoints in store if necessary + """ + # Update unrealized justified checkpoint + if unrealized_justified_checkpoint.epoch > store.unrealized_justified_checkpoint.epoch: + store.unrealized_justified_checkpoint = unrealized_justified_checkpoint - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) - if not get_ancestor(store, new_justified_checkpoint.root, justified_slot) == store.justified_checkpoint.root: - return False + # Update unrealized finalized checkpoint + if unrealized_finalized_checkpoint.epoch > store.unrealized_finalized_checkpoint.epoch: + store.unrealized_finalized_checkpoint = unrealized_finalized_checkpoint +``` + +#### Proposer head and reorg helpers + +_Implementing these helpers is optional_. - return True +##### `is_head_late` + +```python +def is_head_late(store: Store, head_root: Root) -> bool: + return not store.block_timeliness[head_root] +``` + +##### `is_shuffling_stable` + +```python +def is_shuffling_stable(slot: Slot) -> bool: + return slot % SLOTS_PER_EPOCH != 0 +``` + +##### `is_ffg_competitive` + +```python +def is_ffg_competitive(store: Store, head_root: Root, parent_root: Root) -> bool: + return ( + store.unrealized_justifications[head_root] == store.unrealized_justifications[parent_root] + ) +``` + +##### `is_finalization_ok` + +```python +def is_finalization_ok(store: Store, slot: Slot) -> bool: + epochs_since_finalization = compute_epoch_at_slot(slot) - store.finalized_checkpoint.epoch + return epochs_since_finalization <= REORG_MAX_EPOCHS_SINCE_FINALIZATION +``` + +##### `is_proposing_on_time` + +```python +def is_proposing_on_time(store: Store) -> bool: + # Use half `SECONDS_PER_SLOT // INTERVALS_PER_SLOT` as the proposer reorg deadline + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + proposer_reorg_cutoff = SECONDS_PER_SLOT // INTERVALS_PER_SLOT // 2 + return time_into_slot <= proposer_reorg_cutoff +``` + +##### `is_head_weak` + +```python +def is_head_weak(store: Store, head_root: Root) -> bool: + justified_state = store.checkpoint_states[store.justified_checkpoint] + reorg_threshold = calculate_committee_fraction(justified_state, REORG_HEAD_WEIGHT_THRESHOLD) + head_weight = get_weight(store, head_root) + return head_weight < reorg_threshold +``` + +##### `is_parent_strong` + +```python +def is_parent_strong(store: Store, parent_root: Root) -> bool: + justified_state = store.checkpoint_states[store.justified_checkpoint] + parent_threshold = calculate_committee_fraction(justified_state, REORG_PARENT_WEIGHT_THRESHOLD) + parent_weight = get_weight(store, parent_root) + return parent_weight > parent_threshold +``` + +##### `get_proposer_head` + +```python +def get_proposer_head(store: Store, head_root: Root, slot: Slot) -> Root: + head_block = store.blocks[head_root] + parent_root = head_block.parent_root + parent_block = store.blocks[parent_root] + + # Only re-org the head block if it arrived later than the attestation deadline. + head_late = is_head_late(store, head_root) + + # Do not re-org on an epoch boundary where the proposer shuffling could change. + shuffling_stable = is_shuffling_stable(slot) + + # Ensure that the FFG information of the new head will be competitive with the current head. + ffg_competitive = is_ffg_competitive(store, head_root, parent_root) + + # Do not re-org if the chain is not finalizing with acceptable frequency. + finalization_ok = is_finalization_ok(store, slot) + + # Only re-org if we are proposing on-time. + proposing_on_time = is_proposing_on_time(store) + + # Only re-org a single slot at most. + parent_slot_ok = parent_block.slot + 1 == head_block.slot + current_time_ok = head_block.slot + 1 == slot + single_slot_reorg = parent_slot_ok and current_time_ok + + # Check that the head has few enough votes to be overpowered by our proposer boost. + assert store.proposer_boost_root != head_root # ensure boost has worn off + head_weak = is_head_weak(store, head_root) + + # Check that the missing votes are assigned to the parent and not being hoarded. + parent_strong = is_parent_strong(store, parent_root) + + if all( + [ + head_late, + shuffling_stable, + ffg_competitive, + finalization_ok, + proposing_on_time, + single_slot_reorg, + head_weak, + parent_strong, + ] + ): + # We can re-org the current head by building upon its parent block. + return parent_root + else: + return head_root +``` + +*Note*: The ordering of conditions is a suggestion only. Implementations are +free to optimize by re-ordering the conditions from least to most expensive and +by returning early if any of the early conditions are `False`. + +#### Pull-up tip helpers + +##### `compute_pulled_up_tip` + +```python +def compute_pulled_up_tip(store: Store, block_root: Root) -> None: + state = store.block_states[block_root].copy() + # Pull up the post-state of the block to the next epoch boundary + process_justification_and_finalization(state) + + store.unrealized_justifications[block_root] = state.current_justified_checkpoint + update_unrealized_checkpoints( + store, state.current_justified_checkpoint, state.finalized_checkpoint + ) + + # If the block is from a prior epoch, apply the realized values + block_epoch = compute_epoch_at_slot(store.blocks[block_root].slot) + current_epoch = get_current_store_epoch(store) + if block_epoch < current_epoch: + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) +``` + +#### `on_tick` helpers + +##### `on_tick_per_slot` + +```python +def on_tick_per_slot(store: Store, time: uint64) -> None: + previous_slot = get_current_slot(store) + + # Update store time + store.time = time + + current_slot = get_current_slot(store) + + # If this is a new slot, reset store.proposer_boost_root + if current_slot > previous_slot: + store.proposer_boost_root = Root() + + # If a new epoch, pull-up justification and finalization from previous epoch + if current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0: + update_checkpoints( + store, store.unrealized_justified_checkpoint, store.unrealized_finalized_checkpoint + ) ``` #### `on_attestation` helpers -##### `validate_on_attestation` +##### `validate_target_epoch_against_current_time` ```python -def validate_on_attestation(store: Store, attestation: Attestation) -> None: +def validate_target_epoch_against_current_time(store: Store, attestation: Attestation) -> None: target = attestation.data.target # Attestations must be from the current or previous epoch - current_epoch = compute_epoch_at_slot(get_current_slot(store)) + current_epoch = get_current_store_epoch(store) # Use GENESIS_EPOCH for previous when genesis to avoid underflow previous_epoch = current_epoch - 1 if current_epoch > GENESIS_EPOCH else GENESIS_EPOCH # If attestation target is from a future epoch, delay consideration until the epoch arrives assert target.epoch in [current_epoch, previous_epoch] +``` + +##### `validate_on_attestation` + +```python +def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: + target = attestation.data.target + + # If the given attestation is not from a beacon block message, we have to check the target epoch scope. + if not is_from_block: + validate_target_epoch_against_current_time(store, attestation) + + # Check that the epoch number and slot number are matching assert target.epoch == compute_epoch_at_slot(attestation.data.slot) - # Attestations target be for a known block. If target block is unknown, delay consideration until the block is found + # Attestation target must be for a known block. If target block is unknown, delay consideration until block is found assert target.root in store.blocks # Attestations must be for a known block. If block is unknown, delay consideration until the block is found @@ -280,8 +645,9 @@ def validate_on_attestation(store: Store, attestation: Attestation) -> None: assert store.blocks[attestation.data.beacon_block_root].slot <= attestation.data.slot # LMD vote must be consistent with FFG vote target - target_slot = compute_start_slot_at_epoch(target.epoch) - assert target.root == get_ancestor(store, attestation.data.beacon_block_root, target_slot) + assert target.root == get_checkpoint_block( + store, attestation.data.beacon_block_root, target.epoch + ) # Attestations can only affect the fork choice of subsequent slots. # Delay consideration in the fork choice until their slot is in the past. @@ -303,37 +669,32 @@ def store_target_checkpoint_state(store: Store, target: Checkpoint) -> None: ##### `update_latest_messages` ```python -def update_latest_messages(store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation) -> None: +def update_latest_messages( + store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation +) -> None: target = attestation.data.target beacon_block_root = attestation.data.beacon_block_root - for i in attesting_indices: + non_equivocating_attesting_indices = [ + i for i in attesting_indices if i not in store.equivocating_indices + ] + for i in non_equivocating_attesting_indices: if i not in store.latest_messages or target.epoch > store.latest_messages[i].epoch: store.latest_messages[i] = LatestMessage(epoch=target.epoch, root=beacon_block_root) ``` - ### Handlers #### `on_tick` ```python def on_tick(store: Store, time: uint64) -> None: - previous_slot = get_current_slot(store) - - # update store time - store.time = time - - current_slot = get_current_slot(store) - # Not a new epoch, return - if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0): - return - - # Update store.justified_checkpoint if a better checkpoint on the store.finalized_checkpoint chain - if store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.best_justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot == store.finalized_checkpoint.root: - store.justified_checkpoint = store.best_justified_checkpoint + # If the ``store.time`` falls behind, while loop catches up slot by slot + # to ensure that every previous slot is processed with ``on_tick_per_slot`` + tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT + while get_current_slot(store) < tick_slot: + previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT + on_tick_per_slot(store, previous_time) + on_tick_per_slot(store, time) ``` #### `on_block` @@ -345,59 +706,59 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: assert block.parent_root in store.block_states # Make a copy of the state to avoid mutability issues pre_state = copy(store.block_states[block.parent_root]) - # Blocks cannot be in the future. If they are, their consideration must be delayed until the are in the past. + # Blocks cannot be in the future. If they are, their consideration must be delayed until they are in the past. assert get_current_slot(store) >= block.slot # Check that block is later than the finalized epoch slot (optimization to reduce calls to get_ancestor) finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) assert block.slot > finalized_slot # Check block is a descendant of the finalized block at the checkpoint finalized slot - assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root + finalized_checkpoint_block = get_checkpoint_block( + store, + block.parent_root, + store.finalized_checkpoint.epoch, + ) + assert store.finalized_checkpoint.root == finalized_checkpoint_block # Check the block is valid and compute the post-state state = pre_state.copy() + block_root = hash_tree_root(block) state_transition(state, signed_block, True) # Add new block to the store - store.blocks[hash_tree_root(block)] = block + store.blocks[block_root] = block # Add new state for this block to the store - store.block_states[hash_tree_root(block)] = state + store.block_states[block_root] = state - # Update justified checkpoint - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch: - store.best_justified_checkpoint = state.current_justified_checkpoint - if should_update_justified_checkpoint(store, state.current_justified_checkpoint): - store.justified_checkpoint = state.current_justified_checkpoint + # Add block timeliness to the store + time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT + is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT + is_timely = get_current_slot(store) == block.slot and is_before_attesting_interval + store.block_timeliness[hash_tree_root(block)] = is_timely - # Update finalized checkpoint - if state.finalized_checkpoint.epoch > store.finalized_checkpoint.epoch: - store.finalized_checkpoint = state.finalized_checkpoint + # Add proposer score boost if the block is timely and not conflicting with an existing block + is_first_block = store.proposer_boost_root == Root() + if is_timely and is_first_block: + store.proposer_boost_root = hash_tree_root(block) - # Potentially update justified if different from store - if store.justified_checkpoint != state.current_justified_checkpoint: - # Update justified if new justified is later than store justified - if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch: - store.justified_checkpoint = state.current_justified_checkpoint - return + # Update checkpoints in store if necessary + update_checkpoints(store, state.current_justified_checkpoint, state.finalized_checkpoint) - # Update justified if store justified is not in chain with finalized checkpoint - finalized_slot = compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = get_ancestor(store, store.justified_checkpoint.root, finalized_slot) - if ancestor_at_finalized_slot != store.finalized_checkpoint.root: - store.justified_checkpoint = state.current_justified_checkpoint + # Eagerly compute unrealized justification and finality + compute_pulled_up_tip(store, block_root) ``` #### `on_attestation` ```python -def on_attestation(store: Store, attestation: Attestation) -> None: +def on_attestation(store: Store, attestation: Attestation, is_from_block: bool = False) -> None: """ Run ``on_attestation`` upon receiving a new ``attestation`` from either within a block or directly on the wire. An ``attestation`` that is asserted as invalid may be valid at a later time, consider scheduling it for later processing in such case. """ - validate_on_attestation(store, attestation) + validate_on_attestation(store, attestation, is_from_block) + store_target_checkpoint_state(store, attestation.data.target) # Get state at the `target` to fully validate attestation @@ -408,3 +769,27 @@ def on_attestation(store: Store, attestation: Attestation) -> None: # Update latest messages for attesting indices update_latest_messages(store, indexed_attestation.attesting_indices, attestation) ``` + +#### `on_attester_slashing` + +*Note*: `on_attester_slashing` should be called while syncing and a client MUST +maintain the equivocation set of `AttesterSlashing`s from at least the latest +finalized checkpoint. + +```python +def on_attester_slashing(store: Store, attester_slashing: AttesterSlashing) -> None: + """ + Run ``on_attester_slashing`` immediately upon receiving a new ``AttesterSlashing`` + from either within a block or directly on the wire. + """ + attestation_1 = attester_slashing.attestation_1 + attestation_2 = attester_slashing.attestation_2 + assert is_slashable_attestation_data(attestation_1.data, attestation_2.data) + state = store.block_states[store.justified_checkpoint.root] + assert is_valid_indexed_attestation(state, attestation_1) + assert is_valid_indexed_attestation(state, attestation_2) + + indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices) + for index in indices: + store.equivocating_indices.add(index) +``` diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index f5c138f993..5b88769651 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1,27 +1,21 @@ # 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) - [Protocol Negotiation](#protocol-negotiation) - [Multiplexing](#multiplexing) - [Consensus-layer network interaction domains](#consensus-layer-network-interaction-domains) + - [Custom types](#custom-types) + - [Constants](#constants) - [Configuration](#configuration) - [MetaData](#metadata) + - [Maximum message sizes](#maximum-message-sizes) + - [`max_compressed_len`](#max_compressed_len) + - [`max_message_size`](#max_message_size) - [The gossip domain: gossipsub](#the-gossip-domain-gossipsub) - [Topics and messages](#topics-and-messages) - [Global topics](#global-topics) @@ -34,6 +28,7 @@ It consists of four main sections: - [`beacon_attestation_{subnet_id}`](#beacon_attestation_subnet_id) - [Attestations and Aggregation](#attestations-and-aggregation) - [Encodings](#encodings) + - [Gossipsub size limits](#gossipsub-size-limits) - [The Req/Resp domain](#the-reqresp-domain) - [Protocol identification](#protocol-identification) - [Req/Resp interaction](#reqresp-interaction) @@ -42,17 +37,18 @@ It consists of four main sections: - [Encoding strategies](#encoding-strategies) - [SSZ-snappy encoding strategy](#ssz-snappy-encoding-strategy) - [Messages](#messages) - - [Status](#status) - - [Goodbye](#goodbye) - - [BeaconBlocksByRange](#beaconblocksbyrange) - - [BeaconBlocksByRoot](#beaconblocksbyroot) - - [Ping](#ping) - - [GetMetaData](#getmetadata) + - [Status v1](#status-v1) + - [Goodbye v1](#goodbye-v1) + - [BeaconBlocksByRange v1](#beaconblocksbyrange-v1) + - [BeaconBlocksByRoot v1](#beaconblocksbyroot-v1) + - [Ping v1](#ping-v1) + - [GetMetaData v1](#getmetadata-v1) - [The discovery domain: discv5](#the-discovery-domain-discv5) - [Integration into libp2p stacks](#integration-into-libp2p-stacks) - [ENR structure](#enr-structure) - [Attestation subnet bitfield](#attestation-subnet-bitfield) - [`eth2` field](#eth2-field) + - [Attestation subnet subscription](#attestation-subnet-subscription) - [Design decision rationale](#design-decision-rationale) - [Transport](#transport-1) - [Why are we defining specific transports?](#why-are-we-defining-specific-transports) @@ -83,7 +79,7 @@ It consists of four main sections: - [Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots?](#why-are-attestations-limited-to-be-broadcast-on-gossip-channels-within-slots_per_epoch-slots) - [Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s?](#why-are-aggregate-attestations-broadcast-to-the-global-topic-as-aggregateandproofs-rather-than-just-as-attestations) - [Why are we sending entire objects in the pubsub and not just hashes?](#why-are-we-sending-entire-objects-in-the-pubsub-and-not-just-hashes) - - [Should clients gossip blocks if they *cannot* validate the proposer signature due to not yet being synced, not knowing the head block, etc?](#should-clients-gossip-blocks-if-they-cannot-validate-the-proposer-signature-due-to-not-yet-being-synced-not-knowing-the-head-block-etc) + - [Should clients gossip blocks if they cannot validate the proposer signature due to not yet being synced, not knowing the head block, etc?](#should-clients-gossip-blocks-if-they-cannot-validate-the-proposer-signature-due-to-not-yet-being-synced-not-knowing-the-head-block-etc) - [How are we going to discover peers in a gossipsub topic?](#how-are-we-going-to-discover-peers-in-a-gossipsub-topic) - [How should fork version be used in practice?](#how-should-fork-version-be-used-in-practice) - [Req/Resp](#reqresp) @@ -91,6 +87,7 @@ It consists of four main sections: - [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding) - [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver) - [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc) + - [What is a typical rate limiting strategy?](#what-is-a-typical-rate-limiting-strategy) - [Why do we allow empty responses in block requests?](#why-do-we-allow-empty-responses-in-block-requests) - [Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from?](#why-does-beaconblocksbyrange-let-the-server-choose-which-branch-to-send-blocks-from) - [Why are `BlocksByRange` requests only required to be served for the latest `MIN_EPOCHS_FOR_BLOCK_REQUESTS` epochs?](#why-are-blocksbyrange-requests-only-required-to-be-served-for-the-latest-min_epochs_for_block_requests-epochs) @@ -106,83 +103,132 @@ It consists of four main sections: - [Why are we using Snappy for compression?](#why-are-we-using-snappy-for-compression) - [Can I get access to unencrypted bytes on the wire for debugging purposes?](#can-i-get-access-to-unencrypted-bytes-on-the-wire-for-debugging-purposes) - [What are SSZ type size bounds?](#what-are-ssz-type-size-bounds) + - [Why is the message size defined in terms of application payload?](#why-is-the-message-size-defined-in-terms-of-application-payload) + - [Why is there a limit on message sizes at all?](#why-is-there-a-limit-on-message-sizes-at-all) - [libp2p implementations matrix](#libp2p-implementations-matrix) - - + + +## Introduction + +This document contains the networking specification for Phase 0. + +It consists of four main sections: -# Network fundamentals +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. -This section outlines the specification for the networking stack in Ethereum consensus-layer clients. +## Network fundamentals -## Transport +This section outlines the specification for the networking stack in Ethereum +consensus-layer clients. -Even though libp2p is a multi-transport stack (designed to listen on multiple simultaneous transports and endpoints transparently), -we hereby define a profile for basic interoperability. +### Transport -All implementations MUST support the TCP libp2p transport, and it MUST be enabled for both dialing and listening (i.e. outbound and inbound connections). -The libp2p TCP transport supports listening on IPv4 and IPv6 addresses (and on multiple simultaneously). +Even though libp2p is a multi-transport stack (designed to listen on multiple +simultaneous transports and endpoints transparently), we hereby define a profile +for basic interoperability. -Clients must support listening on at least one of IPv4 or IPv6. -Clients that do _not_ have support for listening on IPv4 SHOULD be cognizant of the potential disadvantages in terms of -Internet-wide routability/support. Clients MAY choose to listen only on IPv6, but MUST be capable of dialing both IPv4 and IPv6 addresses. +All implementations MUST support the TCP libp2p transport, MAY support the QUIC +(UDP) libp2p transport, and MUST be enabled for both dialing and listening (i.e. +outbound and inbound connections). The libp2p TCP and QUIC (UDP) transports +support listening on IPv4 and IPv6 addresses (and on multiple simultaneously). -All listening endpoints must be publicly dialable, and thus not rely on libp2p circuit relay, AutoNAT, or AutoRelay facilities. -(Usage of circuit relay, AutoNAT, or AutoRelay will be specifically re-examined soon.) +Clients must support listening on at least one of IPv4 or IPv6. Clients that do +_not_ have support for listening on IPv4 SHOULD be cognizant of the potential +disadvantages in terms of Internet-wide routability/support. Clients MAY choose +to listen only on IPv6, but MUST be capable of dialing both IPv4 and IPv6 +addresses. -Nodes operating behind a NAT, or otherwise undialable by default (e.g. container runtime, firewall, etc.), -MUST have their infrastructure configured to enable inbound traffic on the announced public listening endpoint. +All listening endpoints must be publicly dialable, and thus not rely on libp2p +circuit relay, AutoNAT, or AutoRelay facilities. (Usage of circuit relay, +AutoNAT, or AutoRelay will be specifically re-examined soon.) -## Encryption and identification +Nodes operating behind a NAT, or otherwise undialable by default (e.g. container +runtime, firewall, etc.), MUST have their infrastructure configured to enable +inbound traffic on the announced public listening endpoint. + +### Encryption and identification The [Libp2p-noise](https://github.com/libp2p/specs/tree/master/noise) secure channel handshake with `secp256k1` identities will be used for encryption. -As specified in the libp2p specification, clients MUST support the `XX` handshake pattern. +As specified in the libp2p specification, clients MUST support the `XX` +handshake pattern. -## Protocol Negotiation +### Protocol Negotiation -Clients MUST use exact equality when negotiating protocol versions to use and MAY use the version to give priority to higher version numbers. +Clients MUST use exact equality when negotiating protocol versions to use and +MAY use the version to give priority to higher version numbers. -Clients MUST support [multistream-select 1.0](https://github.com/multiformats/multistream-select/) -and MAY support [multiselect 2.0](https://github.com/libp2p/specs/pull/95) when the spec solidifies. -Once all clients have implementations for multiselect 2.0, multistream-select 1.0 MAY be phased out. +Clients MUST support +[multistream-select 1.0](https://github.com/multiformats/multistream-select/) +and MAY support [multiselect 2.0](https://github.com/libp2p/specs/pull/95) when +the spec solidifies. Once all clients have implementations for multiselect 2.0, +multistream-select 1.0 MAY be phased out. -## Multiplexing +### Multiplexing -During connection bootstrapping, libp2p dynamically negotiates a mutually supported multiplexing method to conduct parallel conversations. -This applies to transports that are natively incapable of multiplexing (e.g. TCP, WebSockets, WebRTC), -and is omitted for capable transports (e.g. QUIC). +During connection bootstrapping, libp2p dynamically negotiates a mutually +supported multiplexing method to conduct parallel conversations. This applies to +transports that are natively incapable of multiplexing (e.g. TCP, WebSockets, +WebRTC), and is omitted for capable transports (e.g. QUIC). Two multiplexers are commonplace in libp2p implementations: -[mplex](https://github.com/libp2p/specs/tree/master/mplex) and [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). -Their protocol IDs are, respectively: `/mplex/6.7.0` and `/yamux/1.0.0`. +[mplex](https://github.com/libp2p/specs/tree/master/mplex) and +[yamux](https://github.com/libp2p/specs/blob/master/yamux/README.md). Their +protocol IDs are, respectively: `/mplex/6.7.0` and `/yamux/1.0.0`. Clients MUST support [mplex](https://github.com/libp2p/specs/tree/master/mplex) -and MAY support [yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). -If both are supported by the client, yamux MUST take precedence during negotiation. -See the [Rationale](#design-decision-rationale) section below for tradeoffs. +and MAY support +[yamux](https://github.com/libp2p/specs/blob/master/yamux/README.md). If both +are supported by the client, yamux MUST take precedence during negotiation. See +the [Rationale](#design-decision-rationale) section below for tradeoffs. + +## Consensus-layer network interaction domains + +### Custom types + +We define the following Python custom types for type hinting and readability: + +| Name | SSZ equivalent | Description | +| ---------- | -------------- | ----------------- | +| `NodeID` | `uint256` | node identifier | +| `SubnetID` | `uint64` | subnet identifier | + +### Constants -# Consensus-layer network interaction domains +| Name | Value | Unit | +| -------------- | ----- | :------------------------------: | +| `NODE_ID_BITS` | `256` | The bit length of uint256 is 256 | -## Configuration +### Configuration -This section outlines constants that are used in this spec. +This section outlines configurations that are used in this spec. -| Name | Value | Description | -|---|---|---| -| `GOSSIP_MAX_SIZE` | `2**20` (= 1048576, 1 MiB) | The maximum allowed size of uncompressed gossip messages. | -| `MAX_REQUEST_BLOCKS` | `2**10` (= 1024) | Maximum number of blocks in a single request | -| `MIN_EPOCHS_FOR_BLOCK_REQUESTS` | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) | The minimum epoch range over which a node must serve blocks | -| `MAX_CHUNK_SIZE` | `2**20` (1048576, 1 MiB) | The maximum allowed size of uncompressed req/resp chunked responses. | -| `TTFB_TIMEOUT` | `5s` | The maximum time to wait for first byte of request response (time-to-first-byte). | -| `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | -| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. | -| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. | -| `MESSAGE_DOMAIN_INVALID_SNAPPY` | `0x00000000` | 4-byte domain for gossip message-id isolation of *invalid* snappy messages | -| `MESSAGE_DOMAIN_VALID_SNAPPY` | `0x01000000` | 4-byte domain for gossip message-id isolation of *valid* snappy messages | +| Name | Value | Description | +| ------------------------------------ | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | +| `MAX_PAYLOAD_SIZE` | `10 * 2**20` (= 10485760, 10 MiB) | The maximum allowed size of uncompressed payload in gossipsub messages and RPC chunks | +| `MAX_REQUEST_BLOCKS` | `2**10` (= 1024) | Maximum number of blocks in a single request | +| `EPOCHS_PER_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | Number of epochs on a subnet subscription (~27 hours) | +| `MIN_EPOCHS_FOR_BLOCK_REQUESTS` | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2` (= 33024, ~5 months) | The minimum epoch range over which a node must serve blocks | +| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated | +| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500` | The maximum **milliseconds** of clock disparity assumed between honest nodes | +| `MESSAGE_DOMAIN_INVALID_SNAPPY` | `DomainType('0x00000000')` | 4-byte domain for gossip message-id isolation of *invalid* snappy messages | +| `MESSAGE_DOMAIN_VALID_SNAPPY` | `DomainType('0x01000000')` | 4-byte domain for gossip message-id isolation of *valid* snappy messages | +| `SUBNETS_PER_NODE` | `2` | The number of long-lived subnets a beacon node should be subscribed to | +| `ATTESTATION_SUBNET_COUNT` | `2**6` (= 64) | The number of attestation subnets used in the gossipsub protocol. | +| `ATTESTATION_SUBNET_EXTRA_BITS` | `0` | The number of extra bits of a NodeId to use when mapping to a subscribed subnet | +| `ATTESTATION_SUBNET_PREFIX_BITS` | `int(ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS)` | | +| `MAX_CONCURRENT_REQUESTS` | `2` | Maximum number of concurrent requests per protocol ID that a client may issue | -## MetaData +### MetaData Clients MUST locally store the following `MetaData`: @@ -195,81 +241,125 @@ Clients MUST locally store the following `MetaData`: Where -- `seq_number` is a `uint64` starting at `0` used to version the node's metadata. - If any other field in the local `MetaData` changes, the node MUST increment `seq_number` by 1. -- `attnets` is a `Bitvector` representing the node's persistent attestation subnet subscriptions. +- `seq_number` is a `uint64` starting at `0` used to version the node's + metadata. If any other field in the local `MetaData` changes, the node MUST + increment `seq_number` by 1. +- `attnets` is a `Bitvector` representing the node's persistent attestation + subnet subscriptions. -*Note*: `MetaData.seq_number` is used for versioning of the node's metadata, -is entirely independent of the ENR sequence number, -and will in most cases be out of sync with the ENR sequence number. +*Note*: `MetaData.seq_number` is used for versioning of the node's metadata, is +entirely independent of the ENR sequence number, and will in most cases be out +of sync with the ENR sequence number. -## The gossip domain: gossipsub +### Maximum message sizes -Clients MUST support the [gossipsub v1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md) libp2p Protocol -including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) extension. +Maximum message sizes are derived from the maximum payload size that the network +can carry according to the following functions: + +#### `max_compressed_len` + +```python +def max_compressed_len(n: uint64) -> uint64: + # Worst-case compressed length for a given payload of size n when using snappy: + # https://github.com/google/snappy/blob/32ded457c0b1fe78ceb8397632c416568d6714a0/snappy.cc#L218C1-L218C47 + return uint64(32 + n + n / 6) +``` + +#### `max_message_size` + +```python +def max_message_size() -> uint64: + # Allow 1024 bytes for framing and encoding overhead but at least 1MiB in case MAX_PAYLOAD_SIZE is small. + return max(max_compressed_len(MAX_PAYLOAD_SIZE) + 1024, 1024 * 1024) +``` + +### The gossip domain: gossipsub + +Clients MUST support the +[gossipsub v1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md) +libp2p Protocol including the +[gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md) +extension. **Protocol ID:** `/meshsub/1.1.0` **Gossipsub Parameters** -The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used: +The following gossipsub +[parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) +will be used: - `D` (topic stable mesh target count): 8 - `D_low` (topic stable mesh low watermark): 6 - `D_high` (topic stable mesh high watermark): 12 - `D_lazy` (gossip target): 6 - `heartbeat_interval` (frequency of heartbeat, seconds): 0.7 -- `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have published to, seconds): 60 -- `mcache_len` (number of windows to retain full messages in cache for `IWANT` responses): 6 +- `fanout_ttl` (ttl for fanout maps for topics we are not subscribed to but have + published to, seconds): 60 +- `mcache_len` (number of windows to retain full messages in cache for `IWANT` + responses): 6 - `mcache_gossip` (number of windows to gossip about): 3 -- `seen_ttl` (number of heartbeat intervals to retain message IDs): 550 +- `seen_ttl` (expiry time for cache of seen message ids, seconds): + SECONDS_PER_SLOT * SLOTS_PER_EPOCH * 2 *Note*: Gossipsub v1.1 introduces a number of [additional parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#overview-of-new-parameters) -for peer scoring and other attack mitigations. -These are currently under investigation and will be spec'd and released to mainnet when they are ready. - -### Topics and messages - -Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). -Topic strings have form: `/eth2/ForkDigestValue/Name/Encoding`. -This defines both the type of data being sent on the topic and how the data field of the message is encoded. - -- `ForkDigestValue` - the lowercase hex-encoded (no "0x" prefix) bytes of `compute_fork_digest(current_fork_version, genesis_validators_root)` where - - `current_fork_version` is the fork version of the epoch of the message to be sent on the topic - - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` +for peer scoring and other attack mitigations. These are currently under +investigation and will be spec'd and released to mainnet when they are ready. + +#### Topics and messages + +Topics are plain UTF-8 strings and are encoded on the wire as determined by +protobuf (gossipsub messages are enveloped in protobuf messages). Topic strings +have form: `/eth2/ForkDigestValue/Name/Encoding`. This defines both the type of +data being sent on the topic and how the data field of the message is encoded. + +- `ForkDigestValue` - the lowercase hex-encoded (no "0x" prefix) bytes of + `compute_fork_digest(current_fork_version, genesis_validators_root)` where + - `current_fork_version` is the fork version of the epoch of the message to be + sent on the topic + - `genesis_validators_root` is the static `Root` found in + `state.genesis_validators_root` - `Name` - see table below -- `Encoding` - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. - See the [Encodings](#Encodings) section for further details. - -*Note*: `ForkDigestValue` is composed of values that are not known until the genesis block/state are available. -Due to this, clients SHOULD NOT subscribe to gossipsub topics until these genesis values are known. - -Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/pb/rpc.proto#L17-L24) has a maximum size of `GOSSIP_MAX_SIZE`. -Clients MUST reject (fail validation) messages that are over this size limit. -Likewise, clients MUST NOT emit or propagate messages larger than this limit. - -The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message, -since messages are identified by content, anonymous, and signed where necessary in the application layer. -Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` -[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options). - -The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data: -* If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of - the concatenation of `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data, - i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`. -* Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of - the concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data, +- `Encoding` - the encoding strategy describes a specific representation of + bytes that will be transmitted over the wire. See the [Encodings](#Encodings) + section for further details. + +Clients MUST reject messages with an unknown topic. + +*Note*: `ForkDigestValue` is composed of values that are not known until the +genesis block/state are available. Due to this, clients SHOULD NOT subscribe to +gossipsub topics until these genesis values are known. + +The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf +fields are omitted from the message, since messages are identified by content, +anonymous, and signed where necessary in the application layer. Starting from +Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` +[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options). + +The `message-id` of a gossipsub message MUST be the following 20 byte value +computed from the message data: + +- If `message.data` has a valid snappy decompression, set `message-id` to the + first 20 bytes of the `SHA256` hash of the concatenation of + `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data, i.e. + `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`. +- Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of the + concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data, i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]`. -*Note*: The above logic handles two exceptional cases: -(1) multiple snappy `data` can decompress to the same value, -and (2) some message `data` can fail to snappy decompress altogether. +Where relevant, clients MUST reject messages with `message-id` sizes other than +20 bytes. + +*Note*: The above logic handles two exceptional cases: (1) multiple snappy +`data` can decompress to the same value, and (2) some message `data` can fail to +snappy decompress altogether. -The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic: +The payload is carried in the `data` field of a gossipsub message, and varies +depending on the topic: | Name | Message Type | -|----------------------------------|---------------------------| +| -------------------------------- | ------------------------- | | `beacon_block` | `SignedBeaconBlock` | | `beacon_aggregate_and_proof` | `SignedAggregateAndProof` | | `beacon_attestation_{subnet_id}` | `Attestation` | @@ -277,193 +367,295 @@ The payload is carried in the `data` field of a gossipsub message, and varies de | `proposer_slashing` | `ProposerSlashing` | | `attester_slashing` | `AttesterSlashing` | -Clients MUST reject (fail validation) messages containing an incorrect type, or invalid payload. +Clients MUST reject (fail validation) messages containing an incorrect type, or +invalid payload. -When processing incoming gossip, clients MAY descore or disconnect peers who fail to observe these constraints. +When processing incoming gossip, clients MAY descore or disconnect peers who +fail to observe these constraints. -For any optional queueing, clients SHOULD maintain maximum queue sizes to avoid DoS vectors. +For any optional queueing, clients SHOULD maintain maximum queue sizes to avoid +DoS vectors. -Gossipsub v1.1 introduces [Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators) -for the application to aid in the gossipsub peer-scoring scheme. -We utilize `ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are application specific validations. -If all validations pass, return `ACCEPT`. -If one or more validations fail while processing the items in order, return either `REJECT` or `IGNORE` as specified in the prefix of the particular condition. +Gossipsub v1.1 introduces +[Extended Validators](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#extended-validators) +for the application to aid in the gossipsub peer-scoring scheme. We utilize +`ACCEPT`, `REJECT`, and `IGNORE`. For each gossipsub topic, there are +application specific validations. If all validations pass, return `ACCEPT`. If +one or more validations fail while processing the items in order, return either +`REJECT` or `IGNORE` as specified in the prefix of the particular condition. -#### Global topics +##### Global topics -There are two primary global topics used to propagate beacon blocks (`beacon_block`) -and aggregate attestations (`beacon_aggregate_and_proof`) to all nodes on the network. +There are two primary global topics used to propagate beacon blocks +(`beacon_block`) and aggregate attestations (`beacon_aggregate_and_proof`) to +all nodes on the network. -There are three additional global topics that are used to propagate lower frequency validator messages -(`voluntary_exit`, `proposer_slashing`, and `attester_slashing`). +There are three additional global topics that are used to propagate lower +frequency validator messages (`voluntary_exit`, `proposer_slashing`, and +`attester_slashing`). -##### `beacon_block` +###### `beacon_block` -The `beacon_block` topic is used solely for propagating new signed beacon blocks to all nodes on the networks. -Signed blocks are sent in their entirety. +The `beacon_block` topic is used solely for propagating new signed beacon blocks +to all nodes on the networks. Signed blocks are sent in their entirety. -The following validations MUST pass before forwarding the `signed_beacon_block` on the network. -- _[IGNORE]_ The block is not from a future slot (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `signed_beacon_block.message.slot <= current_slot` - (a client MAY queue future blocks for processing at the appropriate slot). +The following validations MUST pass before forwarding the `signed_beacon_block` +on the network. + +- _[IGNORE]_ The block is not from a future slot (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. validate that + `signed_beacon_block.message.slot <= current_slot` (a client MAY queue future + blocks for processing at the appropriate slot). - _[IGNORE]_ The block is from a slot greater than the latest finalized slot -- - i.e. validate that `signed_beacon_block.message.slot > compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)` - (a client MAY choose to validate and store such blocks for additional purposes -- e.g. slashing detection, archive nodes, etc). -- _[IGNORE]_ The block is the first block with valid signature received for the proposer for the slot, `signed_beacon_block.message.slot`. -- _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid with respect to the `proposer_index` pubkey. + i.e. validate that + `signed_beacon_block.message.slot > compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)` + (a client MAY choose to validate and store such blocks for additional purposes + -- e.g. slashing detection, archive nodes, etc). +- _[IGNORE]_ The block is the first block with valid signature received for the + proposer for the slot, `signed_beacon_block.message.slot`. +- _[REJECT]_ The proposer signature, `signed_beacon_block.signature`, is valid + with respect to the `proposer_index` pubkey. - _[IGNORE]_ The block's parent (defined by `block.parent_root`) has been seen - (via both gossip and non-gossip sources) - (a client MAY queue blocks for processing once the parent block is retrieved). -- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes validation. + (via gossip or non-gossip sources) (a client MAY queue blocks for processing + once the parent block is retrieved). +- _[REJECT]_ The block's parent (defined by `block.parent_root`) passes + validation. - _[REJECT]_ The block is from a higher slot than its parent. -- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of `block` -- i.e. - `get_ancestor(store, block.parent_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) - == store.finalized_checkpoint.root` -- _[REJECT]_ The block is proposed by the expected `proposer_index` for the block's slot - in the context of the current shuffling (defined by `parent_root`/`slot`). - If the `proposer_index` cannot immediately be verified against the expected shuffling, - the block 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. - -##### `beacon_aggregate_and_proof` - -The `beacon_aggregate_and_proof` topic is used to propagate aggregated attestations (as `SignedAggregateAndProof`s) -to subscribing nodes (typically validators) to be included in future blocks. - -The following validations MUST pass before forwarding the `signed_aggregate_and_proof` on the network. -(We define the following for convenience -- `aggregate_and_proof = signed_aggregate_and_proof.message` and `aggregate = aggregate_and_proof.aggregate`) -- _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` +- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of `block` -- + i.e. + `get_checkpoint_block(store, block.parent_root, store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root` +- _[REJECT]_ The block is proposed by the expected `proposer_index` for the + block's slot in the context of the current shuffling (defined by + `parent_root`/`slot`). If the `proposer_index` cannot immediately be verified + against the expected shuffling, the block 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. + +###### `beacon_aggregate_and_proof` + +The `beacon_aggregate_and_proof` topic is used to propagate aggregated +attestations (as `SignedAggregateAndProof`s) to subscribing nodes (typically +validators) to be included in future blocks. + +We define the following variables for convenience: + +- `aggregate_and_proof = signed_aggregate_and_proof.message` +- `aggregate = aggregate_and_proof.aggregate` +- `index = aggregate.data.index` +- `aggregation_bits = attestation.aggregation_bits` + +The following validations MUST pass before forwarding the +`signed_aggregate_and_proof` on the network. + +- _[REJECT]_ The committee index is within the expected range -- i.e. + `index < get_committee_count_per_slot(state, aggregate.data.target.epoch)`. +- _[IGNORE]_ `aggregate.data.slot` is within the last + `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). -- _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch == - compute_epoch_at_slot(aggregate.data.slot)` -- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator - with index `aggregate_and_proof.aggregator_index` for the epoch `aggregate.data.target.epoch`. -- _[REJECT]_ The attestation has participants -- - that is, `len(get_attesting_indices(state, aggregate.data, aggregate.aggregation_bits)) >= 1`. -- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an aggregator for the slot -- - i.e. `is_aggregator(state, aggregate.data.slot, aggregate.data.index, aggregate_and_proof.selection_proof)` returns `True`. -- _[REJECT]_ The aggregator's validator index is within the committee -- - i.e. `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, aggregate.data.index)`. -- _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature - of the `aggregate.data.slot` by the validator with index `aggregate_and_proof.aggregator_index`. -- _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, is valid. +- _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. + `aggregate.data.target.epoch == compute_epoch_at_slot(aggregate.data.slot)` +- _[REJECT]_ The number of aggregation bits matches the committee size -- i.e. + `len(aggregation_bits) == len(get_beacon_committee(state, aggregate.data.slot, index))`. +- _[REJECT]_ The aggregate attestation has participants -- that is, + `len(get_attesting_indices(state, aggregate)) >= 1`. +- _[IGNORE]_ A valid aggregate attestation defined by + `hash_tree_root(aggregate.data)` whose `aggregation_bits` is a non-strict + superset has _not_ already been seen. (via aggregate gossip, within a verified + block, or through the creation of an equivalent aggregate locally). +- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the + aggregator with index `aggregate_and_proof.aggregator_index` for the epoch + `aggregate.data.target.epoch`. +- _[REJECT]_ The attestation has participants -- that is, + `len(get_attesting_indices(state, aggregate)) >= 1`. +- _[REJECT]_ `aggregate_and_proof.selection_proof` selects the validator as an + aggregator for the slot -- i.e. + `is_aggregator(state, aggregate.data.slot, index, aggregate_and_proof.selection_proof)` + returns `True`. +- _[REJECT]_ The aggregator's validator index is within the committee -- i.e. + `aggregate_and_proof.aggregator_index in get_beacon_committee(state, aggregate.data.slot, index)`. +- _[REJECT]_ The `aggregate_and_proof.selection_proof` is a valid signature of + the `aggregate.data.slot` by the validator with index + `aggregate_and_proof.aggregator_index`. +- _[REJECT]_ The aggregator signature, `signed_aggregate_and_proof.signature`, + is valid. - _[REJECT]_ The signature of `aggregate` is valid. -- _[IGNORE]_ The block being voted for (`aggregate.data.beacon_block_root`) has been seen - (via both gossip and non-gossip sources) - (a client MAY queue aggregates for processing once block is retrieved). -- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) passes validation. -- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `aggregate.data.beacon_block_root` -- i.e. - `get_ancestor(store, aggregate.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) - == store.finalized_checkpoint.root` +- _[IGNORE]_ The block being voted for (`aggregate.data.beacon_block_root`) has + been seen (via gossip or non-gossip sources) (a client MAY queue aggregates + for processing once block is retrieved). +- _[REJECT]_ The block being voted for (`aggregate.data.beacon_block_root`) + passes validation. +- _[REJECT]_ The aggregate attestation's target block is an ancestor of the + block named in the LMD vote -- i.e. + `get_checkpoint_block(store, aggregate.data.beacon_block_root, aggregate.data.target.epoch) == aggregate.data.target.root` +- _[IGNORE]_ The current `finalized_checkpoint` is an ancestor of the `block` + defined by `aggregate.data.beacon_block_root` -- i.e. + `get_checkpoint_block(store, aggregate.data.beacon_block_root, finalized_checkpoint.epoch) == store.finalized_checkpoint.root` +###### `voluntary_exit` -##### `voluntary_exit` +The `voluntary_exit` topic is used solely for propagating signed voluntary +validator exits to proposers on the network. Signed voluntary exits are sent in +their entirety. -The `voluntary_exit` topic is used solely for propagating signed voluntary validator exits to proposers on the network. -Signed voluntary exits are sent in their entirety. +The following validations MUST pass before forwarding the +`signed_voluntary_exit` on to the network. -The following validations MUST pass before forwarding the `signed_voluntary_exit` on to the network. -- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received - for the validator with index `signed_voluntary_exit.message.validator_index`. -- _[REJECT]_ All of the conditions within `process_voluntary_exit` pass validation. +- _[IGNORE]_ The voluntary exit is the first valid voluntary exit received for + the validator with index `signed_voluntary_exit.message.validator_index`. +- _[REJECT]_ All of the conditions within `process_voluntary_exit` pass + validation. -##### `proposer_slashing` +###### `proposer_slashing` -The `proposer_slashing` topic is used solely for propagating proposer slashings to proposers on the network. -Proposer slashings are sent in their entirety. +The `proposer_slashing` topic is used solely for propagating proposer slashings +to proposers on the network. Proposer slashings are sent in their entirety. + +The following validations MUST pass before forwarding the `proposer_slashing` on +to the network. -The following validations MUST pass before forwarding the `proposer_slashing` on to the network. - _[IGNORE]_ The proposer slashing is the first valid proposer slashing received - for the proposer with index `proposer_slashing.signed_header_1.message.proposer_index`. -- _[REJECT]_ All of the conditions within `process_proposer_slashing` pass validation. - -##### `attester_slashing` - -The `attester_slashing` topic is used solely for propagating attester slashings to proposers on the network. -Attester slashings are sent in their entirety. - -Clients who receive an attester slashing on this topic MUST validate the conditions within `process_attester_slashing` before forwarding it across the network. -- _[IGNORE]_ At least one index in the intersection of the attesting indices of each attestation - has not yet been seen in any prior `attester_slashing` - (i.e. `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, - verify if `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). -- _[REJECT]_ All of the conditions within `process_attester_slashing` pass validation. - -#### Attestation subnets - -Attestation subnets are used to propagate unaggregated attestations to subsections of the network. - -##### `beacon_attestation_{subnet_id}` - -The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated attestations -to the subnet `subnet_id` (typically beacon and persistent committees) to be aggregated before being gossiped to `beacon_aggregate_and_proof`. - -The following validations MUST pass before forwarding the `attestation` on the subnet. -- _[REJECT]_ The committee index is within the expected range -- i.e. `data.index < get_committee_count_per_slot(state, data.target.epoch)`. -- _[REJECT]_ The attestation is for the correct subnet -- - i.e. `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index) == subnet_id`, - where `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, - which may be pre-computed along with the committee information for the signature check. -- _[IGNORE]_ `attestation.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots - (within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` - (a client MAY queue future attestations for processing at the appropriate slot). -- _[REJECT]_ The attestation's epoch matches its target -- i.e. `attestation.data.target.epoch == - compute_epoch_at_slot(attestation.data.slot)` -- _[REJECT]_ The attestation is unaggregated -- - that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set). + for the proposer with index + `proposer_slashing.signed_header_1.message.proposer_index`. +- _[REJECT]_ All of the conditions within `process_proposer_slashing` pass + validation. + +###### `attester_slashing` + +The `attester_slashing` topic is used solely for propagating attester slashings +to proposers on the network. Attester slashings are sent in their entirety. + +Clients who receive an attester slashing on this topic MUST validate the +conditions within `process_attester_slashing` before forwarding it across the +network. + +- _[IGNORE]_ At least one index in the intersection of the attesting indices of + each attestation has not yet been seen in any prior `attester_slashing` (i.e. + `attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)`, + verify if + `any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))`). +- _[REJECT]_ All of the conditions within `process_attester_slashing` pass + validation. + +##### Attestation subnets + +Attestation subnets are used to propagate unaggregated attestations to +subsections of the network. + +###### `beacon_attestation_{subnet_id}` + +The `beacon_attestation_{subnet_id}` topics are used to propagate unaggregated +attestations to the subnet `subnet_id` (typically beacon and persistent +committees) to be aggregated before being gossiped to +`beacon_aggregate_and_proof`. + +We define the following variables for convenience: + +- `index = attestation.data.index` +- `aggregation_bits = attestation.aggregation_bits` + +The following validations MUST pass before forwarding the `attestation` on the +subnet. + +- _[REJECT]_ The committee index is within the expected range -- i.e. + `index < get_committee_count_per_slot(state, attestation.data.target.epoch)`. +- _[REJECT]_ The attestation is for the correct subnet -- i.e. + `compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, index) == subnet_id`, + where + `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`, + which may be pre-computed along with the committee information for the + signature check. +- _[IGNORE]_ `attestation.data.slot` is within the last + `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (within a + `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. + `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot` + (a client MAY queue future attestations for processing at the appropriate + slot). +- _[REJECT]_ The attestation's epoch matches its target -- i.e. + `attestation.data.target.epoch == compute_epoch_at_slot(attestation.data.slot)` +- _[REJECT]_ The attestation is unaggregated -- that is, it has exactly one + participating validator (`len([bit 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(attestation.aggregation_bits) == len(get_beacon_committee(state, data.slot, data.index))`. -- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet - that has an identical `attestation.data.target.epoch` and participating validator index. + `len(aggregation_bits) == len(get_beacon_committee(state, attestation.data.slot, index))`. +- _[IGNORE]_ There has been no other valid attestation seen on an attestation + subnet that has an identical `attestation.data.target.epoch` and participating + validator index. - _[REJECT]_ The signature of `attestation` is valid. -- _[IGNORE]_ The block being voted for (`attestation.data.beacon_block_root`) has been seen - (via both gossip and non-gossip sources) - (a client MAY queue attestations for processing once block is retrieved). -- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation. -- _[REJECT]_ The attestation's target block is an ancestor of the block named in the LMD vote -- i.e. - `get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(attestation.data.target.epoch)) == attestation.data.target.root` -- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e. - `get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) - == store.finalized_checkpoint.root` +- _[IGNORE]_ The block being voted for (`attestation.data.beacon_block_root`) + has been seen (via gossip or non-gossip sources) (a client MAY queue + attestations for processing once block is retrieved). +- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) + passes validation. +- _[REJECT]_ The attestation's target block is an ancestor of the block named in + the LMD vote -- i.e. + `get_checkpoint_block(store, attestation.data.beacon_block_root, attestation.data.target.epoch) == attestation.data.target.root` +- _[IGNORE]_ The current `finalized_checkpoint` is an ancestor of the `block` + defined by `attestation.data.beacon_block_root` -- i.e. + `get_checkpoint_block(store, attestation.data.beacon_block_root, store.finalized_checkpoint.epoch) == store.finalized_checkpoint.root` + +##### Attestations and Aggregation + +Attestation broadcasting is grouped into subnets defined by a topic. The number +of subnets is defined via `ATTESTATION_SUBNET_COUNT`. The correct subnet for an +attestation can be calculated with `compute_subnet_for_attestation`. +`beacon_attestation_{subnet_id}` topics, are rotated through throughout the +epoch in a similar fashion to rotating through shards in committees (future +beacon chain upgrade). The subnets are rotated through with +`committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` +subnets per slot. +Unaggregated attestations are sent as `Attestation`s to the subnet topic, +`beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` +as `Attestation`s. +Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as +`AggregateAndProof`s. -#### Attestations and Aggregation +#### Encodings -Attestation broadcasting is grouped into subnets defined by a topic. -The number of subnets is defined via `ATTESTATION_SUBNET_COUNT`. -The correct subnet for an attestation can be calculated with `compute_subnet_for_attestation`. -`beacon_attestation_{subnet_id}` topics, are rotated through throughout the epoch in a similar fashion to rotating through shards in committees (future beacon chain upgrade). -The subnets are rotated through with `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)` subnets per slot. +Topics are post-fixed with an encoding. Encodings define how the payload of a +gossipsub message is encoded. -Unaggregated attestations are sent as `Attestation`s to the subnet topic, -`beacon_attestation_{compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)}` as `Attestation`s. +- `ssz_snappy` - All objects are SSZ-encoded and then compressed with + [Snappy](https://github.com/google/snappy) block compression. Example: The + beacon aggregate attestation topic string is + `/eth2/446a7232/beacon_aggregate_and_proof/ssz_snappy`, the fork digest is + `446a7232` and the data field of a gossipsub message is an `AggregateAndProof` + that has been SSZ-encoded and then compressed with Snappy. -Aggregated attestations are sent to the `beacon_aggregate_and_proof` topic as `AggregateAndProof`s. +Snappy has two formats: "block" and "frames" (streaming). Gossip messages remain +relatively small (100s of bytes to 100s of kilobytes) so +[basic snappy block compression](https://github.com/google/snappy/blob/master/format_description.txt) +is used to avoid the additional overhead associated with snappy frames. -### Encodings +Implementations MUST use a single encoding for gossip. Changing an encoding will +require coordination between participating implementations. -Topics are post-fixed with an encoding. Encodings define how the payload of a gossipsub message is encoded. +#### Gossipsub size limits -- `ssz_snappy` - All objects are SSZ-encoded and then compressed with [Snappy](https://github.com/google/snappy) block compression. - Example: The beacon aggregate attestation topic string is `/eth2/446a7232/beacon_aggregate_and_proof/ssz_snappy`, - the fork digest is `446a7232` and the data field of a gossipsub message is an `AggregateAndProof` - that has been SSZ-encoded and then compressed with Snappy. +Size limits are placed both on the +[`RPCMsg`](https://github.com/libp2p/specs/blob/b5f7fce29b32d4c7d0efe37b019936a11e5db872/pubsub/README.md#the-rpc) +frame as well as the encoded payload in each +[`Message`](https://github.com/libp2p/specs/blob/b5f7fce29b32d4c7d0efe37b019936a11e5db872/pubsub/README.md#the-message). -Snappy has two formats: "block" and "frames" (streaming). -Gossip messages remain relatively small (100s of bytes to 100s of kilobytes) -so [basic snappy block compression](https://github.com/google/snappy/blob/master/format_description.txt) is used to avoid the additional overhead associated with snappy frames. +Clients MUST reject and MUST NOT emit or propagate messages whose size exceed +the following limits: -Implementations MUST use a single encoding for gossip. -Changing an encoding will require coordination between participating implementations. +- The size of the encoded `RPCMsg` (including control messages, framing, topics, + etc) must not exceed `max_message_size()`. +- The size of the compressed payload in the `Message.data` field must not exceed + `max_compressed_len(MAX_PAYLOAD_SIZE)`. +- The size of the uncompressed payload must not exceed `MAX_PAYLOAD_SIZE` or the + [type-specific SSZ bound](#what-are-ssz-type-size-bounds), whichever is lower. -## The Req/Resp domain +### The Req/Resp domain -### Protocol identification +#### Protocol identification -Each message type is segregated into its own libp2p protocol ID, which is a case-sensitive UTF-8 string of the form: +Each message type is segregated into its own libp2p protocol ID, which is a +case-sensitive UTF-8 string of the form: ``` /ProtocolPrefix/MessageName/SchemaVersion/Encoding @@ -471,24 +663,28 @@ Each message type is segregated into its own libp2p protocol ID, which is a case With: -- `ProtocolPrefix` - messages are grouped into families identified by a shared libp2p protocol name prefix. - In this case, we use `/eth2/beacon_chain/req`. -- `MessageName` - each request is identified by a name consisting of English alphabet, digits and underscores (`_`). -- `SchemaVersion` - an ordinal version number (e.g. 1, 2, 3…). - Each schema is versioned to facilitate backward and forward-compatibility when possible. +- `ProtocolPrefix` - messages are grouped into families identified by a shared + libp2p protocol name prefix. In this case, we use `/eth2/beacon_chain/req`. +- `MessageName` - each request is identified by a name consisting of English + alphabet, digits and underscores (`_`). +- `SchemaVersion` - an ordinal version number (e.g. 1, 2, 3…). Each schema is + versioned to facilitate backward and forward-compatibility when possible. - `Encoding` - while the schema defines the data types in more abstract terms, - the encoding strategy describes a specific representation of bytes that will be transmitted over the wire. - See the [Encodings](#Encoding-strategies) section for further details. + the encoding strategy describes a specific representation of bytes that will + be transmitted over the wire. See the [Encodings](#Encoding-strategies) + section for further details. -This protocol segregation allows libp2p `multistream-select 1.0` / `multiselect 2.0` -to handle the request type, version, and encoding negotiation before establishing the underlying streams. +This protocol segregation allows libp2p `multistream-select 1.0` / +`multiselect 2.0` to handle the request type, version, and encoding negotiation +before establishing the underlying streams. -### Req/Resp interaction +#### Req/Resp interaction -We use ONE stream PER request/response interaction. -Streams are closed when the interaction finishes, whether in success or in error. +We use ONE stream PER request/response interaction. Streams are closed when the +interaction finishes, whether in success or in error. -Request/response messages MUST adhere to the encoding specified in the protocol name and follow this structure (relaxed BNF grammar): +Request/response messages MUST adhere to the encoding specified in the protocol +name and follow this structure (relaxed BNF grammar): ``` request ::= | @@ -497,83 +693,110 @@ response_chunk ::= | | result ::= “0” | “1” | “2” | [“128” ... ”255”] ``` -The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. -Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; -however, certain encodings like SSZ do, for added security. +The encoding-dependent header may carry metadata or assertions such as the +encoded payload length, for integrity and attack proofing purposes. Because +req/resp streams are single-use and stream closures implicitly delimit the +boundaries, it is not strictly necessary to length-prefix payloads; however, +certain encodings like SSZ do, for added security. -A `response` is formed by zero or more `response_chunk`s. -Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. -All other response types (non-Lists) send a single `response_chunk`. +A `response` is formed by zero or more `response_chunk`s. Responses that consist +of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list +item as a `response_chunk`. All other response types (non-Lists) send a single +`response_chunk`. -For both `request`s and `response`s, the `encoding-dependent-header` MUST be valid, -and the `encoded-payload` must be valid within the constraints of the `encoding-dependent-header`. -This includes type-specific bounds on payload size for some encoding strategies. -Regardless of these type specific bounds, a global maximum uncompressed byte size of `MAX_CHUNK_SIZE` MUST be applied to all method response chunks. +For both `request`s and `response`s, the `encoding-dependent-header` MUST be +valid, and the `encoded-payload` must be valid within the constraints of the +`encoding-dependent-header`. This includes type-specific bounds on payload size +for some encoding strategies. Regardless of these type specific bounds, a global +maximum uncompressed byte size of `MAX_PAYLOAD_SIZE` MUST be applied to all +method response chunks. -Clients MUST ensure that lengths are within these bounds; if not, they SHOULD reset the stream immediately. -Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. +Clients MUST ensure that lengths are within these bounds; if not, they SHOULD +reset the stream immediately. Clients tracking peer reputation MAY decrement the +score of the misbehaving peer under this circumstance. -#### Requesting side +##### Requesting side -Once a new stream with the protocol ID for the request type has been negotiated, the full request message SHOULD be sent immediately. -The request MUST be encoded according to the encoding strategy. +Once a new stream with the protocol ID for the request type has been negotiated, +the full request message SHOULD be sent immediately. The request MUST be encoded +according to the encoding strategy. -The requester MUST close the write side of the stream once it finishes writing the request message. -At this point, the stream will be half-closed. +The requester MUST close the write side of the stream once it finishes writing +the request message. At this point, the stream will be half-closed. -The requester MUST wait a maximum of `TTFB_TIMEOUT` for the first response byte to arrive (time to first byte—or TTFB—timeout). -On that happening, the requester allows a further `RESP_TIMEOUT` for each subsequent `response_chunk` received. +The requester MUST NOT make more than `MAX_CONCURRENT_REQUESTS` concurrent +requests with the same protocol ID. -If any of these timeouts fire, the requester SHOULD reset the stream and deem the req/resp operation to have failed. +If a timeout occurs or the response is no longer relevant, the requester SHOULD +reset the stream. A requester SHOULD read from the stream until either: -1. An error result is received in one of the chunks (the error payload MAY be read before stopping). + +1. An error result is received in one of the chunks (the error payload MAY be + read before stopping). 2. The responder closes the stream. 3. Any part of the `response_chunk` fails validation. 4. The maximum number of requested chunks are read. -For requests consisting of a single valid `response_chunk`, -the requester SHOULD read the chunk fully, as defined by the `encoding-dependent-header`, before closing the stream. +For requests consisting of a single valid `response_chunk`, the requester SHOULD +read the chunk fully, as defined by the `encoding-dependent-header`, before +closing the stream. -#### Responding side +##### Responding side Once a new stream with the protocol ID for the request type has been negotiated, -the responder SHOULD process the incoming request and MUST validate it before processing it. -Request processing and validation MUST be done according to the encoding strategy, until EOF (denoting stream half-closure by the requester). +the responder SHOULD process the incoming request and MUST validate it before +processing it. Request processing and validation MUST be done according to the +encoding strategy, until EOF (denoting stream half-closure by the requester). The responder MUST: 1. Use the encoding strategy to read the optional header. -2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). - Should this not be the case, it should be treated as a failure. +2. If there are any length assertions for length `N`, it should read exactly `N` + bytes from the stream, at which point an EOF should arise (no more bytes). + Should this not be the case, it should be treated as a failure. 3. Deserialize the expected type, and process the request. -4. Write the response which may consist of zero or more `response_chunk`s (result, optional header, payload). -5. Close their write side of the stream. At this point, the stream will be fully closed. - -If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. -Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. - -The entire request should be read in no more than `RESP_TIMEOUT`. -Upon a timeout, the responder SHOULD reset the stream. - -The responder SHOULD send a `response_chunk` promptly. -Chunks start with a **single-byte** response code which determines the contents of the `response_chunk` (`result` particle in the BNF grammar above). -For multiple chunks, only the last chunk is allowed to have a non-zero error code (i.e. The chunk stream is terminated once an error occurs). - -The response code can have one of the following values, encoded as a single unsigned byte: - -- 0: **Success** -- a normal response follows, with contents matching the expected message schema and encoding specified in the request. -- 1: **InvalidRequest** -- the contents of the request are semantically invalid, or the payload is malformed, or could not be understood. - The response payload adheres to the `ErrorMessage` schema (described below). -- 2: **ServerError** -- the responder encountered an error while processing the request. - The response payload adheres to the `ErrorMessage` schema (described below). -- 3: **ResourceUnavailable** -- the responder does not have requested resource. +4. Write the response which may consist of zero or more `response_chunk`s + (result, optional header, payload). +5. Close their write side of the stream. At this point, the stream will be fully + closed. + +If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, +the responder MUST respond in error. Clients tracking peer reputation MAY record +such failures, as well as unexpected events, e.g. early stream resets. + +The responder MAY rate-limit chunks by withholding each chunk until capacity is +available. The responder MUST NOT respond with an error or close the stream when +rate limiting. + +When rate limiting, the responder MUST send each `response_chunk` in full +promptly but may introduce delays between each chunk. + +Chunks start with a **single-byte** response code which determines the contents +of the `response_chunk` (`result` particle in the BNF grammar above). For +multiple chunks, only the last chunk is allowed to have a non-zero error code +(i.e. The chunk stream is terminated once an error occurs). + +The response code can have one of the following values, encoded as a single +unsigned byte: + +- 0: **Success** -- a normal response follows, with contents matching the + expected message schema and encoding specified in the request. +- 1: **InvalidRequest** -- the contents of the request are semantically invalid, + or the payload is malformed, or could not be understood. The response payload + adheres to the `ErrorMessage` schema (described below). +- 2: **ServerError** -- the responder encountered an error while processing the + request. The response payload adheres to the `ErrorMessage` schema (described + below). +- 3: **ResourceUnavailable** -- the responder does not have requested resource. The response payload adheres to the `ErrorMessage` schema (described below). - *Note*: This response code is only valid as a response to `BlocksByRange`. + *Note*: This response code is only valid as a response where specified. -Clients MAY use response codes above `128` to indicate alternative, erroneous request-specific responses. +Clients MAY use response codes above `128` to indicate alternative, erroneous +request-specific responses. -The range `[3, 127]` is RESERVED for future usages, and should be treated as error if not recognized expressly. +The range `[4, 127]` is RESERVED for future usages, and should be treated as +error if not recognized expressly. The `ErrorMessage` schema is: @@ -583,74 +806,105 @@ The `ErrorMessage` schema is: ) ``` -*Note*: By convention, the `error_message` is a sequence of bytes that MAY be interpreted as a UTF-8 string (for debugging purposes). -Clients MUST treat as valid any byte sequences. +*Note*: By convention, the `error_message` is a sequence of bytes that MAY be +interpreted as a UTF-8 string (for debugging purposes). Clients MUST treat as +valid any byte sequences. -### Encoding strategies +The responder MAY penalise peers that concurrently open more than +`MAX_CONCURRENT_REQUESTS` streams for the same request type, for the protocol +IDs defined in this specification. -The token of the negotiated protocol ID specifies the type of encoding to be used for the req/resp interaction. -Only one value is possible at this time: +#### Encoding strategies -- `ssz_snappy`: The contents are first [SSZ-encoded](../../ssz/simple-serialize.md) - and then compressed with [Snappy](https://github.com/google/snappy) frames compression. - For objects containing a single field, only the field is SSZ-encoded not a container with a single field. - For example, the `BeaconBlocksByRoot` request is an SSZ-encoded list of `Root`'s. - This encoding type MUST be supported by all clients. +The token of the negotiated protocol ID specifies the type of encoding to be +used for the req/resp interaction. Only one value is possible at this time: -#### SSZ-snappy encoding strategy +- `ssz_snappy`: The contents are first + [SSZ-encoded](../../ssz/simple-serialize.md) and then compressed with + [Snappy](https://github.com/google/snappy) frames compression. For objects + containing a single field, only the field is SSZ-encoded not a container with + a single field. For example, the `BeaconBlocksByRoot` request is an + SSZ-encoded list of `Root`'s. This encoding type MUST be supported by all + clients. -The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) outlines how objects are SSZ-encoded. +##### SSZ-snappy encoding strategy -To achieve snappy encoding on top of SSZ, we feed the serialized form of the object to the Snappy compressor on encoding. -The inverse happens on decoding. +The [SimpleSerialize (SSZ) specification](../../ssz/simple-serialize.md) +outlines how objects are SSZ-encoded. -Snappy has two formats: "block" and "frames" (streaming). -To support large requests and response chunks, snappy-framing is used. +To achieve snappy encoding on top of SSZ, we feed the serialized form of the +object to the Snappy compressor on encoding. The inverse happens on decoding. -Since snappy frame contents [have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104) -and frame headers are just `identifier (1) + checksum (4)` bytes, the expected buffering of a single frame is acceptable. +Snappy has two formats: "block" and "frames" (streaming). To support large +requests and response chunks, snappy-framing is used. -**Encoding-dependent header:** Req/Resp protocols using the `ssz_snappy` encoding strategy MUST encode the length of the raw SSZ bytes, -encoded as an unsigned [protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). +Since snappy frame contents +[have a maximum size of `65536` bytes](https://github.com/google/snappy/blob/master/framing_format.txt#L104) +and frame headers are just `identifier (1) + checksum (4)` bytes, the expected +buffering of a single frame is acceptable. -*Writing*: By first computing and writing the SSZ byte length, the SSZ encoder can then directly write the chunk contents to the stream. -When Snappy is applied, it can be passed through a buffered Snappy writer to compress frame by frame. +**Encoding-dependent header:** Req/Resp protocols using the `ssz_snappy` +encoding strategy MUST encode the length of the raw SSZ bytes, encoded as an +unsigned +[protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints). -*Reading*: After reading the expected SSZ byte length, the SSZ decoder can directly read the contents from the stream. -When snappy is applied, it can be passed through a buffered Snappy reader to decompress frame by frame. +*Writing*: By first computing and writing the SSZ byte length, the SSZ encoder +can then directly write the chunk contents to the stream. When Snappy is +applied, it can be passed through a buffered Snappy writer to compress frame by +frame. + +*Reading*: After reading the expected SSZ byte length, the SSZ decoder can +directly read the contents from the stream. When snappy is applied, it can be +passed through a buffered Snappy reader to decompress frame by frame. Before reading the payload, the header MUST be validated: -- The unsigned protobuf varint used for the length-prefix MUST not be longer than 10 bytes, which is sufficient for any `uint64`. -- The length-prefix is within the expected [size bounds derived from the payload SSZ type](#what-are-ssz-type-size-bounds). -After reading a valid header, the payload MAY be read, while maintaining the size constraints from the header. +- The length-prefix MUST be encoded as an unsigned protobuf varint. It SHOULD be + minimally encoded (i.e., without any redundant bytes) and MUST not exceed 10 + bytes in length, which is sufficient to represent any `uint64` value. The + length-prefix MUST be decoded into a type which supports the full range of + `uint64` values. +- The length-prefix is within the expected + [size bounds derived from the payload SSZ type](#what-are-ssz-type-size-bounds) + or `MAX_PAYLOAD_SIZE`, whichever is smaller. + +After reading a valid header, the payload MAY be read, while maintaining the +size constraints from the header. -A reader SHOULD NOT read more than `max_encoded_len(n)` bytes after reading the SSZ length-prefix `n` from the header. -- For `ssz_snappy` this is: `32 + n + n // 6`. - This is considered the [worst-case compression result](https://github.com/google/snappy/blob/537f4ad6240e586970fe554614542e9717df7902/snappy.cc#L98) by Snappy. +A reader MUST NOT read more than `max_compressed_len(n)` bytes after reading the +SSZ length-prefix `n` from the header. -A reader SHOULD consider the following cases as invalid input: -- Any remaining bytes, after having read the `n` SSZ bytes. An EOF is expected if more bytes are read than required. -- An early EOF, before fully reading the declared length-prefix worth of SSZ bytes. +A reader MUST consider the following cases as invalid input: + +- Any remaining bytes, after having read the `n` SSZ bytes. An EOF is expected + if more bytes are read than required. +- An early EOF, before fully reading the declared length-prefix worth of SSZ + bytes. In case of an invalid input (header or payload), a reader MUST: -- From requests: send back an error message, response code `InvalidRequest`. The request itself is ignored. -- From responses: ignore the response, the response MUST be considered bad server behavior. -All messages that contain only a single field MUST be encoded directly as the type of that field and MUST NOT be encoded as an SSZ container. +- From requests: send back an error message, response code `InvalidRequest`. The + request itself is ignored. +- From responses: ignore the response, the response MUST be considered bad + server behavior. + +All messages that contain only a single field MUST be encoded directly as the +type of that field and MUST NOT be encoded as an SSZ container. -Responses that are SSZ-lists (for example `List[SignedBeaconBlock, ...]`) send their -constituents individually as `response_chunk`s. For example, the -`List[SignedBeaconBlock, ...]` response type sends zero or more `response_chunk`s. -Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. +Responses that are SSZ-lists (for example `List[SignedBeaconBlock, ...]`) send +their constituents individually as `response_chunk`s. For example, the +`List[SignedBeaconBlock, ...]` response type sends zero or more +`response_chunk`s. Each _successful_ `response_chunk` contains a single +`SignedBeaconBlock` payload. -### Messages +#### Messages -#### Status +##### Status v1 -**Protocol ID:** ``/eth2/beacon_chain/req/status/1/`` +**Protocol ID:** `/eth2/beacon_chain/req/status/1/` Request, Response Content: + ``` ( fork_digest: ForkDigest @@ -660,16 +914,23 @@ Request, Response Content: head_slot: Slot ) ``` + The fields are, as seen by the client at the time of sending the message: -- `fork_digest`: The node's `ForkDigest` (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where - - `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time - (not necessarily the epoch to which the node is sync) - - `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` -- `finalized_root`: `state.finalized_checkpoint.root` for the state corresponding to the head block - (Note this defaults to `Root(b'\x00' * 32)` for the genesis finalized checkpoint). -- `finalized_epoch`: `state.finalized_checkpoint.epoch` for the state corresponding to the head block. -- `head_root`: The `hash_tree_root` root of the current head block (`BeaconBlock`). +- `fork_digest`: The node's `ForkDigest` + (`compute_fork_digest(current_fork_version, genesis_validators_root)`) where + - `current_fork_version` is the fork version at the node's current epoch + defined by the wall-clock time (not necessarily the epoch to which the node + is sync) + - `genesis_validators_root` is the static `Root` found in + `state.genesis_validators_root` +- `finalized_root`: `store.finalized_checkpoint.root` according to + [fork choice](./fork-choice.md). (Note this defaults to `Root(b'\x00' * 32)` + for the genesis finalized checkpoint). +- `finalized_epoch`: `store.finalized_checkpoint.epoch` according to + [fork choice](./fork-choice.md). +- `head_root`: The `hash_tree_root` root of the current head block + (`BeaconBlock`). - `head_slot`: The slot of the block corresponding to the `head_root`. The dialing client MUST send a `Status` request upon connection. @@ -678,37 +939,47 @@ The request/response MUST be encoded as an SSZ-container. The response MUST consist of a single `response_chunk`. -Clients SHOULD immediately disconnect from one another following the handshake above under the following conditions: +Clients SHOULD immediately disconnect from one another following the handshake +above under the following conditions: -1. If `fork_digest` does not match the node's local `fork_digest`, since the client’s chain is on another fork. -2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the client's chain at the expected epoch. - For example, if Peer 1 sends (root, epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, - then Peer 1 would disconnect because it knows that their chains are irreparably disjoint. +1. If `fork_digest` does not match the node's local `fork_digest`, since the + client’s chain is on another fork. +2. If the (`finalized_root`, `finalized_epoch`) shared by the peer is not in the + client's chain at the expected epoch. For example, if Peer 1 sends (root, + epoch) of (A, 5) and Peer 2 sends (B, 3) but Peer 1 has root C at epoch 3, + then Peer 1 would disconnect because it knows that their chains are + irreparably disjoint. -Once the handshake completes, the client with the lower `finalized_epoch` or `head_slot` (if the clients have equal `finalized_epoch`s) -SHOULD request beacon blocks from its counterparty via the `BeaconBlocksByRange` request. +Once the handshake completes, the client with the lower `finalized_epoch` or +`head_slot` (if the clients have equal `finalized_epoch`s) SHOULD request beacon +blocks from its counterparty via the `BeaconBlocksByRange` request. -*Note*: Under abnormal network condition or after some rounds of `BeaconBlocksByRange` requests, -the client might need to send `Status` request again to learn if the peer has a higher head. -Implementers are free to implement such behavior in their own way. +*Note*: Under abnormal network condition or after some rounds of +`BeaconBlocksByRange` requests, the client might need to send `Status` request +again to learn if the peer has a higher head. Implementers are free to implement +such behavior in their own way. -#### Goodbye +##### Goodbye v1 -**Protocol ID:** ``/eth2/beacon_chain/req/goodbye/1/`` +**Protocol ID:** `/eth2/beacon_chain/req/goodbye/1/` Request, Response Content: + ``` ( uint64 ) ``` -Client MAY send goodbye messages upon disconnection. The reason field MAY be one of the following values: + +Client MAY send goodbye messages upon disconnection. The reason field MAY be one +of the following values: - 1: Client shut down. - 2: Irrelevant network. - 3: Fault/error. -Clients MAY use reason codes above `128` to indicate alternative, erroneous request-specific responses. +Clients MAY use reason codes above `128` to indicate alternative, erroneous +request-specific responses. The range `[4, 127]` is RESERVED for future usage. @@ -716,63 +987,72 @@ The request/response MUST be encoded as a single SSZ-field. The response MUST consist of a single `response_chunk`. -#### BeaconBlocksByRange +##### BeaconBlocksByRange v1 **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_range/1/` Request Content: + ``` ( start_slot: Slot count: uint64 - step: uint64 + step: uint64 # Deprecated, must be set to 1 ) ``` Response Content: + ``` ( List[SignedBeaconBlock, MAX_REQUEST_BLOCKS] ) ``` -Requests beacon blocks in the slot range `[start_slot, start_slot + count * step)`, leading up to the current head block as selected by fork choice. -`step` defines the slot increment between blocks. -For example, requesting blocks starting at `start_slot` 2 with a step value of 2 would return the blocks at slots [2, 4, 6, …]. -In cases where a slot is empty for a given slot number, no block is returned. -For example, if slot 4 were empty in the previous example, the returned array would contain [2, 6, …]. -A request MUST NOT have a 0 slot increment, i.e. `step >= 1`. +Requests beacon blocks in the slot range `[start_slot, start_slot + count)`, +leading up to the current head block as selected by fork choice. For example, +requesting blocks starting at `start_slot=2` and `count=4` would return the +blocks at slots `[2, 3, 4, 5]`. In cases where a slot is empty for a given slot +number, no block is returned. For example, if slot 4 were empty in the previous +example, the returned array would contain `[2, 3, 5]`. + +`step` is deprecated and must be set to 1. Clients may respond with a single +block if a larger step is returned during the deprecation transition period. + +`/eth2/beacon_chain/req/beacon_blocks_by_range/1/` is deprecated. Clients MAY +respond with an empty list during the deprecation transition period. `BeaconBlocksByRange` is primarily used to sync historical blocks. The request MUST be encoded as an SSZ-container. -The response MUST consist of zero or more `response_chunk`. -Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. +The response MUST consist of zero or more `response_chunk`. Each _successful_ +`response_chunk` MUST contain a single `SignedBeaconBlock` payload. Clients MUST keep a record of signed blocks seen on the epoch range `[max(GENESIS_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]` -where `current_epoch` is defined by the current wall-clock time, -and clients MUST support serving requests of blocks on this range. +where `current_epoch` is defined by the current wall-clock time, and clients +MUST support serving requests of blocks on this range. -Peers that are unable to reply to block requests within the `MIN_EPOCHS_FOR_BLOCK_REQUESTS` -epoch range SHOULD respond with error code `3: ResourceUnavailable`. -Such peers that are unable to successfully reply to this range of requests MAY get descored -or disconnected at any time. +Peers that are unable to reply to block requests within the +`MIN_EPOCHS_FOR_BLOCK_REQUESTS` epoch range SHOULD respond with error code +`3: ResourceUnavailable`. Such peers that are unable to successfully reply to +this range of requests MAY get descored or disconnected at any time. -*Note*: The above requirement implies that nodes that start from a recent weak subjectivity checkpoint -MUST backfill the local block database to at least epoch `current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS` -to be fully compliant with `BlocksByRange` requests. To safely perform such a -backfill of blocks to the recent state, the node MUST validate both (1) the -proposer signatures and (2) that the blocks form a valid chain up to the most -recent block referenced in the weak subjectivity state. +*Note*: The above requirement implies that nodes that start from a recent weak +subjectivity checkpoint MUST backfill the local block database to at least epoch +`current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS` to be fully compliant with +`BlocksByRange` requests. To safely perform such a backfill of blocks to the +recent state, the node MUST validate both (1) the proposer signatures and (2) +that the blocks form a valid chain up to the most recent block referenced in the +weak subjectivity state. -*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can begin -participating in the networking immediately, other peers MAY -disconnect and/or temporarily ban such an un-synced or semi-synced client. +*Note*: Although clients that bootstrap from a weak subjectivity checkpoint can +begin participating in the networking immediately, other peers MAY disconnect +and/or temporarily ban such an un-synced or semi-synced client. -Clients MUST respond with at least the first block that exists in the range, if they have it, -and no more than `MAX_REQUEST_BLOCKS` blocks. +Clients MUST respond with at least the first block that exists in the range, if +they have it, and no more than `MAX_REQUEST_BLOCKS` blocks. The following blocks, where they exist, MUST be sent in consecutive order. @@ -780,18 +1060,20 @@ Clients MAY limit the number of blocks in the response. The response MUST contain no more than `count` blocks. -Clients MUST respond with blocks from their view of the current fork choice --- that is, blocks from the single chain defined by the current head. -Of note, blocks from slots before the finalization MUST lead to the finalized block reported in the `Status` handshake. +Clients MUST respond with blocks from their view of the current fork choice -- +that is, blocks from the single chain defined by the current head. Of note, +blocks from slots before the finalization MUST lead to the finalized block +reported in the `Status` handshake. -Clients MUST respond with blocks that are consistent from a single chain within the context of the request. -This applies to any `step` value. -In particular when `step == 1`, each `parent_root` MUST match the `hash_tree_root` of the preceding block. +Clients MUST respond with blocks that are consistent from a single chain within +the context of the request. This applies to any `step` value. In particular when +`step == 1`, each `parent_root` MUST match the `hash_tree_root` of the preceding +block. -After the initial block, clients MAY stop in the process of responding -if their fork choice changes the view of the chain in the context of the request. +After the initial block, clients MAY stop in the process of responding if their +fork choice changes the view of the chain in the context of the request. -#### BeaconBlocksByRoot +##### BeaconBlocksByRoot v1 **Protocol ID:** `/eth2/beacon_chain/req/beacon_blocks_by_root/1/` @@ -812,24 +1094,33 @@ Response Content: ``` Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). -The response is a list of `SignedBeaconBlock` whose length is less than or equal to the number of requested blocks. -It may be less in the case that the responding peer is missing blocks. +The response is a list of `SignedBeaconBlock` whose length is less than or equal +to the number of requested blocks. It may be less in the case that the +responding peer is missing blocks. No more than `MAX_REQUEST_BLOCKS` may be requested at a time. -`BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when receiving a block or attestation whose parent is unknown). +`BeaconBlocksByRoot` is primarily used to recover recent blocks (e.g. when +receiving a block or attestation whose parent is unknown). The request MUST be encoded as an SSZ-field. -The response MUST consist of zero or more `response_chunk`. -Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. +The response MUST consist of zero or more `response_chunk`. Each _successful_ +`response_chunk` MUST contain a single `SignedBeaconBlock` payload. Clients MUST support requesting blocks since the latest finalized epoch. -Clients MUST respond with at least one block, if they have it. -Clients MAY limit the number of blocks in the response. +Clients MUST respond with at least one block, if they have it. Clients MAY limit +the number of blocks in the response. + +Clients MAY include a block in the response as soon as it passes the gossip +validation rules. Clients SHOULD NOT respond with blocks that fail the beacon +chain state transition. + +`/eth2/beacon_chain/req/beacon_blocks_by_root/1/` is deprecated. Clients MAY +respond with an empty list during the deprecation transition period. -#### Ping +##### Ping v1 **Protocol ID:** `/eth2/beacon_chain/req/ping/1/` @@ -850,18 +1141,20 @@ Response Content: ``` Sent intermittently, the `Ping` protocol checks liveness of connected peers. -Peers request and respond with their local metadata sequence number (`MetaData.seq_number`). +Peers request and respond with their local metadata sequence number +(`MetaData.seq_number`). -If the peer does not respond to the `Ping` request, the client MAY disconnect from the peer. +If the peer does not respond to the `Ping` request, the client MAY disconnect +from the peer. -A client can then determine if their local record of a peer's MetaData is up to date -and MAY request an updated version via the `MetaData` RPC method if not. +A client can then determine if their local record of a peer's MetaData is up to +date and MAY request an updated version via the `MetaData` RPC method if not. The request MUST be encoded as an SSZ-field. The response MUST consist of a single `response_chunk`. -#### GetMetaData +##### GetMetaData v1 **Protocol ID:** `/eth2/beacon_chain/req/metadata/1/` @@ -875,715 +1168,1029 @@ Response Content: ) ``` -Requests the MetaData of a peer. -The request opens and negotiates the stream without sending any request content. -Once established the receiving peer responds with -it's local most up-to-date MetaData. +Requests the MetaData of a peer. The request opens and negotiates the stream +without sending any request content. Once established the receiving peer +responds with it's local most up-to-date MetaData. The response MUST be encoded as an SSZ-container. The response MUST consist of a single `response_chunk`. -## The discovery domain: discv5 +### The discovery domain: discv5 -Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) (Protocol version v5.1) is used for peer discovery. +Discovery Version 5 +([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) +(Protocol version v5.1) is used for peer discovery. -`discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. -`discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context. +`discv5` is a standalone protocol, running on UDP on a dedicated port, meant for +peer discovery only. `discv5` supports self-certified, flexible peer records +(ENRs) and topic-based advertisement, both of which are (or will be) +requirements in this context. -### Integration into libp2p stacks +#### Integration into libp2p stacks -`discv5` SHOULD be integrated into the client’s libp2p stack by implementing an adaptor -to make it conform to the [service discovery](https://github.com/libp2p/go-libp2p-core/blob/master/discovery/discovery.go) -and [peer routing](https://github.com/libp2p/go-libp2p-core/blob/master/routing/routing.go#L36-L44) abstractions and interfaces (go-libp2p links provided). +`discv5` SHOULD be integrated into the client’s libp2p stack by implementing an +adaptor to make it conform to the +[service discovery](https://github.com/libp2p/go-libp2p-core/blob/master/discovery/discovery.go) +and +[peer routing](https://github.com/libp2p/go-libp2p-core/blob/master/routing/routing.go#L36-L44) +abstractions and interfaces (go-libp2p links provided). -Inputs to operations include peer IDs (when locating a specific peer) or capabilities (when searching for peers with a specific capability), -and the outputs will be multiaddrs converted from the ENR records returned by the discv5 backend. +Inputs to operations include peer IDs (when locating a specific peer) or +capabilities (when searching for peers with a specific capability), and the +outputs will be multiaddrs converted from the ENR records returned by the discv5 +backend. -This integration enables the libp2p stack to subsequently form connections and streams with discovered peers. +This integration enables the libp2p stack to subsequently form connections and +streams with discovered peers. -### ENR structure +#### ENR structure -The Ethereum Node Record (ENR) for an Ethereum consensus client MUST contain the following entries -(exclusive of the sequence number and signature, which MUST be present in an ENR): +The Ethereum Node Record (ENR) for an Ethereum consensus client MUST contain the +following entries (exclusive of the sequence number and signature, which MUST be +present in an ENR): -- The compressed secp256k1 publickey, 33 bytes (`secp256k1` field). +- The compressed secp256k1 publickey, 33 bytes (`secp256k1` field). 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 UDP port (`udp` field) representing the local discv5 listening port. +- An IPv4 address (`ip` field) and/or IPv6 address (`ip6` field). +- An IPv4 TCP port (`tcp` field) representing the local libp2p TCP listening + port and/or the corresponding IPv6 port (`tcp6` field). +- An IPv4 QUIC port (`quic` field) representing the local libp2p QUIC (UDP) + listening port and/or the corresponding IPv6 port (`quic6` field). +- An IPv4 UDP port (`udp` field) representing the local discv5 listening port + and/or the corresponding IPv6 port (`udp6` field). -Specifications of these parameters can be found in the [ENR Specification](http://eips.ethereum.org/EIPS/eip-778). +Specifications of these parameters can be found in the +[ENR Specification](http://eips.ethereum.org/EIPS/eip-778). -#### Attestation subnet bitfield +##### Attestation subnet bitfield -The ENR `attnets` entry signifies the attestation subnet bitfield with the following form -to more easily discover peers participating in particular attestation gossip subnets. +The ENR `attnets` entry signifies the attestation subnet bitfield with the +following form to more easily discover peers participating in particular +attestation gossip subnets. -| Key | Value | -|:-------------|:-------------------------------------------------| -| `attnets` | SSZ `Bitvector[ATTESTATION_SUBNET_COUNT]` | +| Key | Value | +| :-------- | :---------------------------------------- | +| `attnets` | SSZ `Bitvector[ATTESTATION_SUBNET_COUNT]` | -If a node's `MetaData.attnets` has any non-zero bit, the ENR MUST include the `attnets` entry with the same value as `MetaData.attnets`. +If a node's `MetaData.attnets` has any non-zero bit, the ENR MUST include the +`attnets` entry with the same value as `MetaData.attnets`. -If a node's `MetaData.attnets` is composed of all zeros, the ENR MAY optionally include the `attnets` entry or leave it out entirely. +If a node's `MetaData.attnets` is composed of all zeros, the ENR MAY optionally +include the `attnets` entry or leave it out entirely. -#### `eth2` field +##### `eth2` field -ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current fork digest, next fork version, -and next fork epoch to ensure connections are made with peers on the intended Ethereum network. +ENRs MUST carry a generic `eth2` key with an 16-byte value of the node's current +fork digest, next fork version, and next fork epoch to ensure connections are +made with peers on the intended Ethereum network. -| Key | Value | -|:-------------|:--------------------| -| `eth2` | SSZ `ENRForkID` | +| Key | Value | +| :----- | :-------------- | +| `eth2` | SSZ `ENRForkID` | -Specifically, the value of the `eth2` key MUST be the following SSZ encoded object (`ENRForkID`) +Specifically, the value of the `eth2` key MUST be the following SSZ encoded +object (`ENRForkID`) ``` ( - fork_digest: ForkDigest - next_fork_version: Version - next_fork_epoch: Epoch + fork_digest: ForkDigest + next_fork_version: Version + next_fork_epoch: Epoch ) ``` where the fields of `ENRForkID` are defined as -* `fork_digest` is `compute_fork_digest(current_fork_version, genesis_validators_root)` where - * `current_fork_version` is the fork version at the node's current epoch defined by the wall-clock time - (not necessarily the epoch to which the node is sync) - * `genesis_validators_root` is the static `Root` found in `state.genesis_validators_root` -* `next_fork_version` is the fork version corresponding to the next planned hard fork at a future epoch. - If no future fork is planned, set `next_fork_version = current_fork_version` to signal this fact -* `next_fork_epoch` is the epoch at which the next fork is planned and the `current_fork_version` will be updated. - If no future fork is planned, set `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact - -*Note*: `fork_digest` is composed of values that are not known until the genesis block/state are available. -Due to this, clients SHOULD NOT form ENRs and begin peer discovery until genesis values are known. -One notable exception to this rule is the distribution of bootnode ENRs prior to genesis. -In this case, bootnode ENRs SHOULD be initially distributed with `eth2` field set as +- `fork_digest` is + `compute_fork_digest(current_fork_version, genesis_validators_root)` where + - `current_fork_version` is the fork version at the node's current epoch + defined by the wall-clock time (not necessarily the epoch to which the node + is sync) + - `genesis_validators_root` is the static `Root` found in + `state.genesis_validators_root` +- `next_fork_version` is the fork version corresponding to the next planned hard + fork at a future epoch. If no future fork is planned, set + `next_fork_version = current_fork_version` to signal this fact +- `next_fork_epoch` is the epoch at which the next fork is planned and the + `current_fork_version` will be updated. If no future fork is planned, set + `next_fork_epoch = FAR_FUTURE_EPOCH` to signal this fact + +*Note*: `fork_digest` is composed of values that are not known until the genesis +block/state are available. Due to this, clients SHOULD NOT form ENRs and begin +peer discovery until genesis values are known. One notable exception to this +rule is the distribution of bootnode ENRs prior to genesis. In this case, +bootnode ENRs SHOULD be initially distributed with `eth2` field set as `ENRForkID(fork_digest=compute_fork_digest(GENESIS_FORK_VERSION, b'\x00'*32), next_fork_version=GENESIS_FORK_VERSION, next_fork_epoch=FAR_FUTURE_EPOCH)`. -After genesis values are known, the bootnodes SHOULD update ENRs to participate in normal discovery operations. +After genesis values are known, the bootnodes SHOULD update ENRs to participate +in normal discovery operations. + +Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and +`next_fork_epoch` that match local values. + +Clients MAY connect to peers with the same `fork_digest` but a different +`next_fork_version`/`next_fork_epoch`. Unless `ENRForkID` is manually updated to +matching prior to the earlier `next_fork_epoch` of the two clients, these +connecting clients will be unable to successfully interact starting at the +earlier `next_fork_epoch`. + +### Attestation subnet subscription + +Because Phase 0 does not have shards and thus does not have Shard Committees, +there is no stable backbone to the attestation subnets +(`beacon_attestation_{subnet_id}`). To provide this stability, each beacon node +should: + +- Remain subscribed to `SUBNETS_PER_NODE` for `EPOCHS_PER_SUBNET_SUBSCRIPTION` + epochs. +- Maintain advertisement of the selected subnets in their node's ENR `attnets` + entry by setting the selected `subnet_id` bits to `True` (e.g. + `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets. +- Select these subnets based on their node-id as specified by the following + `compute_subscribed_subnets(node_id, epoch)` function. + +```python +def compute_subscribed_subnet(node_id: NodeID, epoch: Epoch, index: int) -> SubnetID: + node_id_prefix = node_id >> (NODE_ID_BITS - ATTESTATION_SUBNET_PREFIX_BITS) + node_offset = node_id % EPOCHS_PER_SUBNET_SUBSCRIPTION + permutation_seed = hash( + uint_to_bytes(uint64((epoch + node_offset) // EPOCHS_PER_SUBNET_SUBSCRIPTION)) + ) + permutated_prefix = compute_shuffled_index( + node_id_prefix, + 1 << ATTESTATION_SUBNET_PREFIX_BITS, + permutation_seed, + ) + return SubnetID((permutated_prefix + index) % ATTESTATION_SUBNET_COUNT) +``` -Clients SHOULD connect to peers with `fork_digest`, `next_fork_version`, and `next_fork_epoch` that match local values. +```python +def compute_subscribed_subnets(node_id: NodeID, epoch: Epoch) -> Sequence[SubnetID]: + return [compute_subscribed_subnet(node_id, epoch, index) for index in range(SUBNETS_PER_NODE)] +``` -Clients MAY connect to peers with the same `fork_digest` but a different `next_fork_version`/`next_fork_epoch`. -Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, -these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. +*Note*: When preparing for a hard fork, a node must select and subscribe to +subnets of the future fork versioning at least `EPOCHS_PER_SUBNET_SUBSCRIPTION` +epochs in advance of the fork. These new subnets for the fork are maintained in +addition to those for the current fork until the fork occurs. After the fork +occurs, let the subnets from the previous fork reach the end of life with no +replacements. -# Design decision rationale +## Design decision rationale -## Transport +### Transport -### Why are we defining specific transports? +#### Why are we defining specific transports? -libp2p peers can listen on multiple transports concurrently, and these can change over time. -Multiaddrs encode not only the address but also the transport to be used to dial. +libp2p peers can listen on multiple transports concurrently, and these can +change over time. Multiaddrs encode not only the address but also the transport +to be used to dial. -Due to this dynamic nature, agreeing on specific transports like TCP, QUIC, or WebSockets on paper becomes irrelevant. +Due to this dynamic nature, agreeing on specific transports like TCP, QUIC, or +WebSockets on paper becomes irrelevant. -However, it is useful to define a minimum baseline for interoperability purposes. +However, it is useful to define a minimum baseline for interoperability +purposes. -### Can clients support other transports/handshakes than the ones mandated by the spec? +#### Can clients support other transports/handshakes than the ones mandated by the spec? -Clients may support other transports such as libp2p QUIC, WebSockets, and WebRTC transports, if available in the language of choice. -While interoperability shall not be harmed by lack of such support, the advantages are desirable: +Clients may support other transports such as libp2p QUIC, WebSockets, and WebRTC +transports, if available in the language of choice. While interoperability shall +not be harmed by lack of such support, the advantages are desirable: -- Better latency, performance, and other QoS characteristics (QUIC). -- Paving the way for interfacing with future light clients (WebSockets, WebRTC). +- Better latency, performance, and other QoS characteristics (QUIC). +- Paving the way for interfacing with future light clients (WebSockets, WebRTC). -The libp2p QUIC transport inherently relies on TLS 1.3 per requirement in section 7 -of the [QUIC protocol specification](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7) -and the accompanying [QUIC-TLS document](https://tools.ietf.org/html/draft-ietf-quic-tls-22). +The libp2p QUIC transport inherently relies on TLS 1.3 per requirement in +section 7 of the +[QUIC protocol specification](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7) +and the accompanying +[QUIC-TLS document](https://tools.ietf.org/html/draft-ietf-quic-tls-22). -The usage of one handshake procedure or the other shall be transparent to the application layer, -once the libp2p Host/Node object has been configured appropriately. +The usage of one handshake procedure or the other shall be transparent to the +application layer, once the libp2p Host/Node object has been configured +appropriately. -### What are the advantages of using TCP/QUIC/Websockets? +#### What are the advantages of using TCP/QUIC/Websockets? -TCP is a reliable, ordered, full-duplex, congestion-controlled network protocol that powers much of the Internet as we know it today. -HTTP/1.1 and HTTP/2 run atop TCP. +TCP is a reliable, ordered, full-duplex, congestion-controlled network protocol +that powers much of the Internet as we know it today. HTTP/1.1 and HTTP/2 run +atop TCP. -QUIC is a new protocol that’s in the final stages of specification by the IETF QUIC WG. -It emerged from Google’s SPDY experiment. The QUIC transport is undoubtedly promising. -It’s UDP-based yet reliable, ordered, multiplexed, natively secure (TLS 1.3), reduces latency vs. TCP, -and offers stream-level and connection-level congestion control (thus removing head-of-line blocking), -0-RTT connection establishment, and endpoint migration, amongst other features. -UDP also has better NAT traversal properties than TCP—something we desperately pursue in peer-to-peer networks. +QUIC is a new protocol that’s in the final stages of specification by the IETF +QUIC WG. It emerged from Google’s SPDY experiment. The QUIC transport is +undoubtedly promising. It’s UDP-based yet reliable, ordered, multiplexed, +natively secure (TLS 1.3), reduces latency vs. TCP, and offers stream-level and +connection-level congestion control (thus removing head-of-line blocking), 0-RTT +connection establishment, and endpoint migration, amongst other features. UDP +also has better NAT traversal properties than TCP—something we desperately +pursue in peer-to-peer networks. -QUIC is being adopted as the underlying protocol for HTTP/3. -This has the potential to award us censorship resistance via deep packet inspection for free. -Provided that we use the same port numbers and encryption mechanisms as HTTP/3, our traffic may be indistinguishable from standard web traffic, -and we may only become subject to standard IP-based firewall filtering—something we can counteract via other mechanisms. +QUIC is being adopted as the underlying protocol for HTTP/3. This has the +potential to award us censorship resistance via deep packet inspection for free. +Provided that we use the same port numbers and encryption mechanisms as HTTP/3, +our traffic may be indistinguishable from standard web traffic, and we may only +become subject to standard IP-based firewall filtering—something we can +counteract via other mechanisms. WebSockets and/or WebRTC transports are necessary for interaction with browsers, -and will become increasingly important as we incorporate browser-based light clients to the Ethereum network. +and will become increasingly important as we incorporate browser-based light +clients to the Ethereum network. -### Why do we not just support a single transport? +#### Why do we not just support a single transport? -Networks evolve. -Hardcoding design decisions leads to ossification, preventing the evolution of networks alongside the state of the art. -Introducing changes on an ossified protocol is very costly, and sometimes, downright impracticable without causing undesirable breakage. +Networks evolve. Hardcoding design decisions leads to ossification, preventing +the evolution of networks alongside the state of the art. Introducing changes on +an ossified protocol is very costly, and sometimes, downright impracticable +without causing undesirable breakage. -Modeling for upgradeability and dynamic transport selection from the get-go lays the foundation for a future-proof stack. +Modeling for upgradeability and dynamic transport selection from the get-go lays +the foundation for a future-proof stack. -Clients can adopt new transports without breaking old ones, and the multi-transport ability enables constrained and sandboxed environments -(e.g. browsers, embedded devices) to interact with the network as first-class citizens via suitable/native transports (e.g. WSS), -without the need for proxying or trust delegation to servers. +Clients can adopt new transports without breaking old ones, and the +multi-transport ability enables constrained and sandboxed environments (e.g. +browsers, embedded devices) to interact with the network as first-class citizens +via suitable/native transports (e.g. WSS), without the need for proxying or +trust delegation to servers. -### Why are we not using QUIC from the start? +#### Why are we not using QUIC from the start? -The QUIC standard is still not finalized (at working draft 22 at the time of writing), -and not all mainstream runtimes/languages have mature, standard, and/or fully-interoperable [QUIC support](https://github.com/quicwg/base-drafts/wiki/Implementations). -One remarkable example is node.js, where the QUIC implementation is [in early development](https://github.com/nodejs/quic). +The QUIC standard is still not finalized (at working draft 22 at the time of +writing), and not all mainstream runtimes/languages have mature, standard, +and/or fully-interoperable +[QUIC support](https://github.com/quicwg/base-drafts/wiki/Implementations). One +remarkable example is node.js, where the QUIC implementation is +[in early development](https://github.com/nodejs/quic). -*Note*: [TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), -although an experiment exists to integrate Noise as the QUIC crypto layer: [nQUIC](https://eprint.iacr.org/2019/028). +*Note*: +[TLS 1.3 is a prerequisite of the QUIC transport](https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-7), +although an experiment exists to integrate Noise as the QUIC crypto layer: +[nQUIC](https://eprint.iacr.org/2019/028). -On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. -Old, insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as the sole ECDH key agreement function. -Handshakes are faster, 1-RTT data is supported, and session resumption is a reality, amongst other features. +On the other hand, TLS 1.3 is the newest, simplified iteration of TLS. Old, +insecure, obsolete ciphers and algorithms have been removed, adopting Ed25519 as +the sole ECDH key agreement function. Handshakes are faster, 1-RTT data is +supported, and session resumption is a reality, amongst other features. -## Multiplexing +### Multiplexing -### Why are we using mplex/yamux? +#### Why are we using mplex/yamux? -[Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md) is a multiplexer invented by Hashicorp that supports stream-level congestion control. -Implementations exist in a limited set of languages, and it’s not a trivial piece to develop. +[Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md) is a multiplexer +invented by Hashicorp that supports stream-level congestion control. +Implementations exist in a limited set of languages, and it’s not a trivial +piece to develop. -Conscious of that, the libp2p community conceptualized [mplex](https://github.com/libp2p/specs/blob/master/mplex/README.md) -as a simple, minimal multiplexer for usage with libp2p. -It does not support stream-level congestion control and is subject to head-of-line blocking. +Conscious of that, the libp2p community conceptualized +[mplex](https://github.com/libp2p/specs/blob/master/mplex/README.md) as a +simple, minimal multiplexer for usage with libp2p. It does not support +stream-level congestion control and is subject to head-of-line blocking. -Overlay multiplexers are not necessary with QUIC since the protocol provides native multiplexing, -but they need to be layered atop TCP, WebSockets, and other transports that lack such support. +Overlay multiplexers are not necessary with QUIC since the protocol provides +native multiplexing, but they need to be layered atop TCP, WebSockets, and other +transports that lack such support. -## Protocol Negotiation +### Protocol Negotiation -### When is multiselect 2.0 due and why do we plan to migrate to it? +#### When is multiselect 2.0 due and why do we plan to migrate to it? -multiselect 2.0 is currently being conceptualized. -The debate started [on this issue](https://github.com/libp2p/specs/pull/95), -but it got overloaded—as it tends to happen with large conceptual OSS discussions that touch the heart and core of a system. +multiselect 2.0 is currently being conceptualized. The debate started +[on this issue](https://github.com/libp2p/specs/pull/95), but it got +overloaded—as it tends to happen with large conceptual OSS discussions that +touch the heart and core of a system. -At some point in 2020, we expect a renewed initiative to first define the requirements, constraints, assumptions, and features, -in order to lock in basic consensus upfront and subsequently build on that consensus by submitting a specification for implementation. +At some point in 2020, we expect a renewed initiative to first define the +requirements, constraints, assumptions, and features, in order to lock in basic +consensus upfront and subsequently build on that consensus by submitting a +specification for implementation. We plan to eventually migrate to multiselect 2.0 because it will: -1. Reduce round trips during connection bootstrapping and stream protocol negotiation. +1. Reduce round trips during connection bootstrapping and stream protocol + negotiation. 2. Enable efficient one-stream-per-request interaction patterns. -3. Leverage *push data* mechanisms of underlying protocols to expedite negotiation. +3. Leverage *push data* mechanisms of underlying protocols to expedite + negotiation. 4. Provide the building blocks for enhanced censorship resistance. -### What is the difference between connection-level and stream-level protocol negotiation? +#### What is the difference between connection-level and stream-level protocol negotiation? All libp2p connections must be authenticated, encrypted, and multiplexed. -Connections using network transports unsupportive of native authentication/encryption and multiplexing (e.g. TCP) need to undergo protocol negotiation to agree on a mutually supported: +Connections using network transports unsupportive of native +authentication/encryption and multiplexing (e.g. TCP) need to undergo protocol +negotiation to agree on a mutually supported: 1. authentication/encryption mechanism (such as SecIO, TLS 1.3, Noise). 2. overlay multiplexer (such as mplex, Yamux, spdystream). In this specification, we refer to these two as *connection-level negotiations*. -Transports supporting those features natively (such as QUIC) omit those negotiations. +Transports supporting those features natively (such as QUIC) omit those +negotiations. -After successfully selecting a multiplexer, all subsequent I/O happens over *streams*. -When opening streams, peers pin a protocol to that stream, by conducting *stream-level protocol negotiation*. +After successfully selecting a multiplexer, all subsequent I/O happens over +*streams*. When opening streams, peers pin a protocol to that stream, by +conducting *stream-level protocol negotiation*. -At present, multistream-select 1.0 is used for both types of negotiation, -but multiselect 2.0 will use dedicated mechanisms for connection bootstrapping process and stream protocol negotiation. +At present, multistream-select 1.0 is used for both types of negotiation, but +multiselect 2.0 will use dedicated mechanisms for connection bootstrapping +process and stream protocol negotiation. -## Encryption +### Encryption -### Why are we not supporting SecIO? +#### Why are we not supporting SecIO? -SecIO has been the default encryption layer for libp2p for years. -It is used in IPFS and Filecoin. And although it will be superseded shortly, it is proven to work at scale. +SecIO has been the default encryption layer for libp2p for years. It is used in +IPFS and Filecoin. And although it will be superseded shortly, it is proven to +work at scale. -Although SecIO has wide language support, we won’t be using it for mainnet because, amongst other things, -it requires several round trips to be sound, and doesn’t support early data (0-RTT data), -a mechanism that multiselect 2.0 will leverage to reduce round trips during connection bootstrapping. +Although SecIO has wide language support, we won’t be using it for mainnet +because, amongst other things, it requires several round trips to be sound, and +doesn’t support early data (0-RTT data), a mechanism that multiselect 2.0 will +leverage to reduce round trips during connection bootstrapping. SecIO is not considered secure for the purposes of this spec. -### Why are we using Noise? +#### Why are we using Noise? -Copied from the Noise Protocol Framework [website](http://www.noiseprotocol.org): +Copied from the Noise Protocol Framework +[website](http://www.noiseprotocol.org): -> Noise is a framework for building crypto protocols. -Noise protocols support mutual and optional authentication, identity hiding, forward secrecy, zero round-trip encryption, and other advanced features. +> Noise is a framework for building crypto protocols. Noise protocols support +> mutual and optional authentication, identity hiding, forward secrecy, zero +> round-trip encryption, and other advanced features. -Noise in itself does not specify a single handshake procedure, -but provides a framework to build secure handshakes based on Diffie-Hellman key agreement with a variety of tradeoffs and guarantees. +Noise in itself does not specify a single handshake procedure, but provides a +framework to build secure handshakes based on Diffie-Hellman key agreement with +a variety of tradeoffs and guarantees. -Noise handshakes are lightweight and simple to understand, -and are used in major cryptographic-centric projects like WireGuard, I2P, and Lightning. -[Various](https://www.wireguard.com/papers/kobeissi-bhargavan-noise-explorer-2018.pdf) [studies](https://eprint.iacr.org/2019/436.pdf) -have assessed the stated security goals of several Noise handshakes with positive results. +Noise handshakes are lightweight and simple to understand, and are used in major +cryptographic-centric projects like WireGuard, I2P, and Lightning. +[Various](https://www.wireguard.com/papers/kobeissi-bhargavan-noise-explorer-2018.pdf) +[studies](https://eprint.iacr.org/2019/436.pdf) have assessed the stated +security goals of several Noise handshakes with positive results. -### Why are we using encryption at all? +#### Why are we using encryption at all? -Transport level encryption secures message exchange and provides properties that are useful for privacy, safety, and censorship resistance. -These properties are derived from the following security guarantees that apply to the entire communication between two peers: +Transport level encryption secures message exchange and provides properties that +are useful for privacy, safety, and censorship resistance. These properties are +derived from the following security guarantees that apply to the entire +communication between two peers: -- Peer authentication: the peer I’m talking to is really who they claim to be and who I expect them to be. +- Peer authentication: the peer I’m talking to is really who they claim to be + and who I expect them to be. - Confidentiality: no observer can eavesdrop on the content of our messages. -- Integrity: the data has not been tampered with by a third-party while in transit. -- Non-repudiation: the originating peer cannot dispute that they sent the message. -- Depending on the chosen algorithms and mechanisms (e.g. continuous HMAC), we may obtain additional guarantees, - such as non-replayability (this byte could’ve only been sent *now;* e.g. by using continuous HMACs), - or perfect forward secrecy (in the case that a peer key is compromised, the content of a past conversation will not be compromised). +- Integrity: the data has not been tampered with by a third-party while in + transit. +- Non-repudiation: the originating peer cannot dispute that they sent the + message. +- Depending on the chosen algorithms and mechanisms (e.g. continuous HMAC), we + may obtain additional guarantees, such as non-replayability (this byte + could’ve only been sent *now;* e.g. by using continuous HMACs), or perfect + forward secrecy (in the case that a peer key is compromised, the content of a + past conversation will not be compromised). -Note that transport-level encryption is not exclusive of application-level encryption or cryptography. -Transport-level encryption secures the communication itself, -while application-level cryptography is necessary for the application’s use cases (e.g. signatures, randomness, etc.). +Note that transport-level encryption is not exclusive of application-level +encryption or cryptography. Transport-level encryption secures the communication +itself, while application-level cryptography is necessary for the application’s +use cases (e.g. signatures, randomness, etc.). -## Gossipsub +### Gossipsub -### Why are we using a pub/sub algorithm for block and attestation propagation? +#### Why are we using a pub/sub algorithm for block and attestation propagation? Pubsub is a technique to broadcast/disseminate data across a network rapidly. -Such data is packaged in fire-and-forget messages that do not require a response from every recipient. -Peers subscribed to a topic participate in the propagation of messages in that topic. +Such data is packaged in fire-and-forget messages that do not require a response +from every recipient. Peers subscribed to a topic participate in the propagation +of messages in that topic. + +The alternative is to maintain a fully connected mesh (all peers connected to +each other 1:1), which scales poorly (O(n^2)). -The alternative is to maintain a fully connected mesh (all peers connected to each other 1:1), which scales poorly (O(n^2)). +#### Why are we using topics to segregate encodings, yet only support one encoding? -### Why are we using topics to segregate encodings, yet only support one encoding? +For future extensibility with almost zero overhead now (besides the extra bytes +in the topic name). -For future extensibility with almost zero overhead now (besides the extra bytes in the topic name). +#### How do we upgrade gossip channels (e.g. changes in encoding, compression)? -### How do we upgrade gossip channels (e.g. changes in encoding, compression)? +Changing gossipsub/broadcasts requires a coordinated upgrade where all clients +start publishing to the new topic together, during a hard fork. -Changing gossipsub/broadcasts requires a coordinated upgrade where all clients start publishing to the new topic together, during a hard fork. +When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a +gossipsub topic, the node should join the topic of the future epoch in which the +task is to occur in addition to listening to the topics for the current epoch. -When a node is preparing for upcoming tasks (e.g. validator duty lookahead) on a gossipsub topic, -the node should join the topic of the future epoch in which the task is to occur in addition to listening to the topics for the current epoch. +#### Why must all clients use the same gossip topic instead of one negotiated between each peer pair? -### Why must all clients use the same gossip topic instead of one negotiated between each peer pair? +Supporting multiple topics/encodings would require the presence of relayers to +translate between encodings and topics so as to avoid network fragmentation +where participants have diverging views on the gossiped state, making the +protocol more complicated and fragile. -Supporting multiple topics/encodings would require the presence of relayers to translate between encodings -and topics so as to avoid network fragmentation where participants have diverging views on the gossiped state, -making the protocol more complicated and fragile. +Gossip protocols typically remember what messages they've seen for a finite +period of time-based on message identity -- if you publish the same message +again after that time has passed, it will be re-broadcast—adding a relay delay +also makes this scenario more likely. -Gossip protocols typically remember what messages they've seen for a finite period of time-based on message identity --- if you publish the same message again after that time has passed, -it will be re-broadcast—adding a relay delay also makes this scenario more likely. +One can imagine that in a complicated upgrade scenario, we might have peers +publishing the same message on two topics/encodings, but the price here is +pretty high in terms of overhead -- both computational and networking -- so we'd +rather avoid that. -One can imagine that in a complicated upgrade scenario, we might have peers publishing the same message on two topics/encodings, -but the price here is pretty high in terms of overhead -- both computational and networking -- so we'd rather avoid that. +It is permitted for clients to publish data on alternative topics as long as +they also publish on the network-wide mandatory topic. -It is permitted for clients to publish data on alternative topics as long as they also publish on the network-wide mandatory topic. +#### Why are the topics strings and not hashes? -### Why are the topics strings and not hashes? +Topic names have a hierarchical structure. In the future, gossipsub may support +wildcard subscriptions (e.g. subscribe to all children topics under a root +prefix) by way of prefix matching. Enforcing hashes for topic names would +preclude us from leveraging such features going forward. -Topic names have a hierarchical structure. -In the future, gossipsub may support wildcard subscriptions -(e.g. subscribe to all children topics under a root prefix) by way of prefix matching. -Enforcing hashes for topic names would preclude us from leveraging such features going forward. +No security or privacy guarantees are lost as a result of choosing plaintext +topic names, since the domain is finite anyway, and calculating a digest's +preimage would be trivial. -No security or privacy guarantees are lost as a result of choosing plaintext topic names, -since the domain is finite anyway, and calculating a digest's preimage would be trivial. +Furthermore, the topic names are shorter than their digest equivalents (assuming +SHA-256 hash), so hashing topics would bloat messages unnecessarily. -Furthermore, the topic names are shorter than their digest equivalents (assuming SHA-256 hash), -so hashing topics would bloat messages unnecessarily. +#### Why are we using the `StrictNoSign` signature policy? -### Why are we using the `StrictNoSign` signature policy? +The policy omits the `from` (1), `seqno` (3), `signature` (5) and `key` (6) +fields. These fields would: -The policy omits the `from` (1), `seqno` (3), `signature` (5) and `key` (6) fields. These fields would: - Expose origin of sender (`from`), type of sender (based on `seqno`) -- Add extra unused data to the gossip, since message IDs are based on `data`, not on the `from` and `seqno`. +- Add extra unused data to the gossip, since message IDs are based on `data`, + not on the `from` and `seqno`. - Introduce more message validation than necessary, e.g. no `signature`. -### Why are we overriding the default libp2p pubsub `message-id`? +#### Why are we overriding the default libp2p pubsub `message-id`? -For our current purposes, there is no need to address messages based on source peer, or track a message `seqno`. -By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer. +For our current purposes, there is no need to address messages based on source +peer, or track a message `seqno`. By overriding the default `message-id` to use +content-addressing we can filter unnecessary duplicates before hitting the +application layer. Some examples of where messages could be duplicated: -* A validator client connected to multiple beacon nodes publishing duplicate gossip messages -* Attestation aggregation strategies where clients partially aggregate attestations and propagate them. - Partial aggregates could be duplicated -* Clients re-publishing seen messages +- A validator client connected to multiple beacon nodes publishing duplicate + gossip messages +- Attestation aggregation strategies where clients partially aggregate + attestations and propagate them. Partial aggregates could be duplicated +- Clients re-publishing seen messages -### Why are these specific gossip parameters chosen? +#### Why are these specific gossip parameters chosen? - `D`, `D_low`, `D_high`, `D_lazy`: recommended defaults. -- `heartbeat_interval`: 0.7 seconds, recommended for the beacon chain in the [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). -- `fanout_ttl`: 60 seconds, recommended default. - Fanout is primarily used by committees publishing attestations to subnets. - This happens once per epoch per validator and the subnet changes each epoch - so there is little to gain in having a `fanout_ttl` be increased from the recommended default. +- `heartbeat_interval`: 0.7 seconds, recommended for the beacon chain in the + [GossipSub evaluation report by Protocol Labs](https://gateway.ipfs.io/ipfs/QmRAFP5DBnvNjdYSbWhEhVRJJDFCLpPyvew5GwCCB4VxM4). +- `fanout_ttl`: 60 seconds, recommended default. Fanout is primarily used by + committees publishing attestations to subnets. This happens once per epoch per + validator and the subnet changes each epoch so there is little to gain in + having a `fanout_ttl` be increased from the recommended default. - `mcache_len`: 6, increase by one to ensure that mcache is around for long enough for `IWANT`s to respond to `IHAVE`s in the context of the shorter `heartbeat_interval`. If `mcache_gossip` is increased, this param should be increased to be at least `3` (~2 seconds) more than `mcache_gossip`. -- `mcache_gossip`: 3, recommended default. This can be increased to 5 or 6 - (~4 seconds) if gossip times are longer than expected and the current window - does not provide enough responsiveness during adverse conditions. -- `seen_ttl`: `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 550`. - Attestation gossip validity is bounded by an epoch, so this is the safe max bound. - - -### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets? - -For some gossip channels (e.g. those for Attestations and BeaconBlocks), -there are designated ranges of slots during which particular messages can be sent, -limiting messages gossiped to those that can be reasonably used in the consensus at the current time/slot. -This is to reduce optionality in DoS attacks. - -`MAXIMUM_GOSSIP_CLOCK_DISPARITY` provides some leeway in validating slot ranges to prevent the gossip network -from becoming overly brittle with respect to clock disparity. -For minimum and maximum allowable slot broadcast times, -`MAXIMUM_GOSSIP_CLOCK_DISPARITY` MUST be subtracted and added respectively, marginally extending the valid range. -Although messages can at times be eagerly gossiped to the network, -the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot. - -### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? - -Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. -The exact grouping will be dependent on more involved network tests. -This constant allows for more flexibility in setting up the network topology for attestation aggregation (as aggregation should happen on each subnet). -The value is currently set to be equal to `MAX_COMMITTEES_PER_SLOT` if/until network tests indicate otherwise. - -### Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots? - -Attestations can only be included on chain within an epoch's worth of slots so this is the natural cutoff. -There is no utility to the chain to broadcast attestations older than one epoch, -and because validators have a chance to make a new attestation each epoch, -there is minimal utility to the fork choice to relay old attestations as a new latest message can soon be created by each validator. - -In addition to this, relaying attestations requires validating the attestation in the context of the `state` during which it was created. -Thus, validating arbitrarily old attestations would put additional requirements on which states need to be readily available to the node. -This would result in a higher resource burden and could serve as a DoS vector. - -### Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s? - -The dominant strategy for an individual validator is to always broadcast an aggregate containing their own attestation -to the global channel to ensure that proposers see their attestation for inclusion. -Using a private selection criteria and providing this proof of selection alongside -the gossiped aggregate ensures that this dominant strategy will not flood the global channel. - -Also, an attacker can create any number of honest-looking aggregates and broadcast them to the global pubsub channel. -Thus without some sort of proof of selection as an aggregator, the global channel can trivially be spammed. - -### Why are we sending entire objects in the pubsub and not just hashes? - -Entire objects should be sent to get the greatest propagation speeds. -If only hashes are sent, then block and attestation propagation is dependent on recursive requests from each peer. -In a hash-only scenario, peers could receive hashes without knowing who to download the actual contents from. -Sending entire objects ensures that they get propagated through the entire network. - -### Should clients gossip blocks if they *cannot* validate the proposer signature due to not yet being synced, not knowing the head block, etc? +- `mcache_gossip`: 3, recommended default. This can be increased to 5 or 6 (~4 + seconds) if gossip times are longer than expected and the current window does + not provide enough responsiveness during adverse conditions. +- `seen_ttl`: + `SLOTS_PER_EPOCH * SECONDS_PER_SLOT / heartbeat_interval = approx. 550`. + Attestation gossip validity is bounded by an epoch, so this is the safe max + bound. + +#### Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets? + +For some gossip channels (e.g. those for Attestations and BeaconBlocks), there +are designated ranges of slots during which particular messages can be sent, +limiting messages gossiped to those that can be reasonably used in the consensus +at the current time/slot. This is to reduce optionality in DoS attacks. + +`MAXIMUM_GOSSIP_CLOCK_DISPARITY` provides some leeway in validating slot ranges +to prevent the gossip network from becoming overly brittle with respect to clock +disparity. For minimum and maximum allowable slot broadcast times, +`MAXIMUM_GOSSIP_CLOCK_DISPARITY` MUST be subtracted and added respectively, +marginally extending the valid range. Although messages can at times be eagerly +gossiped to the network, the node's fork choice prevents integration of these +messages into the actual consensus until the _actual local start_ of the +designated slot. + +#### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? + +Depending on the number of validators, it may be more efficient to group shard +subnets and might provide better stability for the gossipsub channel. The exact +grouping will be dependent on more involved network tests. This constant allows +for more flexibility in setting up the network topology for attestation +aggregation (as aggregation should happen on each subnet). The value is +currently set to be equal to `MAX_COMMITTEES_PER_SLOT` if/until network tests +indicate otherwise. + +#### Why are attestations limited to be broadcast on gossip channels within `SLOTS_PER_EPOCH` slots? + +Attestations can only be included on chain within an epoch's worth of slots so +this is the natural cutoff. There is no utility to the chain to broadcast +attestations older than one epoch, and because validators have a chance to make +a new attestation each epoch, there is minimal utility to the fork choice to +relay old attestations as a new latest message can soon be created by each +validator. + +In addition to this, relaying attestations requires validating the attestation +in the context of the `state` during which it was created. Thus, validating +arbitrarily old attestations would put additional requirements on which states +need to be readily available to the node. This would result in a higher resource +burden and could serve as a DoS vector. + +#### Why are aggregate attestations broadcast to the global topic as `AggregateAndProof`s rather than just as `Attestation`s? + +The dominant strategy for an individual validator is to always broadcast an +aggregate containing their own attestation to the global channel to ensure that +proposers see their attestation for inclusion. Using a private selection +criteria and providing this proof of selection alongside the gossiped aggregate +ensures that this dominant strategy will not flood the global channel. + +Also, an attacker can create any number of honest-looking aggregates and +broadcast them to the global pubsub channel. Thus without some sort of proof of +selection as an aggregator, the global channel can trivially be spammed. + +#### Why are we sending entire objects in the pubsub and not just hashes? + +Entire objects should be sent to get the greatest propagation speeds. If only +hashes are sent, then block and attestation propagation is dependent on +recursive requests from each peer. In a hash-only scenario, peers could receive +hashes without knowing who to download the actual contents from. Sending entire +objects ensures that they get propagated through the entire network. + +#### Should clients gossip blocks if they *cannot* validate the proposer signature due to not yet being synced, not knowing the head block, etc? + +The prohibition of unverified-block-gossiping extends to nodes that cannot +verify a signature due to not being fully synced to ensure that such (amplified) +DOS attacks are not possible. + +#### How are we going to discover peers in a gossipsub topic? + +In Phase 0, peers for attestation subnets will be found using the `attnets` +entry in the ENR. + +Although this method will be sufficient for early upgrade of the beacon chain, +we aim to use the more appropriate discv5 topics for this and other similar +tasks in the future. ENRs should ultimately not be used for this purpose. They +are best suited to store identity, location, and capability information, rather +than more volatile advertisements. + +#### How should fork version be used in practice? + +Fork versions are to be manually updated (likely via incrementing) at each hard +fork. This is to provide native domain separation for signatures as well as to +aid in usefulness for identifying peers (via ENRs) and versioning network +protocols (e.g. using fork version to naturally version gossipsub topics). + +`BeaconState.genesis_validators_root` is mixed into signature and ENR fork +domains (`ForkDigest`) to aid in the ease of domain separation between chains. +This allows fork versions to safely be reused across chains except for the case +of contentious forks using the same genesis. In these cases, extra care should +be taken to isolate fork versions (e.g. flip a high order bit in all future +versions of one of the chains). + +A node locally stores all previous and future planned fork versions along with +the each fork epoch. This allows for handling sync and processing messages +starting from past forks/epochs. + +### Req/Resp -The prohibition of unverified-block-gossiping extends to nodes that cannot verify a signature -due to not being fully synced to ensure that such (amplified) DOS attacks are not possible. - -### How are we going to discover peers in a gossipsub topic? - -In Phase 0, peers for attestation subnets will be found using the `attnets` entry in the ENR. - -Although this method will be sufficient for early upgrade of the beacon chain, we aim to use the more appropriate discv5 topics for this and other similar tasks in the future. -ENRs should ultimately not be used for this purpose. -They are best suited to store identity, location, and capability information, rather than more volatile advertisements. - -### How should fork version be used in practice? - -Fork versions are to be manually updated (likely via incrementing) at each hard fork. -This is to provide native domain separation for signatures as well as to aid in usefulness for identitying peers (via ENRs) -and versioning network protocols (e.g. using fork version to naturally version gossipsub topics). - -`BeaconState.genesis_validators_root` is mixed into signature and ENR fork domains (`ForkDigest`) to aid in the ease of domain separation between chains. -This allows fork versions to safely be reused across chains except for the case of contentious forks using the same genesis. -In these cases, extra care should be taken to isolate fork versions (e.g. flip a high order bit in all future versions of one of the chains). - -A node locally stores all previous and future planned fork versions along with the each fork epoch. -This allows for handling sync and processing messages starting from past forks/epochs. - -## Req/Resp - -### Why segregate requests into dedicated protocol IDs? +#### Why segregate requests into dedicated protocol IDs? Requests are segregated by protocol ID to: -1. Leverage protocol routing in libp2p, such that the libp2p stack will route the incoming stream to the appropriate handler. - This allows the handler function for each request type to be self-contained. - For an analogy, think about how you attach HTTP handlers to a REST API server. -2. Version requests independently. - In a coarser-grained umbrella protocol, the entire protocol would have to be versioned even if just one field in a single message changed. -3. Enable clients to select the individual requests/versions they support. - It would no longer be a strict requirement to support all requests, - and clients, in principle, could support a subset of requests and variety of versions. -4. Enable flexibility and agility for clients adopting spec changes that impact the request, by signalling to peers exactly which subset of new/old requests they support. -5. Enable clients to explicitly choose backwards compatibility at the request granularity. - Without this, clients would be forced to support entire versions of the coarser request protocol. -6. Parallelise RFCs (or EIPs). - By decoupling requests from one another, each RFC that affects the request protocol can be deployed/tested/debated independently - without relying on a synchronization point to version the general top-level protocol. - 1. This has the benefit that clients can explicitly choose which RFCs to deploy - without buying into all other RFCs that may be included in that top-level version. - 2. Affording this level of granularity with a top-level protocol would imply creating as many variants - (e.g. /protocol/43-{a,b,c,d,...}) as the cartesian product of RFCs inflight, O(n^2). -7. Allow us to simplify the payload of requests. - Request-id’s and method-ids no longer need to be sent. - The encoding/request type and version can all be handled by the framework. - -**Caveat**: The protocol negotiation component in the current version of libp2p is called multistream-select 1.0. -It is somewhat naïve and introduces overhead on every request when negotiating streams, -although implementation-specific optimizations are possible to save this cost. -Multiselect 2.0 will eventually remove this overhead by memoizing previously selected protocols, and modeling shared protocol tables. -Fortunately, this req/resp protocol is not the expected network bottleneck in the protocol -so the additional overhead is not expected to significantly hinder this domain. - -### Why are messages length-prefixed with a protobuf varint in the SSZ-encoding? - -We are using single-use streams where each stream is closed at the end of the message. -Thus, libp2p transparently handles message delimiting in the underlying stream. -libp2p streams are full-duplex, and each party is responsible for closing their write side (like in TCP). -We can therefore use stream closure to mark the end of the request and response independently. - -Nevertheless, in the case of `ssz_snappy`, messages are still length-prefixed with the length of the underlying data: -* A basic reader can prepare a correctly sized buffer before reading the message -* A more advanced reader can stream-decode SSZ given the length of the SSZ data. -* Alignment with protocols like gRPC over HTTP/2 that prefix with length -* Sanity checking of message length, and enabling much stricter message length limiting based on SSZ type information, - to provide even more DOS protection than the global message length already does. - E.g. a small `Status` message does not nearly require `MAX_CHUNK_SIZE` bytes. - -[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) is an efficient technique to encode variable-length (unsigned here) ints. -Instead of reserving a fixed-size field of as many bytes as necessary to convey the maximum possible value, this field is elastic in exchange for 1-bit overhead per byte. - -### Why do we version protocol strings with ordinals instead of semver? - -Using semver for network protocols is confusing. -It is never clear what a change in a field, even if backwards compatible on deserialization, actually implies. +1. Leverage protocol routing in libp2p, such that the libp2p stack will route + the incoming stream to the appropriate handler. This allows the handler + function for each request type to be self-contained. For an analogy, think + about how you attach HTTP handlers to a REST API server. +2. Version requests independently. In a coarser-grained umbrella protocol, the + entire protocol would have to be versioned even if just one field in a single + message changed. +3. Enable clients to select the individual requests/versions they support. It + would no longer be a strict requirement to support all requests, and clients, + in principle, could support a subset of requests and variety of versions. +4. Enable flexibility and agility for clients adopting spec changes that impact + the request, by signalling to peers exactly which subset of new/old requests + they support. +5. Enable clients to explicitly choose backwards compatibility at the request + granularity. Without this, clients would be forced to support entire versions + of the coarser request protocol. +6. Parallelise RFCs (or EIPs). By decoupling requests from one another, each RFC + that affects the request protocol can be deployed/tested/debated + independently without relying on a synchronization point to version the + general top-level protocol. + 1. This has the benefit that clients can explicitly choose which RFCs to + deploy without buying into all other RFCs that may be included in that + top-level version. + 2. Affording this level of granularity with a top-level protocol would imply + creating as many variants (e.g. /protocol/43-{a,b,c,d,...}) as the + cartesian product of RFCs in-flight, O(n^2). +7. Allow us to simplify the payload of requests. Request-id’s and method-ids no + longer need to be sent. The encoding/request type and version can all be + handled by the framework. + +**Caveat**: The protocol negotiation component in the current version of libp2p +is called multistream-select 1.0. It is somewhat naïve and introduces overhead +on every request when negotiating streams, although implementation-specific +optimizations are possible to save this cost. Multiselect 2.0 will eventually +remove this overhead by memoizing previously selected protocols, and modeling +shared protocol tables. Fortunately, this req/resp protocol is not the expected +network bottleneck in the protocol so the additional overhead is not expected to +significantly hinder this domain. + +#### Why are messages length-prefixed with a protobuf varint in the SSZ-encoding? + +We are using single-use streams where each stream is closed at the end of the +message. Thus, libp2p transparently handles message delimiting in the underlying +stream. libp2p streams are full-duplex, and each party is responsible for +closing their write side (like in TCP). We can therefore use stream closure to +mark the end of the request and response independently. + +Nevertheless, in the case of `ssz_snappy`, messages are still length-prefixed +with the length of the underlying data: + +- A basic reader can prepare a correctly sized buffer before reading the message +- A more advanced reader can stream-decode SSZ given the length of the SSZ data. +- Alignment with protocols like gRPC over HTTP/2 that prefix with length +- Sanity checking of message length, and enabling much stricter message length + limiting based on SSZ type information, to provide even more DOS protection + than the global message length already does. E.g. a small `Status` message + does not nearly require `MAX_PAYLOAD_SIZE` bytes. + +[Protobuf varint](https://developers.google.com/protocol-buffers/docs/encoding#varints) +is an efficient technique to encode variable-length (unsigned here) ints. +Instead of reserving a fixed-size field of as many bytes as necessary to convey +the maximum possible value, this field is elastic in exchange for 1-bit overhead +per byte. + +#### Why do we version protocol strings with ordinals instead of semver? + +Using semver for network protocols is confusing. It is never clear what a change +in a field, even if backwards compatible on deserialization, actually implies. Network protocol agreement should be explicit. Imagine two peers: - Peer A supporting v1.1.1 of protocol X. - Peer B supporting v1.1.2 of protocol X. -These two peers should never speak to each other because the results can be unpredictable. -This is an oversimplification: imagine the same problem with a set of 10 possible versions. -We now have 10^2 (100) possible outcomes that peers need to model for. The resulting complexity is unwieldy. - -For this reason, we rely on negotiation of explicit, verbatim protocols. -In the above case, peer B would provide backwards compatibility by supporting and advertising both v1.1.1 and v1.1.2 of the protocol. - -Therefore, semver would be relegated to convey expectations at the human level, and it wouldn't do a good job there either, -because it's unclear if "backwards compatibility" and "breaking change" apply only to wire schema level, to behavior, etc. - -For this reason, we remove and replace semver with ordinals that require explicit agreement and do not mandate a specific policy for changes. - -### Why is it called Req/Resp and not RPC? - -Req/Resp is used to avoid confusion with JSON-RPC and similar user-client interaction mechanisms. - -### Why do we allow empty responses in block requests? - -When requesting blocks by range or root, it may happen that there are no blocks in the selected range or the responding node does not have the requested blocks. - -Thus, it may happen that we need to transmit an empty list - there are several ways to encode this: - -0) Close the stream without sending any data -1) Add a `null` option to the `success` response, for example by introducing an additional byte -2) Respond with an error result, using a specific error code for "No data" +These two peers should never speak to each other because the results can be +unpredictable. This is an oversimplification: imagine the same problem with a +set of 10 possible versions. We now have 10^2 (100) possible outcomes that peers +need to model for. The resulting complexity is unwieldy. -Semantically, it is not an error that a block is missing during a slot making option 2 unnatural. +For this reason, we rely on negotiation of explicit, verbatim protocols. In the +above case, peer B would provide backwards compatibility by supporting and +advertising both v1.1.1 and v1.1.2 of the protocol. -Option 1 allows the responder to signal "no block", but this information may be wrong - for example in the case of a malicious node. +Therefore, semver would be relegated to convey expectations at the human level, +and it wouldn't do a good job there either, because it's unclear if "backwards +compatibility" and "breaking change" apply only to wire schema level, to +behavior, etc. -Under option 0, there is no way for a client to distinguish between a slot without a block and an incomplete response, -but given that it already must contain logic to handle the uncertainty of a malicious peer, option 0 was chosen. -Clients should mark any slots missing blocks as unknown until they can be verified as not containing a block by successive blocks. - -Assuming option 0 with no special `null` encoding, consider a request for slots `2, 3, 4` --- if there was no block produced at slot 4, the response would be `2, 3, EOF`. -Now consider the same situation, but where only `4` is requested --- closing the stream with only `EOF` (without any `response_chunk`) is consistent. - -Failing to provide blocks that nodes "should" have is reason to trust a peer less --- for example, if a particular peer gossips a block, it should have access to its parent. -If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them. - -### Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from? - -When connecting, the `Status` message gives an idea about the sync status of a particular peer, but this changes over time. -By the time a subsequent `BeaconBlockByRange` request is processed, the information may be stale, -and the responding side might have moved on to a new finalization point and pruned blocks around the previous head and finalized blocks. - -To avoid this race condition, we allow the responding side to choose which branch to send to the requesting client. -The requesting client then goes on to validate the blocks and incorporate them in their own database --- because they follow the same rules, they should at this point arrive at the same canonical chain. - -### Why are `BlocksByRange` requests only required to be served for the latest `MIN_EPOCHS_FOR_BLOCK_REQUESTS` epochs? - -Due to economic finality and weak subjectivity requirements of a proof-of-stake blockchain, for a new node to safely join the network -the node must provide a recent checkpoint found out-of-band. This checkpoint can be in the form of a `root` & `epoch` or it can be the entire -beacon state and then a simple block sync from there to the head. We expect the latter to be the dominant UX strategy. - -These checkpoints *in the worst case* (i.e. very large validator set and maximal allowed safety decay) must be from the -most recent `MIN_EPOCHS_FOR_BLOCK_REQUESTS` epochs, and thus a user must be able to block sync to the head from this starting point. -Thus, this defines the epoch range outside which nodes may prune blocks, and -the epoch range that a new node syncing from a checkpoint must backfill. - -`MIN_EPOCHS_FOR_BLOCK_REQUESTS` is calculated using the arithmetic from `compute_weak_subjectivity_period` found in the -[weak subjectivity guide](./weak-subjectivity.md). Specifically to find this max epoch range, we use the worst case event of a very large validator size +For this reason, we remove and replace semver with ordinals that require +explicit agreement and do not mandate a specific policy for changes. + +#### Why is it called Req/Resp and not RPC? + +Req/Resp is used to avoid confusion with JSON-RPC and similar user-client +interaction mechanisms. + +#### What is a typical rate limiting strategy? + +The responder typically will want to rate limit requests to protect against spam +and to manage resource consumption, while the requester will want to maximise +performance based on its own resource allocation strategy. For the network, it +is beneficial if available resources are used optimally. + +Broadly, the requester does not know the capacity / limit of each server but can +derive it from the rate of responses for the purpose of selecting the next peer +for a request. + +Because the server withholds the response until capacity is available, a client +can optimistically send requests without risking running into negative scoring +situations or sub-optimal rate polling. + +A typical approach for the requester is to implement a timeout on the request +that depends on the nature of the request and on connectivity parameters in +general - for example when requesting blocks, a peer might choose to send a +request to a second peer if the first peer does not respond within a reasonable +time, and to reset the request to the first peer if the second peer responds +faster. Clients may use past response performance to reward fast peers when +implementing peer scoring. + +A typical approach for the responder is to implement a two-level token/leaky +bucket with a per-peer limit and a global limit. The granularity of rate +limiting may be based either on full requests or individual chunks with the +latter being preferable. A token cost may be assigned to the request itself and +separately each chunk in the response so as to remain protected both against +large and frequent requests. + +For requesters, rate limiting is not distinguishable from other conditions +causing slow responses (slow peers, congestion etc) and since the latter +conditions must be handled anyway, including rate limiting in this strategy +keeps the implementation simple. + +#### Why do we allow empty responses in block requests? + +When requesting blocks by range or root, it may happen that there are no blocks +in the selected range or the responding node does not have the requested blocks. + +Thus, it may happen that we need to transmit an empty list - there are several +ways to encode this: + +0. Close the stream without sending any data +1. Add a `null` option to the `success` response, for example by introducing an + additional byte +2. Respond with an error result, using a specific error code for "No data" + +Semantically, it is not an error that a block is missing during a slot making +option 2 unnatural. + +Option 1 allows the responder to signal "no block", but this information may be +wrong - for example in the case of a malicious node. + +Under option 0, there is no way for a client to distinguish between a slot +without a block and an incomplete response, but given that it already must +contain logic to handle the uncertainty of a malicious peer, option 0 was +chosen. Clients should mark any slots missing blocks as unknown until they can +be verified as not containing a block by successive blocks. + +Assuming option 0 with no special `null` encoding, consider a request for slots +`2, 3, 4` -- if there was no block produced at slot 4, the response would be +`2, 3, EOF`. Now consider the same situation, but where only `4` is requested -- +closing the stream with only `EOF` (without any `response_chunk`) is consistent. + +Failing to provide blocks that nodes "should" have is reason to trust a peer +less -- for example, if a particular peer gossips a block, it should have access +to its parent. If a request for the parent fails, it's indicative of poor peer +quality since peers should validate blocks before gossiping them. + +#### Why does `BeaconBlocksByRange` let the server choose which branch to send blocks from? + +When connecting, the `Status` message gives an idea about the sync status of a +particular peer, but this changes over time. By the time a subsequent +`BeaconBlockByRange` request is processed, the information may be stale, and the +responder might have moved on to a new finalization point and pruned blocks +around the previous head and finalized blocks. + +To avoid this race condition, we allow the responder to choose which branch to +send to the requester. The requester then goes on to validate the blocks and +incorporate them in their own database -- because they follow the same rules, +they should at this point arrive at the same canonical chain. + +#### Why are `BlocksByRange` requests only required to be served for the latest `MIN_EPOCHS_FOR_BLOCK_REQUESTS` epochs? + +Due to economic finality and weak subjectivity requirements of a proof-of-stake +blockchain, for a new node to safely join the network the node must provide a +recent checkpoint found out-of-band. This checkpoint can be in the form of a +`root` & `epoch` or it can be the entire beacon state and then a simple block +sync from there to the head. We expect the latter to be the dominant UX +strategy. + +These checkpoints *in the worst case* (i.e. very large validator set and maximal +allowed safety decay) must be from the most recent +`MIN_EPOCHS_FOR_BLOCK_REQUESTS` epochs, and thus a user must be able to block +sync to the head from this starting point. Thus, this defines the epoch range +outside which nodes may prune blocks, and the epoch range that a new node +syncing from a checkpoint must backfill. + +`MIN_EPOCHS_FOR_BLOCK_REQUESTS` is calculated using the arithmetic from +`compute_weak_subjectivity_period` found in the +[weak subjectivity guide](./weak-subjectivity.md). Specifically to find this max +epoch range, we use the worst case event of a very large validator size (`>= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT`). + + ```python MIN_EPOCHS_FOR_BLOCK_REQUESTS = ( - MIN_VALIDATOR_WITHDRAWABILITY_DELAY - + MAX_SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // (2 * 100) + MIN_VALIDATOR_WITHDRAWABILITY_DELAY + MAX_SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // (2 * 100) ) ``` -Where `MAX_SAFETY_DECAY = 100` and thus `MIN_EPOCHS_FOR_BLOCK_REQUESTS = 33024` (~5 months). +Where `MAX_SAFETY_DECAY = 100` and thus `MIN_EPOCHS_FOR_BLOCK_REQUESTS = 33024` +(~5 months). -### Why must the proposer signature be checked when backfilling blocks in the database? +#### Why must the proposer signature be checked when backfilling blocks in the database? -When backfilling blocks in a database from a know safe block/state (e.g. when starting from a weak subjectivity state), -the node not only must ensure the `BeaconBlock`s form a chain to the known safe block, -but also must check that the proposer signature is valid in the `SignedBeaconBlock` wrapper. +When backfilling blocks in a database from a know safe block/state (e.g. when +starting from a weak subjectivity state), the node not only must ensure the +`BeaconBlock`s form a chain to the known safe block, but also must check that +the proposer signature is valid in the `SignedBeaconBlock` wrapper. This is because the signature is not part of the `BeaconBlock` hash chain, and thus could be corrupted by an attacker serving valid `BeaconBlock`s but invalid signatures contained in `SignedBeaconBlock`. Although in this particular use case this does not represent a decay in safety -(due to the assumptions of starting at a weak subjectivity checkpoint), it -would represent invalid historic data and could be unwittingly transmitted to +(due to the assumptions of starting at a weak subjectivity checkpoint), it would +represent invalid historic data and could be unwittingly transmitted to additional nodes. -### What's the effect of empty slots on the sync algorithm? +#### What's the effect of empty slots on the sync algorithm? -When syncing one can only tell that a slot has been skipped on a particular branch -by examining subsequent blocks and analyzing the graph formed by the parent root. -Because the server side may choose to omit blocks in the response for any reason, clients must validate the graph and be prepared to fill in gaps. +When syncing one can only tell that a slot has been skipped on a particular +branch by examining subsequent blocks and analyzing the graph formed by the +parent root. Because the server side may choose to omit blocks in the response +for any reason, clients must validate the graph and be prepared to fill in gaps. -For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], clients may not assume that block 4 doesn't exist --- it merely means that the responding peer did not send it (they may not have it yet or may maliciously be trying to hide it) -and successive blocks will be needed to determine if there exists a block at slot 4 in this particular branch. +For example, if a peer responds with blocks [2, 3] when asked for [2, 3, 4], +clients may not assume that block 4 doesn't exist -- it merely means that the +responding peer did not send it (they may not have it yet or may maliciously be +trying to hide it) and successive blocks will be needed to determine if there +exists a block at slot 4 in this particular branch. -## Discovery +### Discovery -### Why are we using discv5 and not libp2p Kademlia DHT? +#### Why are we using discv5 and not libp2p Kademlia DHT? -discv5 is a standalone protocol, running on UDP on a dedicated port, meant for peer and service discovery only. -discv5 supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are, or will be, requirements in this context. +discv5 is a standalone protocol, running on UDP on a dedicated port, meant for +peer and service discovery only. discv5 supports self-certified, flexible peer +records (ENRs) and topic-based advertisement, both of which are, or will be, +requirements in this context. -On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations -with content routing and storage capabilities, both of which are irrelevant in this context. +On the other hand, libp2p Kademlia DHT is a fully-fledged DHT +protocol/implementations with content routing and storage capabilities, both of +which are irrelevant in this context. -Ethereum execution-layer nodes will evolve to support discv5. -By sharing the discovery network between Ethereum consensus-layer and execution-layer clients, -we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, -to which smaller networks are more vulnerable. -It should also help light clients of both networks find nodes with specific capabilities. +Ethereum execution-layer nodes will evolve to support discv5. By sharing the +discovery network between Ethereum consensus-layer and execution-layer clients, +we benefit from the additive effect on network size that enhances resilience and +resistance against certain attacks, to which smaller networks are more +vulnerable. It should also help light clients of both networks find nodes with +specific capabilities. discv5 is in the process of being audited. -### What is the difference between an ENR and a multiaddr, and why are we using ENRs? +#### What is the difference between an ENR and a multiaddr, and why are we using ENRs? -Ethereum Node Records are self-certified node records. -Nodes craft and disseminate ENRs for themselves, proving authorship via a cryptographic signature. -ENRs are sequentially indexed, enabling conflicts to be resolved. +Ethereum Node Records are self-certified node records. Nodes craft and +disseminate ENRs for themselves, proving authorship via a cryptographic +signature. ENRs are sequentially indexed, enabling conflicts to be resolved. -ENRs are key-value records with string-indexed ASCII keys. -They can store arbitrary information, but EIP-778 specifies a pre-defined dictionary, including IPv4 and IPv6 addresses, secp256k1 public keys, etc. +ENRs are key-value records with string-indexed ASCII keys. They can store +arbitrary information, but EIP-778 specifies a pre-defined dictionary, including +IPv4 and IPv6 addresses, secp256k1 public keys, etc. -Comparing ENRs and multiaddrs is like comparing apples and oranges. -ENRs are self-certified containers of identity, addresses, and metadata about a node. -Multiaddrs are address strings with the peculiarity that they’re self-describing, composable and future-proof. -An ENR can contain multiaddrs, and multiaddrs can be derived securely from the fields of an authenticated ENR. +Comparing ENRs and multiaddrs is like comparing apples and oranges. ENRs are +self-certified containers of identity, addresses, and metadata about a node. +Multiaddrs are address strings with the peculiarity that they’re +self-describing, composable and future-proof. An ENR can contain multiaddrs, and +multiaddrs can be derived securely from the fields of an authenticated ENR. discv5 uses ENRs and we will presumably need to: -1. Add `multiaddr` to the dictionary, so that nodes can advertise their multiaddr under a reserved namespace in ENRs. – and/or – -2. Define a bi-directional conversion function between multiaddrs and the corresponding denormalized fields in an ENR - (ip, ip6, tcp, tcp6, etc.), for compatibility with nodes that do not support multiaddr natively (e.g. Ethereum execution-layer nodes). - -### Why do we not form ENRs and find peers until genesis block/state is known? - -Although client software might very well be running locally prior to the solidification of the beacon chain genesis state and block, -clients cannot form valid ENRs prior to this point. -ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cleaner separation between chains -so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. -Once genesis data is known, we can then form ENRs and safely find peers. - -When using a proof-of-work deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`, -providing ample time to find peers and form initial connections and gossip subnets prior to genesis. - -## Compression/Encoding - -### Why are we using SSZ for encoding? - -SSZ is used at the consensus layer, and all implementations should have support for SSZ-encoding/decoding, -requiring no further dependencies to be added to client implementations. -This is a natural choice for serializing objects to be sent across the wire. -The actual data in most protocols will be further compressed for efficiency. - -SSZ has well-defined schemas for consensus objects (typically sent across the wire) reducing any serialization schema data that needs to be sent. -It also has defined all required types that are required for this network specification. - -### Why are we compressing, and at which layers? - -We compress on the wire to achieve smaller payloads per-message, which, in aggregate, -result in higher efficiency, better utilization of available bandwidth, and overall reduction in network-wide traffic overhead. - -At this time, libp2p does not have an out-of-the-box compression feature that can be dynamically negotiated -and layered atop connections and streams, but it is [being considered](https://github.com/libp2p/libp2p/issues/81). - -This is a non-trivial feature because the behavior -of network IO loops, kernel buffers, chunking, and packet fragmentation, amongst others, need to be taken into account. -libp2p streams are unbounded streams, whereas compression algorithms work best on bounded byte streams of which we have some prior knowledge. - -Compression tends not to be a one-size-fits-all problem. -A lot of variables need careful evaluation, and generic approaches/choices lead to poor size shavings, -which may even be counterproductive when factoring in the CPU and memory tradeoff. - -For all these reasons, generically negotiating compression algorithms may be treated as a research problem at the libp2p community, -one we’re happy to tackle in the medium-term. - -At this stage, the wisest choice is to consider libp2p a messenger of bytes, -and to make application layer participate in compressing those bytes. -This looks different depending on the interaction layer: - -- Gossip domain: since gossipsub has a framing protocol and exposes an API, we compress the payload - (when dictated by the encoding token in the topic name) prior to publishing the message via the API. - No length-prefixing is necessary because protobuf takes care of bounding the field in the serialized form. -- Req/Resp domain: since we define custom protocols that operate on byte streams, - implementers are encouraged to encapsulate the encoding and compression logic behind - MessageReader and MessageWriter components/strategies that can be layered on top of the raw byte streams. - -### Why are we using Snappy for compression? - -Snappy is used in Ethereum 1.0. It is well maintained by Google, has good benchmarks, -and can calculate the size of the uncompressed object without inflating it in memory. -This prevents DOS vectors where large uncompressed data is sent. - -### Can I get access to unencrypted bytes on the wire for debugging purposes? - -Yes, you can add loggers in your libp2p protocol handlers to log incoming and outgoing messages. -It is recommended to use programming design patterns to encapsulate the logging logic cleanly. - -If your libp2p library relies on frameworks/runtimes such as Netty (jvm) or Node.js (javascript), -you can use logging facilities in those frameworks/runtimes to enable message tracing. - -For specific ad-hoc testing scenarios, you can use the [plaintext/2.0.0 secure channel](https://github.com/libp2p/specs/blob/master/plaintext/README.md) -(which is essentially no-op encryption or message authentication), in combination with tcpdump or Wireshark to inspect the wire. - -### What are SSZ type size bounds? - -The SSZ encoding outputs of each type have size bounds: each dynamic type, such as a list, has a "limit", which can be used to compute the maximum valid output size. -Note that for some more complex dynamic-length objects, element offsets (4 bytes each) may need to be included. -Other types are static, they have a fixed size: no dynamic-length content is involved, and the minimum and maximum bounds are the same. - -For reference, the type bounds can be computed ahead of time, [as per this example](https://gist.github.com/protolambda/db75c7faa1e94f2464787a480e5d613e). -It is advisable to derive these lengths from the SSZ type definitions in use, to ensure that version changes do not cause out-of-sync type bounds. - -# libp2p implementations matrix - -This section will soon contain a matrix showing the maturity/state of the libp2p features required -by this spec across the languages in which clients are being developed. +1. Add `multiaddr` to the dictionary, so that nodes can advertise their + multiaddr under a reserved namespace in ENRs. – and/or – +2. Define a bi-directional conversion function between multiaddrs and the + corresponding denormalized fields in an ENR (ip, ip6, tcp, tcp6, etc.), for + compatibility with nodes that do not support multiaddr natively (e.g. + Ethereum execution-layer nodes). + +#### Why do we not form ENRs and find peers until genesis block/state is known? + +Although client software might very well be running locally prior to the +solidification of the beacon chain genesis state and block, clients cannot form +valid ENRs prior to this point. ENRs contain `fork_digest` which utilizes the +`genesis_validators_root` for a cleaner separation between chains so prior to +knowing genesis, we cannot use `fork_digest` to cleanly find peers on our +intended chain. Once genesis data is known, we can then form ENRs and safely +find peers. + +When using a proof-of-work deposit contract for deposits, `fork_digest` will be +known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`, +providing ample time to find peers and form initial connections and gossip +subnets prior to genesis. + +### Compression/Encoding + +#### Why are we using SSZ for encoding? + +SSZ is used at the consensus layer, and all implementations should have support +for SSZ-encoding/decoding, requiring no further dependencies to be added to +client implementations. This is a natural choice for serializing objects to be +sent across the wire. The actual data in most protocols will be further +compressed for efficiency. + +SSZ has well-defined schemas for consensus objects (typically sent across the +wire) reducing any serialization schema data that needs to be sent. It also has +defined all required types that are required for this network specification. + +#### Why are we compressing, and at which layers? + +We compress on the wire to achieve smaller payloads per-message, which, in +aggregate, result in higher efficiency, better utilization of available +bandwidth, and overall reduction in network-wide traffic overhead. + +At this time, libp2p does not have an out-of-the-box compression feature that +can be dynamically negotiated and layered atop connections and streams, but it +is [being considered](https://github.com/libp2p/libp2p/issues/81). + +This is a non-trivial feature because the behavior of network IO loops, kernel +buffers, chunking, and packet fragmentation, amongst others, need to be taken +into account. libp2p streams are unbounded streams, whereas compression +algorithms work best on bounded byte streams of which we have some prior +knowledge. + +Compression tends not to be a one-size-fits-all problem. A lot of variables need +careful evaluation, and generic approaches/choices lead to poor size shavings, +which may even be counterproductive when factoring in the CPU and memory +tradeoff. + +For all these reasons, generically negotiating compression algorithms may be +treated as a research problem at the libp2p community, one we’re happy to tackle +in the medium-term. + +At this stage, the wisest choice is to consider libp2p a messenger of bytes, and +to make application layer participate in compressing those bytes. This looks +different depending on the interaction layer: + +- Gossip domain: since gossipsub has a framing protocol and exposes an API, we + compress the payload (when dictated by the encoding token in the topic name) + prior to publishing the message via the API. No length-prefixing is necessary + because protobuf takes care of bounding the field in the serialized form. +- Req/Resp domain: since we define custom protocols that operate on byte + streams, implementers are encouraged to encapsulate the encoding and + compression logic behind MessageReader and MessageWriter components/strategies + that can be layered on top of the raw byte streams. + +#### Why are we using Snappy for compression? + +Snappy is used in Ethereum 1.0. It is well maintained by Google, has good +benchmarks, and can calculate the size of the uncompressed object without +inflating it in memory. This prevents DOS vectors where large uncompressed data +is sent. + +#### Can I get access to unencrypted bytes on the wire for debugging purposes? + +Yes, you can add loggers in your libp2p protocol handlers to log incoming and +outgoing messages. It is recommended to use programming design patterns to +encapsulate the logging logic cleanly. + +If your libp2p library relies on frameworks/runtimes such as Netty (jvm) or +Node.js (javascript), you can use logging facilities in those +frameworks/runtimes to enable message tracing. + +For specific ad-hoc testing scenarios, you can use the +[plaintext/2.0.0 secure channel](https://github.com/libp2p/specs/blob/master/plaintext/README.md) +(which is essentially no-op encryption or message authentication), in +combination with tcpdump or Wireshark to inspect the wire. + +#### What are SSZ type size bounds? + +The SSZ encoding outputs of each type have size bounds: each dynamic type, such +as a list, has a "limit", which can be used to compute the maximum valid output +size. Note that for some more complex dynamic-length objects, element offsets (4 +bytes each) may need to be included. Other types are static, they have a fixed +size: no dynamic-length content is involved, and the minimum and maximum bounds +are the same. + +For reference, the type bounds can be computed ahead of time, +[as per this example](https://gist.github.com/protolambda/db75c7faa1e94f2464787a480e5d613e). +It is advisable to derive these lengths from the SSZ type definitions in use, to +ensure that version changes do not cause out-of-sync type bounds. + +#### Why is the message size defined in terms of application payload? + +When transmitting messages over gossipsub and/or the req/resp domain, we want to +ensure that the same payload sizes are supported regardless of the underlying +transport, decoupling the consensus layer from libp2p-induced overhead and the +particular transmission strategy. + +To derive "encoded size limits" from desired application sizes, we take into +account snappy compression and framing overhead. + +In the case of gossipsub, the protocol supports sending multiple application +payloads as well as mixing application data with control messages in each +gossipsub frame. The limit is set such that at least one max-sized +application-level message together with a small amount (1 KiB) of gossipsub +overhead is allowed. Implementations are free to pack multiple smaller +application messages into a single gossipsub frame, and/or combine it with +control messages as they see fit. + +The limit is set on the uncompressed payload size in particular to protect +against decompression bombs. + +#### Why is there a limit on message sizes at all? + +The message size limit protects against several forms of DoS and network-based +amplification attacks and provides upper bounds for resource (network, memory) +usage in the client based on protocol requirements to decode, buffer, cache, +store and re-transmit messages which in turn translate into performance and +protection tradeoffs, ensuring capacity to handle worst cases during recovery +from network instability. + +In particular, blocks—-currently the only message type without a practical +SSZ-derived upper bound on size—-cannot be fully verified synchronously as part +of gossipsub validity checks. This means that there exist cases where invalid +messages signed by a validator may be amplified by the network. + +## libp2p implementations matrix + +This section will soon contain a matrix showing the maturity/state of the libp2p +features required by this spec across the languages in which clients are being +developed. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 814859c12a..0e3af5b37d 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -1,12 +1,10 @@ # Phase 0 -- Honest Validator -This is an accompanying document to [Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum proof-of-stake protocol. +This is an accompanying document to +[Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected +actions of a "validator" participating in the Ethereum proof-of-stake protocol. -## Table of contents - - - - + - [Introduction](#introduction) - [Prerequisites](#prerequisites) @@ -63,35 +61,46 @@ This is an accompanying document to [Phase 0 -- The Beacon Chain](./beacon-chain - [Aggregation bits](#aggregation-bits-1) - [Aggregate signature](#aggregate-signature-1) - [Broadcast aggregate](#broadcast-aggregate) -- [Phase 0 attestation subnet stability](#phase-0-attestation-subnet-stability) - [How to avoid slashing](#how-to-avoid-slashing) - [Proposer slashing](#proposer-slashing) - [Attester slashing](#attester-slashing) - [Protection best practices](#protection-best-practices) - - + ## Introduction -This document represents the expected behavior of an "honest validator" with respect to Phase 0 of the Ethereum proof-of-stake protocol. This document does not distinguish between a "node" (i.e. the functionality of following and reading the beacon chain) and a "validator client" (i.e. the functionality of actively participating in consensus). The separation of concerns between these (potentially) two pieces of software is left as a design decision that is out of scope. - -A validator is an entity that participates in the consensus of the Ethereum proof-of-stake protocol. This is an optional role for users in which they can post ETH as collateral and verify and attest to the validity of blocks to seek financial returns in exchange for building and securing the protocol. This is similar to proof-of-work networks in which miners provide collateral in the form of hardware/hash-power to seek returns in exchange for building and securing the protocol. +This document represents the expected behavior of an "honest validator" with +respect to Phase 0 of the Ethereum proof-of-stake protocol. This document does +not distinguish between a "node" (i.e. the functionality of following and +reading the beacon chain) and a "validator client" (i.e. the functionality of +actively participating in consensus). The separation of concerns between these +(potentially) two pieces of software is left as a design decision that is out of +scope. + +A validator is an entity that participates in the consensus of the Ethereum +proof-of-stake protocol. This is an optional role for users in which they can +post ETH as collateral and verify and attest to the validity of blocks to seek +financial returns in exchange for building and securing the protocol. This is +similar to proof-of-work networks in which miners provide collateral in the form +of hardware/hash-power to seek returns in exchange for building and securing the +protocol. ## Prerequisites -All terminology, constants, functions, and protocol mechanics defined in the [Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Deposit Contract](./deposit-contract.md) doc are requisite for this document and used throughout. Please see the Phase 0 doc before continuing and use as a reference throughout. +All terminology, constants, functions, and protocol mechanics defined in the +[Phase 0 -- The Beacon Chain](./beacon-chain.md) and +[Phase 0 -- Deposit Contract](./deposit-contract.md) doc are requisite for this +document and used throughout. Please see the Phase 0 doc before continuing and +use as a reference throughout. ## Constants ### Misc -| Name | Value | Unit | Duration | -| - | - | :-: | :-: | -| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | | -| `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | | -| `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours | -| `ATTESTATION_SUBNET_COUNT` | `64` | The number of attestation subnets used in the gossipsub protocol. | +| Name | Value | Unit | +| ---------------------------------- | ------------- | :--------: | +| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | ## Containers @@ -126,79 +135,119 @@ class SignedAggregateAndProof(Container): ### Initialization -A validator must initialize many parameters locally before submitting a deposit and joining the validator registry. +A validator must initialize many parameters locally before submitting a deposit +and joining the validator registry. #### BLS public key -Validator public keys are [G1 points](beacon-chain.md#bls-signatures) on the [BLS12-381 curve](https://z.cash/blog/new-snark-curve). A private key, `privkey`, must be securely generated along with the resultant `pubkey`. This `privkey` must be "hot", that is, constantly available to sign data throughout the lifetime of the validator. +Validator public keys are [G1 points](beacon-chain.md#bls-signatures) on the +[BLS12-381 curve](https://z.cash/blog/new-snark-curve). A private key, +`privkey`, must be securely generated along with the resultant `pubkey`. This +`privkey` must be "hot", that is, constantly available to sign data throughout +the lifetime of the validator. #### Withdrawal credentials -The `withdrawal_credentials` field constrains validator withdrawals. -The first byte of this 32-byte field is a withdrawal prefix which defines the semantics of the remaining 31 bytes. +The `withdrawal_credentials` field constrains validator withdrawals. The first +byte of this 32-byte field is a withdrawal prefix which defines the semantics of +the remaining 31 bytes. The following withdrawal prefixes are currently supported. ##### `BLS_WITHDRAWAL_PREFIX` Withdrawal credentials with the BLS withdrawal prefix allow a BLS key pair -`(bls_withdrawal_privkey, bls_withdrawal_pubkey)` to trigger withdrawals. -The `withdrawal_credentials` field must be such that: +`(bls_withdrawal_privkey, bls_withdrawal_pubkey)` to trigger withdrawals. The +`withdrawal_credentials` field must be such that: -* `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX` -* `withdrawal_credentials[1:] == hash(bls_withdrawal_pubkey)[1:]` +- `withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX` +- `withdrawal_credentials[1:] == hash(bls_withdrawal_pubkey)[1:]` -*Note*: The `bls_withdrawal_privkey` is not required for validating and can be kept in cold storage. +*Note*: The `bls_withdrawal_privkey` is not required for validating and can be +kept in cold storage. ##### `ETH1_ADDRESS_WITHDRAWAL_PREFIX` -Withdrawal credentials with the Eth1 address withdrawal prefix specify -a 20-byte Eth1 address `eth1_withdrawal_address` as the recipient for all withdrawals. -The `eth1_withdrawal_address` can be the address of either an externally owned account or of a contract. +Withdrawal credentials with the Eth1 address withdrawal prefix specify a 20-byte +Eth1 address `eth1_withdrawal_address` as the recipient for all withdrawals. The +`eth1_withdrawal_address` can be the address of either an externally owned +account or of a contract. The `withdrawal_credentials` field must be such that: -* `withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX` -* `withdrawal_credentials[1:12] == b'\x00' * 11` -* `withdrawal_credentials[12:] == eth1_withdrawal_address` +- `withdrawal_credentials[:1] == ETH1_ADDRESS_WITHDRAWAL_PREFIX` +- `withdrawal_credentials[1:12] == b'\x00' * 11` +- `withdrawal_credentials[12:] == eth1_withdrawal_address` -After the merge of the current Ethereum application layer into the Beacon Chain, -withdrawals to `eth1_withdrawal_address` will be normal ETH transfers (with no payload other than the validator's ETH) -triggered by a user transaction that will set the gas price and gas limit as well pay fees. -As long as the account or contract with address `eth1_withdrawal_address` can receive ETH transfers, -the future withdrawal protocol is agnostic to all other implementation details. +After the merge of the current Ethereum execution layer into the Beacon Chain, +withdrawals to `eth1_withdrawal_address` will simply be increases to the +account's ETH balance that do **NOT** trigger any EVM execution. ### Submit deposit -In Phase 0, all incoming validator deposits originate from the Ethereum proof-of-work chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. Deposits are made to the [deposit contract](./deposit-contract.md) located at `DEPOSIT_CONTRACT_ADDRESS`. +In Phase 0, all incoming validator deposits originate from the Ethereum +proof-of-work chain defined by `DEPOSIT_CHAIN_ID` and `DEPOSIT_NETWORK_ID`. +Deposits are made to the [deposit contract](./deposit-contract.md) located at +`DEPOSIT_CONTRACT_ADDRESS`. To submit a deposit: -- Pack the validator's [initialization parameters](#initialization) into `deposit_data`, a [`DepositData`](./beacon-chain.md#depositdata) SSZ object. -- Let `amount` be the amount in Gwei to be deposited by the validator where `amount >= MIN_DEPOSIT_AMOUNT`. +- Pack the validator's [initialization parameters](#initialization) into + `deposit_data`, a [`DepositData`](./beacon-chain.md#depositdata) SSZ object. +- Let `amount` be the amount in Gwei to be deposited by the validator where + `amount >= MIN_DEPOSIT_AMOUNT`. - Set `deposit_data.pubkey` to validator's `pubkey`. - Set `deposit_data.withdrawal_credentials` to `withdrawal_credentials`. - Set `deposit_data.amount` to `amount`. -- Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`. -- Let `signature` be the result of `bls.Sign` of the `compute_signing_root(deposit_message, domain)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (_Warning_: Deposits _must_ be signed with `GENESIS_FORK_VERSION`, calling `compute_domain` without a second argument defaults to the correct version). +- Let `deposit_message` be a `DepositMessage` with all the `DepositData` + contents except the `signature`. +- Let `signature` be the result of `bls.Sign` of the + `compute_signing_root(deposit_message, domain)` with + `domain=compute_domain(DOMAIN_DEPOSIT)`. (_Warning_: Deposits _must_ be signed + with `GENESIS_FORK_VERSION`, calling `compute_domain` without a second + argument defaults to the correct version). - Let `deposit_data_root` be `hash_tree_root(deposit_data)`. -- Send a transaction on the Ethereum proof-of-work chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei. +- Send a transaction on the Ethereum proof-of-work chain to + `DEPOSIT_CONTRACT_ADDRESS` executing + `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` + along with a deposit of `amount` Gwei. -*Note*: Deposits made for the same `pubkey` are treated as for the same validator. A singular `Validator` will be added to `state.validators` with each additional deposit amount added to the validator's balance. A validator can only be activated when total deposits for the validator pubkey meet or exceed `MAX_EFFECTIVE_BALANCE`. +*Note*: Deposits made for the same `pubkey` are treated as for the same +validator. A singular `Validator` will be added to `state.validators` with each +additional deposit amount added to the validator's balance. A validator can only +be activated when total deposits for the validator pubkey meet or exceed +`MAX_EFFECTIVE_BALANCE`. ### Process deposit -Deposits cannot be processed into the beacon chain until the proof-of-work block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once the requisite proof-of-work block data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. +Deposits cannot be processed into the beacon chain until the proof-of-work block +in which they were deposited or any of its descendants is added to the beacon +chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 +blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once +the requisite proof-of-work block data is added, the deposit will normally be +added to a beacon chain block and processed into the `state.validators` within +an epoch or two. The validator is then in a queue to be activated. ### Validator index -Once a validator has been processed and added to the beacon state's `validators`, the validator's `validator_index` is defined by the index into the registry at which the [`ValidatorRecord`](./beacon-chain.md#validator) contains the `pubkey` specified in the validator's deposit. A validator's `validator_index` is guaranteed to not change from the time of initial deposit until the validator exits and fully withdraws. This `validator_index` is used throughout the specification to dictate validator roles and responsibilities at any point and should be stored locally. +Once a validator has been processed and added to the beacon state's +`validators`, the validator's `validator_index` is defined by the index into the +registry at which the [`ValidatorRecord`](./beacon-chain.md#validator) contains +the `pubkey` specified in the validator's deposit. A validator's +`validator_index` is guaranteed to not change from the time of initial deposit +until the validator exits and fully withdraws. This `validator_index` is used +throughout the specification to dictate validator roles and responsibilities at +any point and should be stored locally. ### Activation -In normal operation, the validator is quickly activated, at which point the validator is added to the shuffling and begins validation after an additional `MAX_SEED_LOOKAHEAD` epochs (25.6 minutes). +In normal operation, the validator is quickly activated, at which point the +validator is added to the shuffling and begins validation after an additional +`MAX_SEED_LOOKAHEAD` epochs (25.6 minutes). -The function [`is_active_validator`](./beacon-chain.md#is_active_validator) can be used to check if a validator is active during a given epoch. Usage is as follows: +The function [`is_active_validator`](./beacon-chain.md#is_active_validator) can +be used to check if a validator is active during a given epoch. Usage is as +follows: ```python def check_if_validator_active(state: BeaconState, validator_index: ValidatorIndex) -> bool: @@ -206,19 +255,23 @@ def check_if_validator_active(state: BeaconState, validator_index: ValidatorInde return is_active_validator(validator, get_current_epoch(state)) ``` -Once a validator is activated, the validator is assigned [responsibilities](#beacon-chain-responsibilities) until exited. +Once a validator is activated, the validator is assigned +[responsibilities](#beacon-chain-responsibilities) until exited. -*Note*: There is a maximum validator churn per finalized epoch, so the delay until activation is variable depending upon finality, total active validator balance, and the number of validators in the queue to be activated. +*Note*: There is a maximum validator churn per finalized epoch, so the delay +until activation is variable depending upon finality, total active validator +balance, and the number of validators in the queue to be activated. ## Validator assignments -A validator can get committee assignments for a given epoch using the following helper via `get_committee_assignment(state, epoch, validator_index)` where `epoch <= next_epoch`. +A validator can get committee assignments for a given epoch using the following +helper via `get_committee_assignment(state, epoch, validator_index)` where +`epoch <= next_epoch`. ```python -def get_committee_assignment(state: BeaconState, - epoch: Epoch, - validator_index: ValidatorIndex - ) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: +def get_committee_assignment( + state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex +) -> Optional[Tuple[Sequence[ValidatorIndex], CommitteeIndex, Slot]]: """ Return the committee assignment in the ``epoch`` for ``validator_index``. ``assignment`` returned is a tuple of the following form: @@ -240,70 +293,120 @@ def get_committee_assignment(state: BeaconState, return None ``` -A validator can use the following function to see if they are supposed to propose during a slot. This function can only be run with a `state` of the slot in question. Proposer selection is only stable within the context of the current epoch. +A validator can use the following function to see if they are supposed to +propose during a slot. This function can only be run with a `state` of the slot +in question. Proposer selection is only stable within the context of the current +epoch. ```python def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool: return get_beacon_proposer_index(state) == validator_index ``` -*Note*: To see if a validator is assigned to propose during the slot, the beacon state must be in the epoch in question. At the epoch boundaries, the validator must run an epoch transition into the epoch to successfully check the proposal assignment of the first slot. +*Note*: To see if a validator is assigned to propose during the slot, the beacon +state must be in the epoch in question. At the epoch boundaries, the validator +must run an epoch transition into the epoch to successfully check the proposal +assignment of the first slot. -*Note*: `BeaconBlock` proposal is distinct from beacon committee assignment, and in a given epoch each responsibility might occur at a different slot. +*Note*: `BeaconBlock` proposal is distinct from beacon committee assignment, and +in a given epoch each responsibility might occur at a different slot. ### Lookahead -The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead -on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. -Note that this lookahead does not apply to proposing, which must be checked during the epoch in question. +The beacon chain shufflings are designed to provide a minimum of 1 epoch +lookahead on the validator's upcoming committee assignments for attesting +dictated by the shuffling and slot. Note that this lookahead does not apply to +proposing, which must be checked during the epoch in question. -`get_committee_assignment` should be called at the start of each epoch -to get the assignment for the next epoch (`current_epoch + 1`). -A validator should plan for future assignments by noting their assigned attestation -slot and joining the committee index attestation subnet related to their committee assignment. +`get_committee_assignment` should be called at the start of each epoch to get +the assignment for the next epoch (`current_epoch + 1`). A validator should plan +for future assignments by noting their assigned attestation slot and joining the +committee index attestation subnet related to their committee assignment. Specifically a validator should: -* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments. -* Calculate the committees per slot for the next epoch: `committees_per_slot = get_committee_count_per_slot(state, next_epoch)` -* Calculate the subnet index: `subnet_id = compute_subnet_for_attestation(committees_per_slot, slot, committee_index)` -* Find peers of the pubsub topic `beacon_attestation_{subnet_id}`. - * If an _insufficient_ number of current peers are subscribed to the topic, the validator must discover new peers on this topic. Via the discovery protocol, find peers with an ENR containing the `attnets` entry such that `ENR["attnets"][subnet_id] == True`. Then validate that the peers are still persisted on the desired topic by requesting `GetMetaData` and checking the resulting `attnets` field. - * If the validator is assigned to be an aggregator for the slot (see `is_aggregator()`), then subscribe to the topic. -*Note*: If the validator is _not_ assigned to be an aggregator, the validator only needs sufficient number of peers on the topic to be able to publish messages. The validator does not need to _subscribe_ and listen to all messages on the topic. +- Call + `_, committee_index, _ = get_committee_assignment(state, next_epoch, validator_index)` + when checking for next epoch assignments. +- Calculate the committees per slot for the next epoch: + `committees_per_slot = get_committee_count_per_slot(state, next_epoch)` +- Calculate the subnet index: + `subnet_id = compute_subnet_for_attestation(committees_per_slot, slot, committee_index)` +- Find peers of the pubsub topic `beacon_attestation_{subnet_id}`. + - If an _insufficient_ number of current peers are subscribed to the topic, + the validator must discover new peers on this topic. Via the discovery + protocol, find peers with an ENR containing the `attnets` entry such that + `ENR["attnets"][subnet_id] == True`. Then validate that the peers are still + persisted on the desired topic by requesting `GetMetaData` and checking the + resulting `attnets` field. + - If the validator is assigned to be an aggregator for the slot (see + `is_aggregator()`), then subscribe to the topic. + +*Note*: If the validator is _not_ assigned to be an aggregator, the validator +only needs sufficient number of peers on the topic to be able to publish +messages. The validator does not need to _subscribe_ and listen to all messages +on the topic. ## Beacon chain responsibilities -A validator has two primary responsibilities to the beacon chain: [proposing blocks](#block-proposal) and [creating attestations](#attestations-1). Proposals happen infrequently, whereas attestations should be created once per epoch. +A validator has two primary responsibilities to the beacon chain: +[proposing blocks](#block-proposal) and [creating attestations](#attesting). +Proposals happen infrequently, whereas attestations should be created once per +epoch. ### Block proposal -A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at -the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. -To propose, the validator selects the `BeaconBlock`, `parent`, -that in their view of the fork choice is the head of the chain during `slot - 1`. -The validator creates, signs, and broadcasts a `block` that is a child of `parent` -that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function). - -There is one proposer per slot, so if there are N active validators any individual validator -will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks). - -*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied. -That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`. +A validator is expected to propose a +[`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at the beginning of +any `slot` during which `is_proposer(state, validator_index)` returns `True`. + +To propose, the validator selects a `BeaconBlock`, `parent` using this process: + +1. Compute fork choice's view of the head at the start of `slot`, after running + `on_tick` and applying any queued attestations from `slot - 1`. Set + `head_root = get_head(store)`. +2. Compute the _proposer head_, which is the head upon which the proposer SHOULD + build in order to incentivise timely block propagation by other validators. + Set `parent_root = get_proposer_head(store, head_root, slot)`. A proposer may + set `parent_root == head_root` if proposer re-orgs are not implemented or + have been disabled. +3. Let `parent` be the block with `parent_root`. + +The validator creates, signs, and broadcasts a `block` that is a child of +`parent` and satisfies a valid +[beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function). +Note that the parent's slot must be strictly less than the slot of the block +about to be proposed, i.e. `parent.slot < slot`. + +There is one proposer per slot, so if there are N active validators any +individual validator will on average be assigned to propose once per N slots +(e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks). + +*Note*: In this section, `state` is the state of the slot for the block proposal +_without_ the block yet applied. That is, `state` is the `previous_state` +processed through any empty slots up to the assigned slot using +`process_slots(previous_state, slot)`. #### Preparing for a `BeaconBlock` -To construct a `BeaconBlockBody`, a `block` (`BeaconBlock`) is defined with the necessary context for a block proposal: +To construct a `BeaconBlockBody`, a `block` (`BeaconBlock`) is defined with the +necessary context for a block proposal: ##### Slot -Set `block.slot = slot` where `slot` is the current slot at which the validator has been selected to propose. The `parent` selected must satisfy that `parent.slot < block.slot`. +Set `block.slot = slot` where `slot` is the current slot at which the validator +has been selected to propose. The `parent` selected must satisfy that +`parent.slot < block.slot`. -*Note*: There might be "skipped" slots between the `parent` and `block`. These skipped slots are processed in the state transition function without per-block processing. +*Note*: There might be "skipped" slots between the `parent` and `block`. These +skipped slots are processed in the state transition function without per-block +processing. ##### Proposer index -Set `block.proposer_index = validator_index` where `validator_index` is the validator chosen to propose at this slot. The private key mapping to `state.validators[validator_index].pubkey` is used to sign the block. +Set `block.proposer_index = validator_index` where `validator_index` is the +validator chosen to propose at this slot. The private key mapping to +`state.validators[validator_index].pubkey` is used to sign the block. ##### Parent root @@ -313,7 +416,8 @@ Set `block.parent_root = hash_tree_root(parent)`. ##### Randao reveal -Set `block.body.randao_reveal = epoch_signature` where `epoch_signature` is obtained from: +Set `block.body.randao_reveal = epoch_signature` where `epoch_signature` is +obtained from: ```python def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: @@ -324,30 +428,31 @@ def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> ##### Eth1 Data -The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. -This recent data contains an Eth1 block hash as well as the associated deposit root -(as calculated by the `get_deposit_root()` method of the deposit contract) and -deposit count after execution of the corresponding Eth1 block. -If over half of the block proposers in the current Eth1 voting period vote for the same -`eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed. -Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`. +The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 +data. This recent data contains an Eth1 block hash as well as the associated +deposit root (as calculated by the `get_deposit_root()` method of the deposit +contract) and deposit count after execution of the corresponding Eth1 block. If +over half of the block proposers in the current Eth1 voting period vote for the +same `eth1_data` then `state.eth1_data` updates immediately allowing new +deposits to be processed. Each deposit in `block.body.deposits` must verify +against `state.eth1_data.eth1_deposit_root`. ###### `get_eth1_data` -Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` and depost contract data available. +Let `Eth1Block` be an abstract object representing Eth1 blocks with the +`timestamp` and deposit contract data available. -Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block. +Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns +the Eth1 data for a given Eth1 block. -An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, eth1_chain)` where: - -```python -def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: - return uint64(state.genesis_time + slot * SECONDS_PER_SLOT) -``` +An honest block proposer sets +`block.body.eth1_data = get_eth1_vote(state, eth1_chain)` where: ```python def voting_period_start_time(state: BeaconState) -> uint64: - eth1_voting_period_start_slot = Slot(state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH)) + eth1_voting_period_start_slot = Slot( + state.slot - state.slot % (EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH) + ) return compute_time_at_slot(state, eth1_voting_period_start_slot) ``` @@ -364,7 +469,8 @@ def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Da period_start = voting_period_start_time(state) # `eth1_chain` abstractly represents all blocks in the eth1 chain sorted by ascending block height votes_to_consider = [ - get_eth1_data(block) for block in eth1_chain + get_eth1_data(block) + for block in eth1_chain if ( is_candidate_block(block, period_start) # Ensure cannot move back to earlier deposit contract states @@ -378,36 +484,73 @@ def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Da # Default vote on latest eth1 block data in the period range unless eth1 chain is not live # Non-substantive casting for linter state_eth1_data: Eth1Data = state.eth1_data - default_vote = votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + default_vote = ( + votes_to_consider[len(votes_to_consider) - 1] if any(votes_to_consider) else state_eth1_data + ) return max( valid_votes, - key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance - default=default_vote + # Tiebreak by smallest distance + key=lambda v: ( + valid_votes.count(v), + -valid_votes.index(v), + ), + default=default_vote, ) ``` ##### Proposer slashings -Up to `MAX_PROPOSER_SLASHINGS`, [`ProposerSlashing`](./beacon-chain.md#proposerslashing) objects can be included in the `block`. The proposer slashings must satisfy the verification conditions found in [proposer slashings processing](./beacon-chain.md#proposer-slashings). The validator receives a small "whistleblower" reward for each proposer slashing found and included. +Up to `MAX_PROPOSER_SLASHINGS`, +[`ProposerSlashing`](./beacon-chain.md#proposerslashing) objects can be included +in the `block`. The proposer slashings must satisfy the verification conditions +found in [proposer slashings processing](./beacon-chain.md#proposer-slashings). +The validator receives a small "whistleblower" reward for each proposer slashing +found and included. ##### Attester slashings -Up to `MAX_ATTESTER_SLASHINGS`, [`AttesterSlashing`](./beacon-chain.md#attesterslashing) objects can be included in the `block`. The attester slashings must satisfy the verification conditions found in [attester slashings processing](./beacon-chain.md#attester-slashings). The validator receives a small "whistleblower" reward for each attester slashing found and included. +Up to `MAX_ATTESTER_SLASHINGS`, +[`AttesterSlashing`](./beacon-chain.md#attesterslashing) objects can be included +in the `block`. The attester slashings must satisfy the verification conditions +found in [attester slashings processing](./beacon-chain.md#attester-slashings). +The validator receives a small "whistleblower" reward for each attester slashing +found and included. ##### Attestations -Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. The attestations added must satisfy the verification conditions found in [attestation processing](./beacon-chain.md#attestations). To maximize profit, the validator should attempt to gather aggregate attestations that include singular attestations from the largest number of validators whose signatures from the same epoch have not previously been added on chain. +Up to `MAX_ATTESTATIONS`, aggregate attestations can be included in the `block`. +The attestations added must satisfy the verification conditions found in +[attestation processing](./beacon-chain.md#attestations). To maximize profit, +the validator should attempt to gather aggregate attestations that include +singular attestations from the largest number of validators whose signatures +from the same epoch have not previously been added on chain. ##### Deposits -If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. `state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending deposits _must_ be added to the block. The expected number of deposits is exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. These [`deposits`](./beacon-chain.md#deposit) are constructed from the `Deposit` logs from the [deposit contract](./deposit-contract.md) and must be processed in sequential order. The deposits included in the `block` must satisfy the verification conditions found in [deposits processing](./beacon-chain.md#deposits). - -The `proof` for each deposit must be constructed against the deposit root contained in `state.eth1_data` rather than the deposit root at the time the deposit was initially logged from the proof-of-work chain. This entails storing a full deposit merkle tree locally and computing updated proofs against the `eth1_data.deposit_root` as needed. See [`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) for a sample implementation. +If there are any unprocessed deposits for the existing `state.eth1_data` (i.e. +`state.eth1_data.deposit_count > state.eth1_deposit_index`), then pending +deposits _must_ be added to the block. The expected number of deposits is +exactly `min(MAX_DEPOSITS, eth1_data.deposit_count - state.eth1_deposit_index)`. +These [`deposits`](./beacon-chain.md#deposit) are constructed from the `Deposit` +logs from the [deposit contract](./deposit-contract.md) and must be processed in +sequential order. The deposits included in the `block` must satisfy the +verification conditions found in +[deposits processing](./beacon-chain.md#deposits). + +The `proof` for each deposit must be constructed against the deposit root +contained in `state.eth1_data` rather than the deposit root at the time the +deposit was initially logged from the proof-of-work chain. This entails storing +a full deposit merkle tree locally and computing updated proofs against the +`eth1_data.deposit_root` as needed. See +[`minimal_merkle.py`](https://github.com/ethereum/research/blob/master/spec_pythonizer/utils/merkle_minimal.py) +for a sample implementation. ##### Voluntary exits -Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits). +Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) +objects can be included in the `block`. The exits must satisfy the verification +conditions found in [exits processing](./beacon-chain.md#voluntary-exits). *Note*: If a slashing for a validator is included in the same block as a voluntary exit, the voluntary exit will fail and cause the block to be invalid @@ -418,10 +561,14 @@ operation interaction when packing blocks. ##### State root -Set `block.state_root = hash_tree_root(state)` of the resulting `state` of the `parent -> block` state transition. +Set `block.state_root = hash_tree_root(state)` of the resulting `state` of the +`parent -> block` state transition. -*Note*: To calculate `state_root`, the validator should first run the state transition function on an unsigned `block` containing a stub for the `state_root`. -It is useful to be able to run a state transition function (working on a copy of the state) that does _not_ validate signatures or state root for this purpose: +*Note*: To calculate `state_root`, the validator should first run the state +transition function on an unsigned `block` containing a stub for the +`state_root`. It is useful to be able to run a state transition function +(working on a copy of the state) that does _not_ validate signatures or state +root for this purpose: ```python def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: @@ -433,7 +580,8 @@ def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: ##### Signature -`signed_block = SignedBeaconBlock(message=block, signature=block_signature)`, where `block_signature` is obtained from: +`signed_block = SignedBeaconBlock(message=block, signature=block_signature)`, +where `block_signature` is obtained from: ```python def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> BLSSignature: @@ -444,23 +592,38 @@ def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) -> ### Attesting -A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`. +A validator is expected to create, sign, and broadcast an attestation during +each epoch. The `committee`, assigned `index`, and assigned `slot` for which the +validator performs this role during an epoch are defined by +`get_committee_assignment(state, epoch, validator_index)`. -A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid block from the expected block proposer for the assigned `slot` or (b) one-third of the `slot` has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_. +A validator should create and broadcast the `attestation` to the associated +attestation subnet when either (a) the validator has received a valid block from +the expected block proposer for the assigned `slot` or (b) +`1 / INTERVALS_PER_SLOT` of the `slot` has transpired +(`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of `slot`) -- +whichever comes _first_. -*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded, and should be made. +*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG +finality, these initial attestations do give weight to the fork choice, are +rewarded, and should be made. #### Attestation data -First, the validator should construct `attestation_data`, an [`AttestationData`](./beacon-chain.md#attestationdata) object based upon the state at the assigned slot. +First, the validator should construct `attestation_data`, an +[`AttestationData`](./beacon-chain.md#attestationdata) object based upon the +state at the assigned slot. -- Let `head_block` be the result of running the fork choice during the assigned slot. -- Let `head_state` be the state of `head_block` processed through any empty slots up to the assigned slot using `process_slots(state, slot)`. +- Let `head_block` be the result of running the fork choice during the assigned + slot. +- Let `head_state` be the state of `head_block` processed through any empty + slots up to the assigned slot using `process_slots(state, slot)`. ##### General -* Set `attestation_data.slot = slot` where `slot` is the assigned slot. -* Set `attestation_data.index = index` where `index` is the index associated with the validator's committee. +- Set `attestation_data.slot = slot` where `slot` is the assigned slot. +- Set `attestation_data.index = index` where `index` is the index associated + with the validator's committee. ##### LMD GHOST vote @@ -469,33 +632,46 @@ Set `attestation_data.beacon_block_root = hash_tree_root(head_block)`. ##### FFG vote - Set `attestation_data.source = head_state.current_justified_checkpoint`. -- Set `attestation_data.target = Checkpoint(epoch=get_current_epoch(head_state), root=epoch_boundary_block_root)` where `epoch_boundary_block_root` is the root of block at the most recent epoch boundary. +- Set + `attestation_data.target = Checkpoint(epoch=get_current_epoch(head_state), root=epoch_boundary_block_root)` + where `epoch_boundary_block_root` is the root of block at the most recent + epoch boundary. *Note*: `epoch_boundary_block_root` can be looked up in the state using: - Let `start_slot = compute_start_slot_at_epoch(get_current_epoch(head_state))`. -- Let `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, get_current_epoch(head_state))`. +- Let + `epoch_boundary_block_root = hash_tree_root(head_block) if start_slot == head_state.slot else get_block_root(state, get_current_epoch(head_state))`. #### Construct attestation -Next, the validator creates `attestation`, an [`Attestation`](./beacon-chain.md#attestation) object. +Next, the validator creates `attestation`, an +[`Attestation`](./beacon-chain.md#attestation) object. ##### Data -Set `attestation.data = attestation_data` where `attestation_data` is the `AttestationData` object defined in the previous section, [attestation data](#attestation-data). +Set `attestation.data = attestation_data` where `attestation_data` is the +`AttestationData` object defined in the previous section, +[attestation data](#attestation-data). ##### Aggregation bits -- Let `attestation.aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where the bit of the index of the validator in the `committee` is set to `0b1`. +- Let `attestation.aggregation_bits` be a + `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where the + bit of the index of the validator in the `committee` is set to `0b1`. -*Note*: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bits)` should return a list of length equal to 1, containing `validator_index`. +*Note*: Calling `get_attesting_indices(state, attestation)` should return a list +of length equal to 1, containing `validator_index`. ##### Aggregate signature -Set `attestation.signature = attestation_signature` where `attestation_signature` is obtained from: +Set `attestation.signature = attestation_signature` where +`attestation_signature` is obtained from: ```python -def get_attestation_signature(state: BeaconState, attestation_data: AttestationData, privkey: int) -> BLSSignature: +def get_attestation_signature( + state: BeaconState, attestation_data: AttestationData, privkey: int +) -> BLSSignature: domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation_data.target.epoch) signing_root = compute_signing_root(attestation_data, domain) return bls.Sign(privkey, signing_root) @@ -503,14 +679,20 @@ def get_attestation_signature(state: BeaconState, attestation_data: AttestationD #### Broadcast attestation -Finally, the validator broadcasts `attestation` to the associated attestation subnet, the `beacon_attestation_{subnet_id}` pubsub topic. +Finally, the validator broadcasts `attestation` to the associated attestation +subnet, the `beacon_attestation_{subnet_id}` pubsub topic. The `subnet_id` for the `attestation` is calculated with: -- Let `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`. -- Let `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.committee_index)`. + +- Let + `committees_per_slot = get_committee_count_per_slot(state, attestation.data.target.epoch)`. +- Let + `subnet_id = compute_subnet_for_attestation(committees_per_slot, attestation.data.slot, attestation.data.index)`. ```python -def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex) -> uint64: +def compute_subnet_for_attestation( + committees_per_slot: uint64, slot: Slot, committee_index: CommitteeIndex +) -> SubnetID: """ Compute the correct subnet for an attestation for Phase 0. Note, this mimics expected future behavior where attestations will be mapped to their shard subnet. @@ -518,16 +700,18 @@ def compute_subnet_for_attestation(committees_per_slot: uint64, slot: Slot, comm slots_since_epoch_start = uint64(slot % SLOTS_PER_EPOCH) committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - return uint64((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) + return SubnetID((committees_since_epoch_start + committee_index) % ATTESTATION_SUBNET_COUNT) ``` ### Attestation aggregation -Some validators are selected to locally aggregate attestations with a similar `attestation_data` to their constructed `attestation` for the assigned `slot`. +Some validators are selected to locally aggregate attestations with a similar +`attestation_data` to their constructed `attestation` for the assigned `slot`. #### Aggregation selection -A validator is selected to aggregate based upon the return value of `is_aggregator()`. +A validator is selected to aggregate based upon the return value of +`is_aggregator()`. ```python def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSignature: @@ -537,7 +721,9 @@ def get_slot_signature(state: BeaconState, slot: Slot, privkey: int) -> BLSSigna ``` ```python -def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature) -> bool: +def is_aggregator( + state: BeaconState, slot: Slot, index: CommitteeIndex, slot_signature: BLSSignature +) -> bool: committee = get_beacon_committee(state, slot, index) modulo = max(1, len(committee) // TARGET_AGGREGATORS_PER_COMMITTEE) return bytes_to_uint64(hash(slot_signature)[0:8]) % modulo == 0 @@ -545,21 +731,30 @@ def is_aggregator(state: BeaconState, slot: Slot, index: CommitteeIndex, slot_si #### Construct aggregate -If the validator is selected to aggregate (`is_aggregator()`), they construct an aggregate attestation via the following. +If the validator is selected to aggregate (`is_aggregator()`), they construct an +aggregate attestation via the following. -Collect `attestations` seen via gossip during the `slot` that have an equivalent `attestation_data` to that constructed by the validator. If `len(attestations) > 0`, create an `aggregate_attestation: Attestation` with the following fields. +Collect `attestations` seen via gossip during the `slot` that have an equivalent +`attestation_data` to that constructed by the validator. If +`len(attestations) > 0`, create an `aggregate_attestation: Attestation` with the +following fields. ##### Data -Set `aggregate_attestation.data = attestation_data` where `attestation_data` is the `AttestationData` object that is the same for each individual attestation being aggregated. +Set `aggregate_attestation.data = attestation_data` where `attestation_data` is +the `AttestationData` object that is the same for each individual attestation +being aggregated. ##### Aggregation bits -Let `aggregate_attestation.aggregation_bits` be a `Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where each bit set from each individual attestation is set to `0b1`. +Let `aggregate_attestation.aggregation_bits` be a +`Bitlist[MAX_VALIDATORS_PER_COMMITTEE]` of length `len(committee)`, where each +bit set from each individual attestation is set to `0b1`. ##### Aggregate signature -Set `aggregate_attestation.signature = aggregate_signature` where `aggregate_signature` is obtained from: +Set `aggregate_attestation.signature = aggregate_signature` where +`aggregate_signature` is obtained from: ```python def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature: @@ -569,19 +764,27 @@ def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature #### Broadcast aggregate -If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`. +If the validator is selected to aggregate (`is_aggregator`), then they broadcast +their best aggregate as a `SignedAggregateAndProof` to the global aggregate +channel (`beacon_aggregate_and_proof`) `2 / INTERVALS_PER_SLOT` of the way +through the `slot`-that is, `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` seconds +after the start of `slot`. -Selection proofs are provided in `AggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator. +Selection proofs are provided in `AggregateAndProof` to prove to the gossip +channel that the validator has been selected as an aggregator. -`AggregateAndProof` messages are signed by the aggregator and broadcast inside of `SignedAggregateAndProof` objects to prevent a class of DoS attacks and message forgeries. +`AggregateAndProof` messages are signed by the aggregator and broadcast inside +of `SignedAggregateAndProof` objects to prevent a class of DoS attacks and +message forgeries. -First, `aggregate_and_proof = get_aggregate_and_proof(state, validator_index, aggregate_attestation, privkey)` is constructed. +First, +`aggregate_and_proof = get_aggregate_and_proof(state, validator_index, aggregate_attestation, privkey)` +is constructed. ```python -def get_aggregate_and_proof(state: BeaconState, - aggregator_index: ValidatorIndex, - aggregate: Attestation, - privkey: int) -> AggregateAndProof: +def get_aggregate_and_proof( + state: BeaconState, aggregator_index: ValidatorIndex, aggregate: Attestation, privkey: int +) -> AggregateAndProof: return AggregateAndProof( aggregator_index=aggregator_index, aggregate=aggregate, @@ -589,66 +792,102 @@ def get_aggregate_and_proof(state: BeaconState, ) ``` -Then `signed_aggregate_and_proof = SignedAggregateAndProof(message=aggregate_and_proof, signature=signature)` is constructed and broadcast. Where `signature` is obtained from: +Then +`signed_aggregate_and_proof = SignedAggregateAndProof(message=aggregate_and_proof, signature=signature)` +is constructed and broadcast. Where `signature` is obtained from: ```python -def get_aggregate_and_proof_signature(state: BeaconState, - aggregate_and_proof: AggregateAndProof, - privkey: int) -> BLSSignature: +def get_aggregate_and_proof_signature( + state: BeaconState, aggregate_and_proof: AggregateAndProof, privkey: int +) -> BLSSignature: aggregate = aggregate_and_proof.aggregate - domain = get_domain(state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot)) + domain = get_domain( + state, DOMAIN_AGGREGATE_AND_PROOF, compute_epoch_at_slot(aggregate.data.slot) + ) signing_root = compute_signing_root(aggregate_and_proof, domain) return bls.Sign(privkey, signing_root) ``` -## Phase 0 attestation subnet stability - -Because Phase 0 does not have shards and thus does not have Shard Committees, there is no stable backbone to the attestation subnets (`beacon_attestation_{subnet_id}`). To provide this stability, each validator must: - -* Randomly select and remain subscribed to `RANDOM_SUBNETS_PER_VALIDATOR` attestation subnets -* Maintain advertisement of the randomly selected subnets in their node's ENR `attnets` entry by setting the randomly selected `subnet_id` bits to `True` (e.g. `ENR["attnets"][subnet_id] = True`) for all persistent attestation subnets -* Set the lifetime of each random subscription to a random number of epochs between `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` and `2 * EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION]`. At the end of life for a subscription, select a new random subnet, update subnet subscriptions, and publish an updated ENR - -*Note*: Short lived beacon committee assignments should not be added in into the ENR `attnets` entry. - -*Note*: When preparing for a hard fork, a validator must select and subscribe to random subnets of the future fork versioning at least `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` epochs in advance of the fork. These new subnets for the fork are maintained in addition to those for the current fork until the fork occurs. After the fork occurs, let the subnets from the previous fork reach the end of life with no replacements. - ## How to avoid slashing -"Slashing" is the burning of some amount of validator funds and immediate ejection from the active validator set. In Phase 0, there are two ways in which funds can be slashed: [proposer slashing](#proposer-slashing) and [attester slashing](#attester-slashing). Although being slashed has serious repercussions, it is simple enough to avoid being slashed all together by remaining _consistent_ with respect to the messages a validator has previously signed. +"Slashing" is the burning of some amount of validator funds and immediate +ejection from the active validator set. In Phase 0, there are two ways in which +funds can be slashed: [proposer slashing](#proposer-slashing) and +[attester slashing](#attester-slashing). Although being slashed has serious +repercussions, it is simple enough to avoid being slashed all together by +remaining _consistent_ with respect to the messages a validator has previously +signed. -*Note*: Signed data must be within a sequential `Fork` context to conflict. Messages cannot be slashed across diverging forks. If the previous fork version is 1 and the chain splits into fork 2 and 102, messages from 1 can slashable against messages in forks 1, 2, and 102. Messages in 2 cannot be slashable against messages in 102, and vice versa. +*Note*: Signed data must be within a sequential `Fork` context to conflict. +Messages cannot be slashed across diverging forks. If the previous fork version +is 1 and the chain splits into fork 2 and 102, messages from 1 can be slashable +against messages in forks 1, 2, and 102. Messages in 2 cannot be slashable +against messages in 102, and vice versa. ### Proposer slashing -To avoid "proposer slashings", a validator must not sign two conflicting [`BeaconBlock`](./beacon-chain.md#beaconblock) where conflicting is defined as two distinct blocks within the same slot. +To avoid "proposer slashings", a validator must not sign two conflicting +[`BeaconBlock`](./beacon-chain.md#beaconblock) where conflicting is defined as +two distinct blocks within the same slot. -*In Phase 0, as long as the validator does not sign two different beacon blocks for the same slot, the validator is safe against proposer slashings.* +*In Phase 0, as long as the validator does not sign two different beacon blocks +for the same slot, the validator is safe against proposer slashings.* -Specifically, when signing a `BeaconBlock`, a validator should perform the following steps in the following order: +Specifically, when signing a `BeaconBlock`, a validator should perform the +following steps in the following order: -1. Save a record to hard disk that a beacon block has been signed for the `slot=block.slot`. +1. Save a record to hard disk that a beacon block has been signed for the + `slot=block.slot`. 2. Generate and broadcast the block. -If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast block and can effectively avoid slashing. +If the software crashes at some point within this routine, then when the +validator comes back online, the hard disk has the record of the *potentially* +signed/broadcast block and can effectively avoid slashing. ### Attester slashing -To avoid "attester slashings", a validator must not sign two conflicting [`AttestationData`](./beacon-chain.md#attestationdata) objects, i.e. two attestations that satisfy [`is_slashable_attestation_data`](./beacon-chain.md#is_slashable_attestation_data). +To avoid "attester slashings", a validator must not sign two conflicting +[`AttestationData`](./beacon-chain.md#attestationdata) objects, i.e. two +attestations that satisfy +[`is_slashable_attestation_data`](./beacon-chain.md#is_slashable_attestation_data). -Specifically, when signing an `Attestation`, a validator should perform the following steps in the following order: +Specifically, when signing an `Attestation`, a validator should perform the +following steps in the following order: -1. Save a record to hard disk that an attestation has been signed for source (i.e. `attestation_data.source.epoch`) and target (i.e. `attestation_data.target.epoch`). +1. Save a record to hard disk that an attestation has been signed for source + (i.e. `attestation_data.source.epoch`) and target (i.e. + `attestation_data.target.epoch`). 2. Generate and broadcast attestation. -If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing. +If the software crashes at some point within this routine, then when the +validator comes back online, the hard disk has the record of the *potentially* +signed/broadcast attestation and can effectively avoid slashing. ## Protection best practices -A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect: - -1) Private keys -- private keys should be protected from being exported accidentally or by an attacker. -2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object. -3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format. -4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity penalty and potential ejection due to low balance. -A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event. +A validator client should be considered standalone and should consider the +beacon node as untrusted. This means that the validator client should protect: + +1. Private keys -- private keys should be protected from being exported + accidentally or by an attacker. +2. Slashing -- before a validator client signs a message it should validate the + data, check it against a local slashing database (do not sign a slashable + attestation or block) and update its internal slashing database with the + newly signed object. +3. Recovered validator -- Recovering a validator from a private key will result + in an empty local slashing db. Best practice is to import (from a trusted + source) that validator's attestation history. See + [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard + slashing interchange format. +4. Far future signing requests -- A validator client can be requested to sign a + far into the future attestation, resulting in a valid non-slashable request. + If the validator client signs this message, it will result in it blocking + itself from attesting any other attestation until the beacon-chain reaches + that far into the future epoch. This will result in an inactivity penalty and + potential ejection due to low balance. A validator client should prevent + itself from signing such requests by: a) keeping a local time clock if + possible and following best practices to stop time server attacks and b) + refusing to sign, by default, any message that has a large (>6h) gap from the + current slashing protection database indicated a time "jump" or a long + offline event. The administrator can manually override this protection to + restart the validator after a genuine long offline event. diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md index 52953c34df..ad61837f35 100644 --- a/specs/phase0/weak-subjectivity.md +++ b/specs/phase0/weak-subjectivity.md @@ -1,10 +1,6 @@ # Phase 0 -- Weak Subjectivity Guide -## Table of contents - - - - + - [Introduction](#introduction) - [Prerequisites](#prerequisites) @@ -21,14 +17,14 @@ - [`is_within_weak_subjectivity_period`](#is_within_weak_subjectivity_period) - [Distributing Weak Subjectivity Checkpoints](#distributing-weak-subjectivity-checkpoints) - - + ## Introduction -This document is a guide for implementing the Weak Subjectivity protections in Phase 0. -This document is still a work-in-progress, and is subject to large changes. -For more information about weak subjectivity and why it is required, please refer to: +This document is a guide for implementing the Weak Subjectivity protections in +Phase 0. This document is still a work-in-progress, and is subject to large +changes. For more information about weak subjectivity and why it is required, +please refer to: - [Weak Subjectivity in Ethereum Proof-of-Stake](https://notes.ethereum.org/@adiasg/weak-subjectvity-eth2) - [Proof of Stake: How I Learned to Love Weak Subjectivity](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/) @@ -36,59 +32,70 @@ For more information about weak subjectivity and why it is required, please refe ## Prerequisites This document uses data structures, constants, functions, and terminology from -[Phase 0 -- The Beacon Chain](./beacon-chain.md) and [Phase 0 -- Beacon Chain Fork Choice](./fork-choice.md). +[Phase 0 -- The Beacon Chain](./beacon-chain.md) and +[Phase 0 -- Beacon Chain Fork Choice](./fork-choice.md). ## Custom Types -| Name | SSZ Equivalent | Description | -|---|---|---| -| `Ether` | `uint64` | an amount in Ether | +| Name | SSZ Equivalent | Description | +| ------- | -------------- | ------------------ | +| `Ether` | `uint64` | an amount in Ether | ## Constants -| Name | Value | -|---|---| +| Name | Value | +| ------------- | --------------- | | `ETH_TO_GWEI` | `uint64(10**9)` | ## Configuration -| Name | Value | -|---|---| +| Name | Value | +| -------------- | ------------ | | `SAFETY_DECAY` | `uint64(10)` | ## Weak Subjectivity Checkpoint -Any `Checkpoint` object can be used as a Weak Subjectivity Checkpoint. -These Weak Subjectivity Checkpoints are distributed by providers, -downloaded by users and/or distributed as a part of clients, and used as input while syncing a client. +Any `Checkpoint` object can be used as a Weak Subjectivity Checkpoint. These +Weak Subjectivity Checkpoints are distributed by providers, downloaded by users +and/or distributed as a part of clients, and used as input while syncing a +client. ## Weak Subjectivity Period The Weak Subjectivity Period is the number of recent epochs within which there -must be a Weak Subjectivity Checkpoint to ensure that an attacker who takes control -of the validator set at the beginning of the period is slashed at least a minimum threshold -in the event that a conflicting `Checkpoint` is finalized. +must be a Weak Subjectivity Checkpoint to ensure that an attacker who takes +control of the validator set at the beginning of the period is slashed at least +a minimum threshold in the event that a conflicting `Checkpoint` is finalized. -`SAFETY_DECAY` is defined as the maximum percentage tolerable loss in the one-third -safety margin of FFG finality. Thus, any attack exploiting the Weak Subjectivity Period has -a safety margin of at least `1/3 - SAFETY_DECAY/100`. +`SAFETY_DECAY` is defined as the maximum percentage tolerable loss in the +one-third safety margin of FFG finality. Thus, any attack exploiting the Weak +Subjectivity Period has a safety margin of at least `1/3 - SAFETY_DECAY/100`. ### Calculating the Weak Subjectivity Period -A detailed analysis of the calculation of the weak subjectivity period is made in [this report](https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf). +A detailed analysis of the calculation of the weak subjectivity period is made +in +[this report](https://github.com/runtimeverification/beacon-chain-verification/blob/master/weak-subjectivity/weak-subjectivity-analysis.pdf). -*Note*: The expressions in the report use fractions, whereas the consensus-specs only use `uint64` arithmetic. The expressions have been simplified to avoid computing fractions, and more details can be found [here](https://www.overleaf.com/read/wgjzjdjpvpsd). +*Note*: The expressions in the report use fractions, whereas the consensus-specs +only use `uint64` arithmetic. The expressions have been simplified to avoid +computing fractions, and more details can be found +[here](https://www.overleaf.com/read/wgjzjdjpvpsd). -*Note*: The calculations here use `Ether` instead of `Gwei`, because the large magnitude of balances in `Gwei` can cause an overflow while computing using `uint64` arithmetic operations. Using `Ether` reduces the magnitude of the multiplicative factors by an order of `ETH_TO_GWEI` (`= 10**9`) and avoid the scope for overflows in `uint64`. +*Note*: The calculations here use `Ether` instead of `Gwei`, because the large +magnitude of balances in `Gwei` can cause an overflow while computing using +`uint64` arithmetic operations. Using `Ether` reduces the magnitude of the +multiplicative factors by an order of `ETH_TO_GWEI` (`= 10**9`) and avoid the +scope for overflows in `uint64`. #### `compute_weak_subjectivity_period` ```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 @@ -105,70 +112,75 @@ def compute_weak_subjectivity_period(state: BeaconState) -> uint64: epochs_for_validator_set_churn = ( N * (t * (200 + 12 * D) - T * (200 + 3 * D)) // (600 * delta * (2 * t + T)) ) - epochs_for_balance_top_ups = ( - N * (200 + 3 * D) // (600 * Delta) - ) + epochs_for_balance_top_ups = N * (200 + 3 * D) // (600 * Delta) ws_period += max(epochs_for_validator_set_churn, epochs_for_balance_top_ups) else: - ws_period += ( - 3 * N * D * t // (200 * Delta * (T - t)) - ) - + ws_period += 3 * N * D * t // (200 * Delta * (T - t)) + return ws_period ``` -A brief reference for what these values look like in practice ([reference script](https://gist.github.com/adiasg/3aceab409b36aa9a9d9156c1baa3c248)): +A brief reference for what these values look like in practice +([reference script](https://gist.github.com/adiasg/3aceab409b36aa9a9d9156c1baa3c248)): | Safety Decay | Avg. Val. Balance (ETH) | Val. Count | Weak Sub. Period (Epochs) | -| ---- | ---- | ---- | ---- | -| 10 | 28 | 32768 | 504 | -| 10 | 28 | 65536 | 752 | -| 10 | 28 | 131072 | 1248 | -| 10 | 28 | 262144 | 2241 | -| 10 | 28 | 524288 | 2241 | -| 10 | 28 | 1048576 | 2241 | -| 10 | 32 | 32768 | 665 | -| 10 | 32 | 65536 | 1075 | -| 10 | 32 | 131072 | 1894 | -| 10 | 32 | 262144 | 3532 | -| 10 | 32 | 524288 | 3532 | -| 10 | 32 | 1048576 | 3532 | +| ------------ | ----------------------- | ---------- | ------------------------- | +| 10 | 28 | 32768 | 504 | +| 10 | 28 | 65536 | 752 | +| 10 | 28 | 131072 | 1248 | +| 10 | 28 | 262144 | 2241 | +| 10 | 28 | 524288 | 2241 | +| 10 | 28 | 1048576 | 2241 | +| 10 | 32 | 32768 | 665 | +| 10 | 32 | 65536 | 1075 | +| 10 | 32 | 131072 | 1894 | +| 10 | 32 | 262144 | 3532 | +| 10 | 32 | 524288 | 3532 | +| 10 | 32 | 1048576 | 3532 | ## Weak Subjectivity Sync Clients should allow users to input a Weak Subjectivity Checkpoint at startup, -and guarantee that any successful sync leads to the given Weak Subjectivity Checkpoint along the canonical chain. -If such a sync is not possible, the client should treat this as a critical and irrecoverable failure. +and guarantee that any successful sync leads to the given Weak Subjectivity +Checkpoint along the canonical chain. If such a sync is not possible, the client +should treat this as a critical and irrecoverable failure. ### Weak Subjectivity Sync Procedure -1. Input a Weak Subjectivity Checkpoint as a CLI parameter in `block_root:epoch_number` format, - where `block_root` (an "0x" prefixed 32-byte hex string) and `epoch_number` (an integer) represent a valid `Checkpoint`. - Example of the format: +1. Input a Weak Subjectivity Checkpoint as a CLI parameter in + `block_root:epoch_number` format, where `block_root` (an "0x" prefixed + 32-byte hex string) and `epoch_number` (an integer) represent a valid + `Checkpoint`. Example of the format: ``` 0x8584188b86a9296932785cc2827b925f9deebacce6d72ad8d53171fa046b43d9:9544 ``` 2. Check the weak subjectivity requirements: - - *IF* `epoch_number > store.finalized_checkpoint.epoch`, - then *ASSERT* during block sync that block with root `block_root` is in the sync path at epoch `epoch_number`. - Emit descriptive critical error if this assert fails, then exit client process. - - *IF* `epoch_number <= store.finalized_checkpoint.epoch`, - then *ASSERT* that the block in the canonical chain at epoch `epoch_number` has root `block_root`. - Emit descriptive critical error if this assert fails, then exit client process. + + - *IF* `epoch_number > store.finalized_checkpoint.epoch`, then *ASSERT* + during block sync that block with root `block_root` is in the sync path at + epoch `epoch_number`. Emit descriptive critical error if this assert fails, + then exit client process. + - *IF* `epoch_number <= store.finalized_checkpoint.epoch`, then *ASSERT* that + the block in the canonical chain at epoch `epoch_number` has root + `block_root`. Emit descriptive critical error if this assert fails, then + exit client process. ### Checking for Stale Weak Subjectivity Checkpoint -Clients may choose to validate that the input Weak Subjectivity Checkpoint is not stale at the time of startup. -To support this mechanism, the client needs to take the state at the Weak Subjectivity Checkpoint as -a CLI parameter input (or fetch the state associated with the input Weak Subjectivity Checkpoint from some source). -The check can be implemented in the following way: +Clients may choose to validate that the input Weak Subjectivity Checkpoint is +not stale at the time of startup. To support this mechanism, the client needs to +take the state at the Weak Subjectivity Checkpoint as a CLI parameter input (or +fetch the state associated with the input Weak Subjectivity Checkpoint from some +source). The check can be implemented in the following way: #### `is_within_weak_subjectivity_period` ```python -def is_within_weak_subjectivity_period(store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint) -> bool: +def is_within_weak_subjectivity_period( + store: Store, ws_state: BeaconState, ws_checkpoint: Checkpoint +) -> bool: # Clients may choose to validate the input state against the input Weak Subjectivity Checkpoint assert ws_state.latest_block_header.state_root == ws_checkpoint.root assert compute_epoch_at_slot(ws_state.slot) == ws_checkpoint.epoch diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md deleted file mode 100644 index f54394275c..0000000000 --- a/specs/sharding/beacon-chain.md +++ /dev/null @@ -1,896 +0,0 @@ -# Sharding -- The Beacon Chain - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) - - [Glossary](#glossary) -- [Custom types](#custom-types) -- [Constants](#constants) - - [Misc](#misc) - - [Domain types](#domain-types) - - [Shard Work Status](#shard-work-status) - - [Misc](#misc-1) - - [Participation flag indices](#participation-flag-indices) - - [Incentivization weights](#incentivization-weights) -- [Preset](#preset) - - [Misc](#misc-2) - - [Shard blob samples](#shard-blob-samples) - - [Precomputed size verification points](#precomputed-size-verification-points) - - [Gwei values](#gwei-values) -- [Configuration](#configuration) -- [Updated containers](#updated-containers) - - [`AttestationData`](#attestationdata) - - [`BeaconBlockBody`](#beaconblockbody) - - [`BeaconState`](#beaconstate) -- [New containers](#new-containers) - - [`Builder`](#builder) - - [`DataCommitment`](#datacommitment) - - [`AttestedDataCommitment`](#attesteddatacommitment) - - [`ShardBlobBody`](#shardblobbody) - - [`ShardBlobBodySummary`](#shardblobbodysummary) - - [`ShardBlob`](#shardblob) - - [`ShardBlobHeader`](#shardblobheader) - - [`SignedShardBlob`](#signedshardblob) - - [`SignedShardBlobHeader`](#signedshardblobheader) - - [`PendingShardHeader`](#pendingshardheader) - - [`ShardBlobReference`](#shardblobreference) - - [`ShardProposerSlashing`](#shardproposerslashing) - - [`ShardWork`](#shardwork) -- [Helper functions](#helper-functions) - - [Misc](#misc-3) - - [`next_power_of_two`](#next_power_of_two) - - [`compute_previous_slot`](#compute_previous_slot) - - [`compute_updated_sample_price`](#compute_updated_sample_price) - - [`compute_committee_source_epoch`](#compute_committee_source_epoch) - - [`batch_apply_participation_flag`](#batch_apply_participation_flag) - - [Beacon state accessors](#beacon-state-accessors) - - [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot) - - [`get_active_shard_count`](#get_active_shard_count) - - [`get_shard_proposer_index`](#get_shard_proposer_index) - - [`get_start_shard`](#get_start_shard) - - [`compute_shard_from_committee_index`](#compute_shard_from_committee_index) - - [`compute_committee_index_from_shard`](#compute_committee_index_from_shard) - - [Block processing](#block-processing) - - [Operations](#operations) - - [Extended Attestation processing](#extended-attestation-processing) - - [`process_shard_header`](#process_shard_header) - - [`process_shard_proposer_slashing`](#process_shard_proposer_slashing) - - [Epoch transition](#epoch-transition) - - [`process_pending_shard_confirmations`](#process_pending_shard_confirmations) - - [`reset_pending_shard_work`](#reset_pending_shard_work) - - - - - -## Introduction - -This document describes the extensions made to the Phase 0 design of The Beacon Chain to support data sharding, -based on the ideas [here](https://hackmd.io/G-Iy5jqyT7CXWEz8Ssos8g) and more broadly [here](https://arxiv.org/abs/1809.09044), -using KZG10 commitments to commit to data to remove any need for fraud proofs (and hence, safety-critical synchrony assumptions) in the design. - -### Glossary - -- **Data**: A list of KZG points, to translate a byte string into -- **Blob**: Data with commitments and meta-data, like a flattened bundle of L2 transactions. -- **Builder**: Independent actor that builds blobs and bids for proposal slots via fee-paying blob-headers, responsible for availability. -- **Shard proposer**: Validator taking bids from blob builders for shard data opportunity, co-signs with builder to propose the blob. - -## Custom types - -We define the following Python custom types for type hinting and readability: - -| Name | SSZ equivalent | Description | -| - | - | - | -| `Shard` | `uint64` | A shard number | -| `BLSCommitment` | `Bytes48` | A G1 curve point | -| `BLSPoint` | `uint256` | A number `x` in the range `0 <= x < MODULUS` | -| `BuilderIndex` | `uint64` | Builder registry index | - -## Constants - -The following values are (non-configurable) constants used throughout the specification. - -### Misc - -| Name | Value | Notes | -| - | - | - | -| `PRIMITIVE_ROOT_OF_UNITY` | `5` | Primitive root of unity of the BLS12_381 (inner) modulus | -| `DATA_AVAILABILITY_INVERSE_CODING_RATE` | `2**1` (= 2) | Factor by which samples are extended for data availability encoding | -| `POINTS_PER_SAMPLE` | `uint64(2**3)` (= 8) | 31 * 8 = 248 bytes | -| `MODULUS` | `0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001` (curve order of BLS12_381) | - -### Domain types - -| Name | Value | -| - | - | -| `DOMAIN_SHARD_BLOB` | `DomainType('0x80000000')` | - -### Shard Work Status - -| Name | Value | Notes | -| - | - | - | -| `SHARD_WORK_UNCONFIRMED` | `0` | Unconfirmed, nullified after confirmation time elapses | -| `SHARD_WORK_CONFIRMED` | `1` | Confirmed, reduced to just the commitment | -| `SHARD_WORK_PENDING` | `2` | Pending, a list of competing headers | - -### Misc - -TODO: `PARTICIPATION_FLAG_WEIGHTS` backwards-compatibility is difficult, depends on usage. - -| Name | Value | -| - | - | -| `PARTICIPATION_FLAG_WEIGHTS` | `[TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT, TIMELY_SHARD_WEIGHT]` | - -### Participation flag indices - -| Name | Value | -| - | - | -| `TIMELY_SHARD_FLAG_INDEX` | `3` | - -### Incentivization weights - -TODO: determine weight for shard attestations - -| Name | Value | -| - | - | -| `TIMELY_SHARD_WEIGHT` | `uint64(8)` | - -TODO: `WEIGHT_DENOMINATOR` needs to be adjusted, but this breaks a lot of Altair code. - -## Preset - -### Misc - -| Name | Value | Notes | -| - | - | - | -| `MAX_SHARDS` | `uint64(2**10)` (= 1,024) | Theoretical max shard count (used to determine data structure sizes) | -| `INITIAL_ACTIVE_SHARDS` | `uint64(2**6)` (= 64) | Initial shard count | -| `SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT` | `uint64(2**3)` (= 8) | Sample price may decrease/increase by at most exp(1 / this value) *per epoch* | -| `MAX_SHARD_PROPOSER_SLASHINGS` | `2**4` (= 16) | Maximum amount of shard proposer slashing operations per block | -| `MAX_SHARD_HEADERS_PER_SHARD` | `4` | | -| `SHARD_STATE_MEMORY_SLOTS` | `uint64(2**8)` (= 256) | Number of slots for which shard commitments and confirmation status is directly available in the state | -| `BLOB_BUILDER_REGISTRY_LIMIT` | `uint64(2**40)` (= 1,099,511,627,776) | shard blob builders | - -### Shard blob samples - -| Name | Value | Notes | -| - | - | - | -| `MAX_SAMPLES_PER_BLOB` | `uint64(2**11)` (= 2,048) | 248 * 2,048 = 507,904 bytes | -| `TARGET_SAMPLES_PER_BLOB` | `uint64(2**10)` (= 1,024) | 248 * 1,024 = 253,952 bytes | - -### Precomputed size verification points - -| Name | Value | -| - | - | -| `G1_SETUP` | Type `List[G1]`. The G1-side trusted setup `[G, G*s, G*s**2....]`; note that the first point is the generator. | -| `G2_SETUP` | Type `List[G2]`. The G2-side trusted setup `[G, G*s, G*s**2....]` | -| `ROOT_OF_UNITY` | `pow(PRIMITIVE_ROOT_OF_UNITY, (MODULUS - 1) // int(MAX_SAMPLES_PER_BLOB * POINTS_PER_SAMPLE), MODULUS)` | - -### Gwei values - -| Name | Value | Unit | Description | -| - | - | - | - | -| `MAX_SAMPLE_PRICE` | `Gwei(2**33)` (= 8,589,934,592) | Gwei | Max sample charged for a TARGET-sized shard blob | -| `MIN_SAMPLE_PRICE` | `Gwei(2**3)` (= 8) | Gwei | Min sample price charged for a TARGET-sized shard blob | - -## Configuration - -Note: Some preset variables may become run-time configurable for testnets, but default to a preset while the spec is unstable. -E.g. `INITIAL_ACTIVE_SHARDS`, `MAX_SAMPLES_PER_BLOB` and `TARGET_SAMPLES_PER_BLOB`. - -## Updated containers - -The following containers have updated definitions to support Sharding. - -### `AttestationData` - -```python -class AttestationData(Container): - slot: Slot - index: CommitteeIndex - # LMD GHOST vote - beacon_block_root: Root - # FFG vote - source: Checkpoint - target: Checkpoint - # Hash-tree-root of ShardBlob - shard_blob_root: Root # [New in Sharding] -``` - -### `BeaconBlockBody` - -```python -class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body] - shard_proposer_slashings: List[ShardProposerSlashing, MAX_SHARD_PROPOSER_SLASHINGS] - shard_headers: List[SignedShardBlobHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD] -``` - -### `BeaconState` - -```python -class BeaconState(merge.BeaconState): - # Blob builder registry. - blob_builders: List[Builder, BLOB_BUILDER_REGISTRY_LIMIT] - blob_builder_balances: List[Gwei, BLOB_BUILDER_REGISTRY_LIMIT] - # A ring buffer of the latest slots, with information per active shard. - shard_buffer: Vector[List[ShardWork, MAX_SHARDS], SHARD_STATE_MEMORY_SLOTS] - shard_sample_price: uint64 -``` - -## New containers - -### `Builder` - -```python -class Builder(Container): - pubkey: BLSPubkey - # TODO: fields for either an expiry mechanism (refunding execution account with remaining balance) - # and/or a builder-transaction mechanism. -``` - -### `DataCommitment` - -```python -class DataCommitment(Container): - # KZG10 commitment to the data - point: BLSCommitment - # Length of the data in samples - samples_count: uint64 -``` - -### `AttestedDataCommitment` - -```python -class AttestedDataCommitment(Container): - # KZG10 commitment to the data, and length - commitment: DataCommitment - # hash_tree_root of the ShardBlobHeader (stored so that attestations can be checked against it) - root: Root - # The proposer who included the shard-header - includer_index: ValidatorIndex -``` - -### `ShardBlobBody` - -Unsigned shard data, bundled by a shard-builder. -Unique, signing different bodies as shard proposer for the same `(slot, shard)` is slashable. - -```python -class ShardBlobBody(Container): - # The actual data commitment - commitment: DataCommitment - # Proof that the degree < commitment.samples_count * POINTS_PER_SAMPLE - degree_proof: BLSCommitment - # The actual data. Should match the commitment and degree proof. - data: List[BLSPoint, POINTS_PER_SAMPLE * MAX_SAMPLES_PER_BLOB] - # Latest block root of the Beacon Chain, before shard_blob.slot - beacon_block_root: Root - # fee payment fields (EIP 1559 like) - # TODO: express in MWei instead? - max_priority_fee_per_sample: Gwei - max_fee_per_sample: Gwei -``` - -### `ShardBlobBodySummary` - -Summary version of the `ShardBlobBody`, omitting the data payload, while preserving the data-commitments. - -The commitments are not further collapsed to a single hash, -to avoid an extra network roundtrip between proposer and builder, to include the header on-chain more quickly. - -```python -class ShardBlobBodySummary(Container): - # The actual data commitment - commitment: DataCommitment - # Proof that the degree < commitment.samples_count * POINTS_PER_SAMPLE - degree_proof: BLSCommitment - # Hash-tree-root as summary of the data field - data_root: Root - # Latest block root of the Beacon Chain, before shard_blob.slot - beacon_block_root: Root - # fee payment fields (EIP 1559 like) - # TODO: express in MWei instead? - max_priority_fee_per_sample: Gwei - max_fee_per_sample: Gwei -``` - -### `ShardBlob` - -`ShardBlobBody` wrapped with the header data that is unique to the shard blob proposal. - -```python -class ShardBlob(Container): - slot: Slot - shard: Shard - # Builder of the data, pays data-fee to proposer - builder_index: BuilderIndex - # Proposer of the shard-blob - proposer_index: ValidatorIndex - # Blob contents - body: ShardBlobBody -``` - -### `ShardBlobHeader` - -Header version of `ShardBlob`. - -```python -class ShardBlobHeader(Container): - slot: Slot - shard: Shard - # Builder of the data, pays data-fee to proposer - builder_index: BuilderIndex - # Proposer of the shard-blob - proposer_index: ValidatorIndex - # Blob contents, without the full data - body_summary: ShardBlobBodySummary -``` - -### `SignedShardBlob` - -Full blob data, signed by the shard builder (ensuring fee payment) and shard proposer (ensuring a single proposal). - -```python -class SignedShardBlob(Container): - message: ShardBlob - signature: BLSSignature -``` - -### `SignedShardBlobHeader` - -Header of the blob, the signature is equally applicable to `SignedShardBlob`. -Shard proposers can accept `SignedShardBlobHeader` as a data-transaction by co-signing the header. - -```python -class SignedShardBlobHeader(Container): - message: ShardBlobHeader - # Signature by builder. - # Once accepted by proposer, the signatures is the aggregate of both. - signature: BLSSignature -``` - -### `PendingShardHeader` - -```python -class PendingShardHeader(Container): - # The commitment that is attested - attested: AttestedDataCommitment - # Who voted for the header - votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE] - # Sum of effective balances of votes - weight: Gwei - # When the header was last updated, as reference for weight accuracy - update_slot: Slot -``` - -### `ShardBlobReference` - -Reference version of `ShardBlobHeader`, substituting the body for just a hash-tree-root. - -```python -class ShardBlobReference(Container): - slot: Slot - shard: Shard - # Builder of the data - builder_index: BuilderIndex - # Proposer of the shard-blob - proposer_index: ValidatorIndex - # Blob hash-tree-root for slashing reference - body_root: Root -``` - -### `ShardProposerSlashing` - -```python -class ShardProposerSlashing(Container): - slot: Slot - shard: Shard - proposer_index: ValidatorIndex - builder_index_1: BuilderIndex - builder_index_2: BuilderIndex - body_root_1: Root - body_root_2: Root - signature_1: BLSSignature - signature_2: BLSSignature -``` - -### `ShardWork` - -```python -class ShardWork(Container): - # Upon confirmation the data is reduced to just the commitment. - status: Union[ # See Shard Work Status enum - None, # SHARD_WORK_UNCONFIRMED - AttestedDataCommitment, # SHARD_WORK_CONFIRMED - List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] # SHARD_WORK_PENDING - ] -``` - -## Helper functions - -### Misc - -#### `next_power_of_two` - -```python -def next_power_of_two(x: int) -> int: - return 2 ** ((x - 1).bit_length()) -``` - -#### `compute_previous_slot` - -```python -def compute_previous_slot(slot: Slot) -> Slot: - if slot > 0: - return Slot(slot - 1) - else: - return Slot(0) -``` - -#### `compute_updated_sample_price` - -```python -def compute_updated_sample_price(prev_price: Gwei, samples_length: uint64, active_shards: uint64) -> Gwei: - adjustment_quotient = active_shards * SLOTS_PER_EPOCH * SAMPLE_PRICE_ADJUSTMENT_COEFFICIENT - if samples_length > TARGET_SAMPLES_PER_BLOB: - delta = max(1, prev_price * (samples_length - TARGET_SAMPLES_PER_BLOB) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) - return min(prev_price + delta, MAX_SAMPLE_PRICE) - else: - delta = max(1, prev_price * (TARGET_SAMPLES_PER_BLOB - samples_length) // TARGET_SAMPLES_PER_BLOB // adjustment_quotient) - return max(prev_price, MIN_SAMPLE_PRICE + delta) - delta -``` - -#### `compute_committee_source_epoch` - -```python -def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch: - """ - Return the source epoch for computing the committee. - """ - source_epoch = Epoch(epoch - epoch % period) - if source_epoch >= period: - source_epoch -= period # `period` epochs lookahead - return source_epoch -``` - -#### `batch_apply_participation_flag` - -```python -def batch_apply_participation_flag(state: BeaconState, bits: Bitlist[MAX_VALIDATORS_PER_COMMITTEE], - epoch: Epoch, full_committee: Sequence[ValidatorIndex], flag_index: int): - if epoch == get_current_epoch(state): - epoch_participation = state.current_epoch_participation - else: - epoch_participation = state.previous_epoch_participation - for bit, index in zip(bits, full_committee): - if bit: - epoch_participation[index] = add_flag(epoch_participation[index], flag_index) -``` - -### Beacon state accessors - -#### Updated `get_committee_count_per_slot` - -```python -def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of committees in each slot for the given ``epoch``. - """ - return max(uint64(1), min( - get_active_shard_count(state, epoch), - uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE, - )) -``` - -#### `get_active_shard_count` - -```python -def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64: - """ - Return the number of active shards. - Note that this puts an upper bound on the number of committees per slot. - """ - return INITIAL_ACTIVE_SHARDS -``` - -#### `get_shard_proposer_index` - -```python -def get_shard_proposer_index(state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex: - """ - Return the proposer's index of shard block at ``slot``. - """ - epoch = compute_epoch_at_slot(slot) - seed = hash(get_seed(state, epoch, DOMAIN_SHARD_BLOB) + uint_to_bytes(slot) + uint_to_bytes(shard)) - indices = get_active_validator_indices(state, epoch) - return compute_proposer_index(state, indices, seed) -``` - -#### `get_start_shard` - -```python -def get_start_shard(state: BeaconState, slot: Slot) -> Shard: - """ - Return the start shard at ``slot``. - """ - epoch = compute_epoch_at_slot(Slot(slot)) - committee_count = get_committee_count_per_slot(state, epoch) - active_shard_count = get_active_shard_count(state, epoch) - return committee_count * slot % active_shard_count -``` - -#### `compute_shard_from_committee_index` - -```python -def compute_shard_from_committee_index(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Shard: - active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot)) - assert index < active_shards - return Shard((index + get_start_shard(state, slot)) % active_shards) -``` - -#### `compute_committee_index_from_shard` - -```python -def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Shard) -> CommitteeIndex: - epoch = compute_epoch_at_slot(slot) - active_shards = get_active_shard_count(state, epoch) - index = CommitteeIndex((active_shards + shard - get_start_shard(state, slot)) % active_shards) - assert index < get_committee_count_per_slot(state, epoch) - return index -``` - - -### Block processing - -```python -def process_block(state: BeaconState, block: BeaconBlock) -> None: - process_block_header(state, block) - process_randao(state, block.body) - process_eth1_data(state, block.body) - process_operations(state, block.body) # [Modified in Sharding] - process_sync_aggregate(state, block.body.sync_aggregate) - # is_execution_enabled is omitted, execution is enabled by default. - process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE) -``` - -#### Operations - -```python -def process_operations(state: BeaconState, body: BeaconBlockBody) -> None: - # Verify that outstanding deposits are processed up to the maximum number of deposits - assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index) - - def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None: - for operation in operations: - fn(state, operation) - - for_ops(body.proposer_slashings, process_proposer_slashing) - for_ops(body.attester_slashings, process_attester_slashing) - # New shard proposer slashing processing - for_ops(body.shard_proposer_slashings, process_shard_proposer_slashing) - - # Limit is dynamic: based on active shard count - assert len(body.shard_headers) <= MAX_SHARD_HEADERS_PER_SHARD * get_active_shard_count(state, get_current_epoch(state)) - for_ops(body.shard_headers, process_shard_header) - - # New attestation processing - for_ops(body.attestations, process_attestation) - for_ops(body.deposits, process_deposit) - for_ops(body.voluntary_exits, process_voluntary_exit) - - # TODO: to avoid parallel shards racing, and avoid inclusion-order problems, - # update the fee price per slot, instead of per header. - # state.shard_sample_price = compute_updated_sample_price(state.shard_sample_price, ?, shard_count) -``` - -##### Extended Attestation processing - -```python -def process_attestation(state: BeaconState, attestation: Attestation) -> None: - altair.process_attestation(state, attestation) - process_attested_shard_work(state, attestation) -``` - -```python -def process_attested_shard_work(state: BeaconState, attestation: Attestation) -> None: - attestation_shard = compute_shard_from_committee_index( - state, - attestation.data.slot, - attestation.data.index, - ) - full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index) - - buffer_index = attestation.data.slot % SHARD_STATE_MEMORY_SLOTS - committee_work = state.shard_buffer[buffer_index][attestation_shard] - - # Skip attestation vote accounting if the header is not pending - if committee_work.status.selector != SHARD_WORK_PENDING: - # If the data was already confirmed, check if this matches, to apply the flag to the attesters. - if committee_work.status.selector == SHARD_WORK_CONFIRMED: - attested: AttestedDataCommitment = committee_work.status.value - if attested.root == attestation.data.shard_blob_root: - batch_apply_participation_flag(state, attestation.aggregation_bits, - attestation.data.target.epoch, - full_committee, TIMELY_SHARD_FLAG_INDEX) - return - - current_headers: Sequence[PendingShardHeader] = committee_work.status.value - - # Find the corresponding header, abort if it cannot be found - header_index = len(current_headers) - for i, header in enumerate(current_headers): - if attestation.data.shard_blob_root == header.attested.root: - header_index = i - break - - # Attestations for an unknown header do not count towards shard confirmations, but can otherwise be valid. - if header_index == len(current_headers): - # Note: Attestations may be re-included if headers are included late. - return - - pending_header: PendingShardHeader = current_headers[header_index] - - # The weight may be outdated if it is not the initial weight, and from a previous epoch - if pending_header.weight != 0 and compute_epoch_at_slot(pending_header.update_slot) < get_current_epoch(state): - pending_header.weight = sum(state.validators[index].effective_balance for index, bit - in zip(full_committee, pending_header.votes) if bit) - - pending_header.update_slot = state.slot - - full_committee_balance = Gwei(0) - # Update votes bitfield in the state, update weights - for i, bit in enumerate(attestation.aggregation_bits): - weight = state.validators[full_committee[i]].effective_balance - full_committee_balance += weight - if bit: - if not pending_header.votes[i]: - pending_header.weight += weight - pending_header.votes[i] = True - - # Check if the PendingShardHeader is eligible for expedited confirmation, requiring 2/3 of balance attesting - if pending_header.weight * 3 >= full_committee_balance * 2: - # participants of the winning header are remembered with participation flags - batch_apply_participation_flag(state, pending_header.votes, attestation.data.target.epoch, - full_committee, TIMELY_SHARD_FLAG_INDEX) - - if pending_header.attested.commitment == DataCommitment(): - # The committee voted to not confirm anything - state.shard_buffer[buffer_index][attestation_shard].status.change( - selector=SHARD_WORK_UNCONFIRMED, - value=None, - ) - else: - state.shard_buffer[buffer_index][attestation_shard].status.change( - selector=SHARD_WORK_CONFIRMED, - value=pending_header.attested, - ) -``` - -##### `process_shard_header` - -```python -def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None: - header: ShardBlobHeader = signed_header.message - slot = header.slot - shard = header.shard - - # Verify the header is not 0, and not from the future. - assert Slot(0) < slot <= state.slot - header_epoch = compute_epoch_at_slot(slot) - # Verify that the header is within the processing time window - assert header_epoch in [get_previous_epoch(state), get_current_epoch(state)] - # Verify that the shard is valid - shard_count = get_active_shard_count(state, header_epoch) - assert shard < shard_count - # Verify that a committee is able to attest this (slot, shard) - start_shard = get_start_shard(state, slot) - committee_index = (shard_count + shard - start_shard) % shard_count - committees_per_slot = get_committee_count_per_slot(state, header_epoch) - assert committee_index <= committees_per_slot - - # Verify that the block root matches, - # to ensure the header will only be included in this specific Beacon Chain sub-tree. - assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, slot - 1) - - # Check that this data is still pending - committee_work = state.shard_buffer[slot % SHARD_STATE_MEMORY_SLOTS][shard] - assert committee_work.status.selector == SHARD_WORK_PENDING - - # Check that this header is not yet in the pending list - current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value - header_root = hash_tree_root(header) - assert header_root not in [pending_header.attested.root for pending_header in current_headers] - - # Verify proposer matches - assert header.proposer_index == get_shard_proposer_index(state, slot, shard) - - # Verify builder and proposer aggregate signature - blob_signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_BLOB)) - builder_pubkey = state.blob_builders[header.builder_index].pubkey - proposer_pubkey = state.validators[header.proposer_index].pubkey - assert bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_header.signature) - - # Verify the length by verifying the degree. - body_summary = header.body_summary - points_count = body_summary.commitment.samples_count * POINTS_PER_SAMPLE - if points_count == 0: - assert body_summary.degree_proof == G1_SETUP[0] - assert ( - bls.Pairing(body_summary.degree_proof, G2_SETUP[0]) - == bls.Pairing(body_summary.commitment.point, G2_SETUP[-points_count]) - ) - - # Charge EIP 1559 fee, builder pays for opportunity, and is responsible for later availability, - # or fail to publish at their own expense. - samples = body_summary.commitment.samples_count - # TODO: overflows, need bigger int type - max_fee = body_summary.max_fee_per_sample * samples - - # Builder must have sufficient balance, even if max_fee is not completely utilized - assert state.blob_builder_balances[header.builder_index] >= max_fee - - base_fee = state.shard_sample_price * samples - # Base fee must be paid - assert max_fee >= base_fee - - # Remaining fee goes towards proposer for prioritizing, up to a maximum - max_priority_fee = body_summary.max_priority_fee_per_sample * samples - priority_fee = min(max_fee - base_fee, max_priority_fee) - - # Burn base fee, take priority fee - # priority_fee <= max_fee - base_fee, thus priority_fee + base_fee <= max_fee, thus sufficient balance. - state.blob_builder_balances[header.builder_index] -= base_fee + priority_fee - # Pay out priority fee - increase_balance(state, header.proposer_index, priority_fee) - - # Initialize the pending header - index = compute_committee_index_from_shard(state, slot, shard) - committee_length = len(get_beacon_committee(state, slot, index)) - initial_votes = Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length) - pending_header = PendingShardHeader( - attested=AttestedDataCommitment( - commitment=body_summary.commitment, - root=header_root, - includer_index=get_beacon_proposer_index(state), - ), - votes=initial_votes, - weight=0, - update_slot=state.slot, - ) - - # Include it in the pending list - current_headers.append(pending_header) -``` - -The degree proof works as follows. For a block `B` with length `l` (so `l` values in `[0...l - 1]`, seen as a polynomial `B(X)` which takes these values), -the length proof is the commitment to the polynomial `B(X) * X**(MAX_DEGREE + 1 - l)`, -where `MAX_DEGREE` is the maximum power of `s` available in the setup, which is `MAX_DEGREE = len(G2_SETUP) - 1`. -The goal is to ensure that a proof can only be constructed if `deg(B) < l` (there are not hidden higher-order terms in the polynomial, which would thwart reconstruction). - -##### `process_shard_proposer_slashing` - -```python -def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: ShardProposerSlashing) -> None: - slot = proposer_slashing.slot - shard = proposer_slashing.shard - proposer_index = proposer_slashing.proposer_index - - reference_1 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, - builder_index=proposer_slashing.builder_index_1, - body_root=proposer_slashing.body_root_1) - reference_2 = ShardBlobReference(slot=slot, shard=shard, - proposer_index=proposer_index, - builder_index=proposer_slashing.builder_index_2, - body_root=proposer_slashing.body_root_2) - - # Verify the signed messages are different - assert reference_1 != reference_2 - - # Verify the proposer is slashable - proposer = state.validators[proposer_index] - assert is_slashable_validator(proposer, get_current_epoch(state)) - - # The builders are not slashed, the proposer co-signed with them - builder_pubkey_1 = state.blob_builders[proposer_slashing.builder_index_1].pubkey - builder_pubkey_2 = state.blob_builders[proposer_slashing.builder_index_2].pubkey - domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(slot)) - signing_root_1 = compute_signing_root(reference_1, domain) - signing_root_2 = compute_signing_root(reference_2, domain) - assert bls.FastAggregateVerify([builder_pubkey_1, proposer.pubkey], signing_root_1, proposer_slashing.signature_1) - assert bls.FastAggregateVerify([builder_pubkey_2, proposer.pubkey], signing_root_2, proposer_slashing.signature_2) - - slash_validator(state, proposer_index) -``` - -### Epoch transition - -This epoch transition overrides the Merge epoch transition: - -```python -def process_epoch(state: BeaconState) -> None: - # Sharding pre-processing - process_pending_shard_confirmations(state) - reset_pending_shard_work(state) - - # Base functionality - process_justification_and_finalization(state) - process_inactivity_updates(state) - process_rewards_and_penalties(state) # Note: modified, see new TIMELY_SHARD_FLAG_INDEX - process_registry_updates(state) - process_slashings(state) - process_eth1_data_reset(state) - process_effective_balance_updates(state) - process_slashings_reset(state) - process_randao_mixes_reset(state) - process_historical_roots_update(state) - process_participation_flag_updates(state) - process_sync_committee_updates(state) -``` - -#### `process_pending_shard_confirmations` - -```python -def process_pending_shard_confirmations(state: BeaconState) -> None: - # Pending header processing applies to the previous epoch. - # Skip if `GENESIS_EPOCH` because no prior epoch to process. - if get_current_epoch(state) == GENESIS_EPOCH: - return - - previous_epoch = get_previous_epoch(state) - previous_epoch_start_slot = compute_start_slot_at_epoch(previous_epoch) - - # Mark stale headers as unconfirmed - for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH): - buffer_index = slot % SHARD_STATE_MEMORY_SLOTS - for shard_index in range(len(state.shard_buffer[buffer_index])): - committee_work = state.shard_buffer[buffer_index][shard_index] - if committee_work.status.selector == SHARD_WORK_PENDING: - winning_header = max(committee_work.status.value, key=lambda header: header.weight) - if winning_header.attested.commitment == DataCommitment(): - committee_work.status.change(selector=SHARD_WORK_UNCONFIRMED, value=None) - else: - committee_work.status.change(selector=SHARD_WORK_CONFIRMED, value=winning_header.attested) -``` - -#### `reset_pending_shard_work` - -```python -def reset_pending_shard_work(state: BeaconState) -> None: - # Add dummy "empty" PendingShardHeader (default vote if no shard header is available) - next_epoch = get_current_epoch(state) + 1 - next_epoch_start_slot = compute_start_slot_at_epoch(next_epoch) - committees_per_slot = get_committee_count_per_slot(state, next_epoch) - active_shards = get_active_shard_count(state, next_epoch) - - for slot in range(next_epoch_start_slot, next_epoch_start_slot + SLOTS_PER_EPOCH): - buffer_index = slot % SHARD_STATE_MEMORY_SLOTS - - # Reset the shard work tracking - state.shard_buffer[buffer_index] = [ShardWork() for _ in range(active_shards)] - - start_shard = get_start_shard(state, slot) - for committee_index in range(committees_per_slot): - shard = (start_shard + committee_index) % active_shards - # a committee is available, initialize a pending shard-header list - committee_length = len(get_beacon_committee(state, slot, CommitteeIndex(committee_index))) - state.shard_buffer[buffer_index][shard].status.change( - selector=SHARD_WORK_PENDING, - value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD]( - PendingShardHeader( - attested=AttestedDataCommitment(), - votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length), - weight=0, - update_slot=slot, - ) - ) - ) - # a shard without committee available defaults to SHARD_WORK_UNCONFIRMED. -``` diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md deleted file mode 100644 index c47d2ee074..0000000000 --- a/specs/sharding/p2p-interface.md +++ /dev/null @@ -1,177 +0,0 @@ -# Sharding -- Networking - -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - - - -- [Introduction](#introduction) -- [Constants](#constants) - - [Misc](#misc) -- [Gossip domain](#gossip-domain) - - [Topics and messages](#topics-and-messages) - - [Shard blob subnets](#shard-blob-subnets) - - [`shard_blob_{subnet_id}`](#shard_blob_subnet_id) - - [Global topics](#global-topics) - - [`shard_blob_header`](#shard_blob_header) - - [`shard_blob_tx`](#shard_blob_tx) - - [`shard_proposer_slashing`](#shard_proposer_slashing) - - - - - -## Introduction - -The specification of these changes continues in the same format as the network specifications of previous upgrades, and assumes them as pre-requisite. -The adjustments and additions for Shards are outlined in this document. - -## Constants - -### Misc - -| Name | Value | Description | -| ---- | ----- | ----------- | -| `SHARD_BLOB_SUBNET_COUNT` | `64` | The number of `shard_blob_{subnet_id}` subnets used in the gossipsub protocol. | -| `SHARD_TX_PROPAGATION_GRACE_SLOTS` | `4` | The number of slots for a late transaction to propagate | -| `SHARD_TX_PROPAGATION_BUFFER_SLOTS` | `8` | The number of slots for an early transaction to propagate | - - -## Gossip domain - -### Topics and messages - -Following the same scheme as the [Phase0 gossip topics](../phase0/p2p-interface.md#topics-and-messages), names and payload types are: - -| Name | Message Type | -|---------------------------------|--------------------------| -| `shard_blob_{subnet_id}` | `SignedShardBlob` | -| `shard_blob_header` | `SignedShardBlobHeader` | -| `shard_blob_tx` | `SignedShardBlobHeader` | -| `shard_proposer_slashing` | `ShardProposerSlashing` | - -The [DAS network specification](./das-p2p.md) defines additional topics. - -#### Shard blob subnets - -Shard blob subnets are used by builders to make their blobs available after selection by shard proposers. - -##### `shard_blob_{subnet_id}` - -Shard blob data, in the form of a `SignedShardBlob` is published to the `shard_blob_{subnet_id}` subnets. - -```python -def compute_subnet_for_shard_blob(state: BeaconState, slot: Slot, shard: Shard) -> uint64: - """ - Compute the correct subnet for a shard blob publication. - Note, this mimics compute_subnet_for_attestation(). - """ - committee_index = compute_committee_index_from_shard(state, slot, shard) - committees_per_slot = get_committee_count_per_slot(state, compute_epoch_at_slot(slot)) - slots_since_epoch_start = Slot(slot % SLOTS_PER_EPOCH) - committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - - return uint64((committees_since_epoch_start + committee_index) % SHARD_BLOB_SUBNET_COUNT) -``` - -The following validations MUST pass before forwarding the `signed_blob`, -on the horizontal subnet or creating samples for it. Alias `blob = signed_blob.message`. - -- _[IGNORE]_ The `blob` is published 1 slot early or later (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `blob.slot <= current_slot + 1` - (a client MAY queue future blobs for propagation at the appropriate slot). -- _[IGNORE]_ The `blob` is new enough to still be processed -- - i.e. validate that `compute_epoch_at_slot(blob.slot) >= get_previous_epoch(state)` -- _[REJECT]_ The shard blob is for an active shard -- - i.e. `blob.shard < get_active_shard_count(state, compute_epoch_at_slot(blob.slot))` -- _[REJECT]_ The `blob.shard` MUST have a committee at the `blob.slot` -- - i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard)` doesn't raise an error -- _[REJECT]_ The shard blob is for the correct subnet -- - i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` -- _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. -- _[REJECT]_ The blob is not too large -- the data MUST NOT be larger than the SSZ list-limit, and a client MAY apply stricter bounds. -- _[REJECT]_ The `blob.body.data` MUST NOT contain any point `p >= MODULUS`. Although it is a `uint256`, not the full 256 bit range is valid. -- _[REJECT]_ The blob builder defined by `blob.builder_index` exists and has sufficient balance to back the fee payment. -- _[REJECT]_ The blob signature, `signed_blob.signature`, is valid for the aggregate of proposer and builder -- - i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob.signature)`. -- _[REJECT]_ The blob is proposed by the expected `proposer_index` for the blob's `slot` and `shard`, - in the context of the current shuffling (defined by `blob.body.beacon_block_root`/`slot`). - If the `proposer_index` cannot immediately be verified against the expected shuffling, - the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- - in such a case _do not_ `REJECT`, instead `IGNORE` this message. - -#### Global topics - -There are three additional global topics for Sharding. - -- `shard_blob_header`: co-signed headers to be included on-chain and to serve as a signal to the builder to publish full data. -- `shard_blob_tx`: builder-signed headers, also known as "data transaction". -- `shard_proposer_slashing`: slashings of duplicate shard proposals. - -##### `shard_blob_header` - -Shard header data, in the form of a `SignedShardBlobHeader` is published to the global `shard_blob_header` subnet. -Shard blob headers select shard blob bids by builders -and should be timely to ensure builders can publish the full shard blob before subsequent attestations. - -The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. - -- _[IGNORE]_ The `header` is published 1 slot early or later (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - i.e. validate that `header.slot <= current_slot + 1` - (a client MAY queue future headers for propagation at the appropriate slot). -- _[IGNORE]_ The header is new enough to still be processed -- - i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` -- _[REJECT]_ The shard header is for an active shard -- - i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` -- _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error. -- _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. -- _[REJECT]_ The blob builder defined by `blob.builder_index` exists and has sufficient balance to back the fee payment. -- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for the aggregate of proposer and builder -- - i.e. `bls.FastAggregateVerify([builder_pubkey, proposer_pubkey], blob_signing_root, signed_blob_header.signature)`. -- _[REJECT]_ The header is proposed by the expected `proposer_index` for the blob's `header.slot` and `header.shard` - in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). - If the `proposer_index` cannot immediately be verified against the expected shuffling, - the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- - in such a case _do not_ `REJECT`, instead `IGNORE` this message. - -##### `shard_blob_tx` - -Shard data-transactions in the form of a `SignedShardBlobHeader` are published to the global `shard_blob_tx` subnet. -These shard blob headers are signed solely by the blob-builder. - -The following validations MUST pass before forwarding the `signed_blob_header` on the network. Alias `header = signed_blob_header.message`. - -- _[IGNORE]_ The header is not propagating more than `SHARD_TX_PROPAGATION_BUFFER_SLOTS` slots ahead of time -- - i.e. validate that `header.slot <= current_slot + SHARD_TX_PROPAGATION_BUFFER_SLOTS`. -- _[IGNORE]_ The header is not propagating later than `SHARD_TX_PROPAGATION_GRACE_SLOTS` slots too late -- - i.e. validate that `header.slot + SHARD_TX_PROPAGATION_GRACE_SLOTS >= current_slot` -- _[REJECT]_ The shard header is for an active shard -- - i.e. `header.shard < get_active_shard_count(state, compute_epoch_at_slot(header.slot))` -- _[REJECT]_ The `header.shard` MUST have a committee at the `header.slot` -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error. -- _[IGNORE]_ The header is not stale -- i.e. the corresponding shard proposer has not already selected a header for `(header.slot, header.shard)`. -- _[IGNORE]_ The header is the first header with valid signature received for the `(header.builder_index, header.slot, header.shard)` combination. -- _[REJECT]_ The blob builder, define by `header.builder_index`, exists and has sufficient balance to back the fee payment. -- _[IGNORE]_ The header fee SHOULD be higher than previously seen headers for `(header.slot, header.shard)`, from any builder. - Propagating nodes MAY increase fee increments in case of spam. -- _[REJECT]_ The header signature, `signed_blob_header.signature`, is valid for ONLY the builder -- - i.e. `bls.Verify(builder_pubkey, blob_signing_root, signed_blob_header.signature)`. The signature is not an aggregate with the proposer. -- _[REJECT]_ The header is designated for proposal by the expected `proposer_index` for the blob's `header.slot` and `header.shard` - in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). - If the `proposer_index` cannot immediately be verified against the expected shuffling, - the blob MAY be queued for later processing while proposers for the blob's branch are calculated -- - in such a case _do not_ `REJECT`, instead `IGNORE` this message. - -##### `shard_proposer_slashing` - -Shard proposer slashings, in the form of `ShardProposerSlashing`, are published to the global `shard_proposer_slashing` topic. - -The following validations MUST pass before forwarding the `shard_proposer_slashing` on to the network. -- _[IGNORE]_ The shard proposer slashing is the first valid shard proposer slashing received - for the proposer with index `proposer_slashing.proposer_index`. - The `proposer_slashing.slot` and `proposer_slashing.shard` are ignored, there are no repeated or per-shard slashings. -- _[REJECT]_ All of the conditions within `process_shard_proposer_slashing` pass validation. diff --git a/ssz/merkle-proofs.md b/ssz/merkle-proofs.md index 6772026fe1..263ebd90a8 100644 --- a/ssz/merkle-proofs.md +++ b/ssz/merkle-proofs.md @@ -1,11 +1,6 @@ # Merkle proof formats -**Notice**: This document is a work-in-progress for researchers and implementers. - -## Table of contents - - - + - [Helper functions](#helper-functions) - [Generalized Merkle tree index](#generalized-merkle-tree-index) @@ -19,8 +14,7 @@ - [`generalized_index_parent`](#generalized_index_parent) - [Merkle multiproofs](#merkle-multiproofs) - - + ## Helper functions @@ -57,7 +51,8 @@ def get_power_of_two_floor(x: int) -> int: ## Generalized Merkle tree index -In a binary Merkle tree, we define a "generalized index" of a node as `2**depth + index`. Visually, this looks as follows: +In a binary Merkle tree, we define a "generalized index" of a node as +`2**depth + index`. Visually, this looks as follows: ``` 1 @@ -66,12 +61,15 @@ In a binary Merkle tree, we define a "generalized index" of a node as `2**depth ... ``` -Note that the generalized index has the convenient property that the two children of node `k` are `2k` and `2k+1`, and also that it equals the position of a node in the linear representation of the Merkle tree that's computed by this function: +Note that the generalized index has the convenient property that the two +children of node `k` are `2k` and `2k+1`, and also that it equals the position +of a node in the linear representation of the Merkle tree that's computed by +this function: ```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. """ @@ -82,13 +80,16 @@ def merkle_tree(leaves: Sequence[Bytes32]) -> Sequence[Bytes32]: return o ``` -We define a custom type `GeneralizedIndex` as a Python integer type in this document. It can be represented as a Bitvector/Bitlist object as well. +We define a custom type `GeneralizedIndex` as a Python integer type in this +document. It can be represented as a Bitvector/Bitlist object as well. We will define Merkle proofs in terms of generalized indices. ## SSZ object to index -We can describe the hash tree of any SSZ object, rooted in `hash_tree_root(object)`, as a binary Merkle tree whose depth may vary. For example, an object `{x: bytes32, y: List[uint64]}` would look as follows: +We can describe the hash tree of any SSZ object, rooted in +`hash_tree_root(object)`, as a binary Merkle tree whose depth may vary. For +example, an object `{x: bytes32, y: List[uint64]}` would look as follows: ``` root @@ -101,7 +102,16 @@ y_data_root len(y) ....... ``` -We can now define a concept of a "path", a way of describing a function that takes as input an SSZ object and outputs some specific (possibly deeply nested) member. For example, `foo -> foo.x` is a path, as are `foo -> len(foo.y)` and `foo -> foo.y[5].w`. We'll describe paths as lists, which can have two representations. In "human-readable form", they are `["x"]`, `["y", "__len__"]` and `["y", 5, "w"]` respectively. In "encoded form", they are lists of `uint64` values, in these cases (assuming the fields of `foo` in order are `x` then `y`, and `w` is the first field of `y[i]`) `[0]`, `[1, 2**64-1]`, `[1, 5, 0]`. We define `SSZVariableName` as the member variable name string, i.e., a path is presented as a sequence of integers and `SSZVariableName`. +We can now define a concept of a "path", a way of describing a function that +takes as input an SSZ object and outputs some specific (possibly deeply nested) +member. For example, `foo -> foo.x` is a path, as are `foo -> len(foo.y)` and +`foo -> foo.y[5].w`. We'll describe paths as lists, which can have two +representations. In "human-readable form", they are `["x"]`, `["y", "__len__"]` +and `["y", 5, "w"]` respectively. In "encoded form", they are lists of `uint64` +values, in these cases (assuming the fields of `foo` in order are `x` then `y`, +and `w` is the first field of `y[i]`) `[0]`, `[1, 2**64-1]`, `[1, 5, 0]`. We +define `SSZVariableName` as the member variable name string, i.e., a path is +presented as a sequence of integers and `SSZVariableName`. ```python def item_length(typ: SSZType) -> int: @@ -115,8 +125,9 @@ def item_length(typ: SSZType) -> int: ``` ```python -def get_elem_type(typ: Union[BaseBytes, BaseList, Container], - index_or_variable_name: Union[int, SSZVariableName]) -> SSZType: +def get_elem_type( + typ: Union[BaseBytes, BaseList, Container], index_or_variable_name: Union[int, SSZVariableName] +) -> SSZType: """ Return the type of the element of an object of the given type with the given index or member variable name (eg. `7` for `x[7]`, `"foo"` for `x.foo`) @@ -147,7 +158,9 @@ def chunk_count(typ: SSZType) -> int: ``` ```python -def get_item_position(typ: SSZType, index_or_variable_name: Union[int, SSZVariableName]) -> Tuple[int, int, int]: +def get_item_position( + typ: SSZType, index_or_variable_name: Union[int, SSZVariableName] +) -> Tuple[int, int, int]: """ Return three variables: (i) the index of the chunk in which the given element of the item is represented; @@ -161,35 +174,47 @@ def get_item_position(typ: SSZType, index_or_variable_name: Union[int, SSZVariab return start // 32, start % 32, start % 32 + item_length(typ.elem_type) elif issubclass(typ, Container): variable_name = index_or_variable_name - return typ.get_field_names().index(variable_name), 0, item_length(get_elem_type(typ, variable_name)) + return ( + typ.get_field_names().index(variable_name), + 0, + item_length(get_elem_type(typ, variable_name)), + ) else: raise Exception("Only lists/vectors/containers supported") ``` ```python -def get_generalized_index(typ: SSZType, path: Sequence[Union[int, SSZVariableName]]) -> GeneralizedIndex: +def get_generalized_index(typ: SSZType, *path: PyUnion[int, SSZVariableName]) -> GeneralizedIndex: """ Converts a path (eg. `[7, "foo", 3]` for `x[7].foo[3]`, `[12, "bar", "__len__"]` for `len(x[12].bar)`) into the generalized index representing its position in the Merkle tree. """ root = GeneralizedIndex(1) for p in path: - assert not issubclass(typ, BasicValue) # If we descend to a basic type, the path cannot continue further - if p == '__len__': - typ = uint64 + # If we descend to a basic type, the path cannot continue further + assert not issubclass(typ, BasicValue) + if p == "__len__": assert issubclass(typ, (List, ByteList)) + typ = uint64 root = GeneralizedIndex(root * 2 + 1) else: pos, _, _ = get_item_position(typ, p) - base_index = (GeneralizedIndex(2) if issubclass(typ, (List, ByteList)) else GeneralizedIndex(1)) - root = GeneralizedIndex(root * base_index * get_power_of_two_ceil(chunk_count(typ)) + pos) + base_index = ( + GeneralizedIndex(2) if issubclass(typ, (List, ByteList)) else GeneralizedIndex(1) + ) + root = GeneralizedIndex( + root * base_index * get_power_of_two_ceil(chunk_count(typ)) + pos + ) typ = get_elem_type(typ, p) return root ``` ### Helpers for generalized indices -_Usage note: functions outside this section should manipulate generalized indices using only functions inside this section. This is to make it easier for developers to implement generalized indices with underlying representations other than bigints._ +_Usage note: functions outside this section should manipulate generalized +indices using only functions inside this section. This is to make it easier for +developers to implement generalized indices with underlying representations +other than bigints._ #### `concat_generalized_indices` @@ -248,7 +273,11 @@ def generalized_index_parent(index: GeneralizedIndex) -> GeneralizedIndex: ## Merkle multiproofs -We define a Merkle multiproof as a minimal subset of nodes in a Merkle tree needed to fully authenticate that a set of nodes actually are part of a Merkle tree with some specified root, at a particular set of generalized indices. For example, here is the Merkle multiproof for positions 0, 1, 6 in an 8-node Merkle tree (i.e. generalized indices 8, 9, 14): +We define a Merkle multiproof as a minimal subset of nodes in a Merkle tree +needed to fully authenticate that a set of nodes actually are part of a Merkle +tree with some specified root, at a particular set of generalized indices. For +example, here is the Merkle multiproof for positions 0, 1, 6 in an 8-node Merkle +tree (i.e. generalized indices 8, 9, 14): ``` . @@ -257,9 +286,16 @@ We define a Merkle multiproof as a minimal subset of nodes in a Merkle tree need x x . . . . x * ``` -. are unused nodes, * are used nodes, x are the values we are trying to prove. Notice how despite being a multiproof for 3 values, it requires only 3 auxiliary nodes, only one node more than would be required to prove a single value. Normally the efficiency gains are not quite that extreme, but the savings relative to individual Merkle proofs are still significant. As a rule of thumb, a multiproof for k nodes at the same level of an n-node tree has size `k * (n/k + log(n/k))`. +. are unused nodes, * are used nodes, x are the values we are trying to prove. +Notice how despite being a multiproof for 3 values, it requires only 3 auxiliary +nodes, the same amount required to prove a single value. Normally the efficiency +gains are not quite that extreme, but the savings relative to individual Merkle +proofs are still significant. As a rule of thumb, a multiproof for k nodes at +the same level of an n-node tree has size `k * (n/k + log(n/k))`. -First, we provide a method for computing the generalized indices of the auxiliary tree nodes that a proof of a given set of generalized indices will require: +First, we provide a method for computing the generalized indices of the +auxiliary tree nodes that a proof of a given set of generalized indices will +require: ```python def get_branch_indices(tree_index: GeneralizedIndex) -> Sequence[GeneralizedIndex]: @@ -301,7 +337,8 @@ def get_helper_indices(indices: Sequence[GeneralizedIndex]) -> Sequence[Generali return sorted(all_helper_indices.difference(all_path_indices), reverse=True) ``` -Now we provide the Merkle proof verification functions. First, for single item proofs: +Now we provide the Merkle proof verification functions. First, for single item +proofs: ```python def calculate_merkle_root(leaf: Bytes32, proof: Sequence[Bytes32], index: GeneralizedIndex) -> Root: @@ -315,22 +352,24 @@ def calculate_merkle_root(leaf: Bytes32, proof: Sequence[Bytes32], index: Genera ``` ```python -def verify_merkle_proof(leaf: Bytes32, proof: Sequence[Bytes32], index: GeneralizedIndex, root: Root) -> bool: +def verify_merkle_proof( + leaf: Bytes32, proof: Sequence[Bytes32], index: GeneralizedIndex, root: Root +) -> bool: return calculate_merkle_root(leaf, proof, index) == root ``` Now for multi-item proofs: ```python -def calculate_multi_merkle_root(leaves: Sequence[Bytes32], - proof: Sequence[Bytes32], - indices: Sequence[GeneralizedIndex]) -> Root: +def calculate_multi_merkle_root( + leaves: Sequence[Bytes32], proof: Sequence[Bytes32], indices: Sequence[GeneralizedIndex] +) -> Root: assert len(leaves) == len(indices) helper_indices = get_helper_indices(indices) assert len(proof) == len(helper_indices) objects = { **{index: node for index, node in zip(indices, leaves)}, - **{index: node for index, node in zip(helper_indices, proof)} + **{index: node for index, node in zip(helper_indices, proof)}, } keys = sorted(objects.keys(), reverse=True) pos = 0 @@ -338,8 +377,7 @@ def calculate_multi_merkle_root(leaves: Sequence[Bytes32], k = keys[pos] if k in objects and k ^ 1 in objects and k // 2 not in objects: objects[GeneralizedIndex(k // 2)] = hash( - objects[GeneralizedIndex((k | 1) ^ 1)] + - objects[GeneralizedIndex(k | 1)] + objects[GeneralizedIndex((k | 1) ^ 1)] + objects[GeneralizedIndex(k | 1)] ) keys.append(GeneralizedIndex(k // 2)) pos += 1 @@ -347,11 +385,18 @@ def calculate_multi_merkle_root(leaves: Sequence[Bytes32], ``` ```python -def verify_merkle_multiproof(leaves: Sequence[Bytes32], - proof: Sequence[Bytes32], - indices: Sequence[GeneralizedIndex], - root: Root) -> bool: +def verify_merkle_multiproof( + leaves: Sequence[Bytes32], + proof: Sequence[Bytes32], + indices: Sequence[GeneralizedIndex], + root: Root, +) -> bool: return calculate_multi_merkle_root(leaves, proof, indices) == root ``` -Note that the single-item proof is a special case of a multi-item proof; a valid single-item proof verifies correctly when put into the multi-item verification function (making the natural trivial changes to input arguments, `index -> [index]` and `leaf -> [leaf]`). Note also that `calculate_merkle_root` and `calculate_multi_merkle_root` can be used independently to compute the new Merkle root of a proof with leaves updated. +Note that the single-item proof is a special case of a multi-item proof; a valid +single-item proof verifies correctly when put into the multi-item verification +function (making the natural trivial changes to input arguments, +`index -> [index]` and `leaf -> [leaf]`). Note also that `calculate_merkle_root` +and `calculate_multi_merkle_root` can be used independently to compute the new +Merkle root of a proof with leaves updated. diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 4ef64f2f28..e99689fa2e 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -1,15 +1,13 @@ # SimpleSerialize (SSZ) -## Table of contents - - - + - [Constants](#constants) - [Typing](#typing) - [Basic types](#basic-types) - [Composite types](#composite-types) - [Variable-size and fixed-size](#variable-size-and-fixed-size) + - [Byte](#byte) - [Aliases](#aliases) - [Default values](#default-values) - [`is_zero`](#is_zero) @@ -25,88 +23,113 @@ - [Merkleization](#merkleization) - [Summaries and expansions](#summaries-and-expansions) - [Implementations](#implementations) +- [JSON mapping](#json-mapping) - - + ## Constants -| Name | Value | Description | -|-|-|-| -| `BYTES_PER_CHUNK` | `32` | Number of bytes per chunk. | -| `BYTES_PER_LENGTH_OFFSET` | `4` | Number of bytes per serialized length offset. | -| `BITS_PER_BYTE` | `8` | Number of bits per byte. | +| Name | Value | Description | +| ------------------------- | ----- | --------------------------------------------- | +| `BYTES_PER_CHUNK` | `32` | Number of bytes per chunk. | +| `BYTES_PER_LENGTH_OFFSET` | `4` | Number of bytes per serialized length offset. | +| `BITS_PER_BYTE` | `8` | Number of bits per byte. | ## Typing + ### Basic types -* `uintN`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`) -* `boolean`: `True` or `False` +- `uintN`: `N`-bit unsigned integer (where `N in [8, 16, 32, 64, 128, 256]`) +- `byte`: 8-bit opaque data container, equivalent in serialization and hashing + to `uint8` +- `boolean`: `True` or `False` ### Composite types -* **container**: ordered heterogeneous collection of values - * python dataclass notation with key-type pairs, e.g. - ```python - class ContainerExample(Container): - foo: uint64 - bar: boolean - ``` -* **vector**: ordered fixed-length homogeneous collection, with `N` values - * notation `Vector[type, N]`, e.g. `Vector[uint64, N]` -* **list**: ordered variable-length homogeneous collection, limited to `N` values - * notation `List[type, N]`, e.g. `List[uint64, N]` -* **bitvector**: ordered fixed-length collection of `boolean` values, with `N` bits - * notation `Bitvector[N]` -* **bitlist**: ordered variable-length collection of `boolean` values, limited to `N` bits - * notation `Bitlist[N]` -* **union**: union type containing one of the given subtypes - * notation `Union[type_0, type_1, ...]`, e.g. `union[None, uint64, uint32]` - -*Note*: Both `Vector[boolean, N]` and `Bitvector[N]` are valid, yet distinct due to their different serialization requirements. Similarly, both `List[boolean, N]` and `Bitlist[N]` are valid, yet distinct. Generally `Bitvector[N]`/`Bitlist[N]` are preferred because of their serialization efficiencies. +- **container**: ordered heterogeneous collection of values + - python dataclass notation with key-type pairs, e.g. + ```python + class ContainerExample(Container): + foo: uint64 + bar: boolean + ``` +- **vector**: ordered fixed-length homogeneous collection, with `N` values + - notation `Vector[type, N]`, e.g. `Vector[uint64, N]` +- **list**: ordered variable-length homogeneous collection, limited to `N` + values + - notation `List[type, N]`, e.g. `List[uint64, N]` +- **bitvector**: ordered fixed-length collection of `boolean` values, with `N` + bits + - notation `Bitvector[N]` +- **bitlist**: ordered variable-length collection of `boolean` values, limited + to `N` bits + - notation `Bitlist[N]` +- **union**: union type containing one of the given subtypes + - notation `Union[type_0, type_1, ...]`, e.g. `union[None, uint64, uint32]` + +*Note*: Both `Vector[boolean, N]` and `Bitvector[N]` are valid, yet distinct due +to their different serialization requirements. Similarly, both +`List[boolean, N]` and `Bitlist[N]` are valid, yet distinct. Generally +`Bitvector[N]`/`Bitlist[N]` are preferred because of their serialization +efficiencies. ### Variable-size and fixed-size -We recursively define "variable-size" types to be lists, unions, `Bitlist` and all types that contain a variable-size type. All other types are said to be "fixed-size". +We recursively define "variable-size" types to be lists, unions, `Bitlist` and +all types that contain a variable-size type. All other types are said to be +"fixed-size". + +### Byte + +Although the SSZ serialization of `byte` is equivalent to that of `uint8`, the +former is used for opaque data while the latter is intended as a number. ### Aliases For convenience we alias: -* `bit` to `boolean` -* `byte` to `uint8` (this is a basic type) -* `BytesN` and `ByteVector[N]` to `Vector[byte, N]` (this is *not* a basic type) -* `ByteList[N]` to `List[byte, N]` +- `bit` to `boolean` +- `BytesN` and `ByteVector[N]` to `Vector[byte, N]` (this is *not* a basic type) +- `ByteList[N]` to `List[byte, N]` + +Aliases are semantically equivalent to their underlying type and therefore share +canonical representations both in SSZ and in related formats. ### Default values -Assuming a helper function `default(type)` which returns the default value for `type`, we can recursively define the default value for all types. - -| Type | Default Value | -| ---- | ------------- | -| `uintN` | `0` | -| `boolean` | `False` | -| `Container` | `[default(type) for type in container]` | -| `Vector[type, N]` | `[default(type)] * N` | -| `Bitvector[N]` | `[False] * N` | -| `List[type, N]` | `[]` | -| `Bitlist[N]` | `[]` | -| `Union[type_0, type_1, ...]` | `default(type_0)` | + +Assuming a helper function `default(type)` which returns the default value for +`type`, we can recursively define the default value for all types. + +| Type | Default Value | +| ---------------------------- | --------------------------------------- | +| `uintN` | `0` | +| `boolean` | `False` | +| `Container` | `[default(type) for type in container]` | +| `Vector[type, N]` | `[default(type)] * N` | +| `Bitvector[N]` | `[False] * N` | +| `List[type, N]` | `[]` | +| `Bitlist[N]` | `[]` | +| `Union[type_0, type_1, ...]` | `default(type_0)` | #### `is_zero` -An SSZ object is called zeroed (and thus, `is_zero(object)` returns true) if it is equal to the default value for that type. +An SSZ object is called zeroed (and thus, `is_zero(object)` returns true) if it +is equal to the default value for that type. ### Illegal types - Empty vector types (`Vector[type, 0]`, `Bitvector[0]`) are illegal. - Containers with no fields are illegal. -- The `None` type option in a `Union` type is only legal as the first option (i.e. with index zero). +- The `None` type option in a `Union` type is only legal as the first option + (i.e. with index zero). ## Serialization -We recursively define the `serialize` function which consumes an object `value` (of the type specified) and returns a bytestring of type `bytes`. +We recursively define the `serialize` function which consumes an object `value` +(of the type specified) and returns a bytestring of type `bytes`. -*Note*: In the function definitions below (`serialize`, `hash_tree_root`, `is_variable_size`, etc.) objects implicitly carry their type. +*Note*: In the function definitions below (`serialize`, `hash_tree_root`, +`is_variable_size`, etc.) objects implicitly carry their type. ### `uintN` @@ -133,7 +156,9 @@ return bytes(array) ### `Bitlist[N]` -Note that from the offset coding, the length (in bytes) of the bitlist is known. An additional `1` bit is added to the end, at index `e` where `e` is the length of the bitlist (not the limit), so that the length in bits will also be known. +Note that from the offset coding, the length (in bytes) of the bitlist is known. +An additional `1` bit is added to the end, at index `e` where `e` is the length +of the bitlist (not the limit), so that the length in bits will also be known. ```python array = [0] * ((len(value) // 8) + 1) @@ -153,10 +178,12 @@ variable_parts = [serialize(element) if is_variable_size(element) else b"" for e # Compute and check lengths fixed_lengths = [len(part) if part != None else BYTES_PER_LENGTH_OFFSET for part in fixed_parts] variable_lengths = [len(part) for part in variable_parts] -assert sum(fixed_lengths + variable_lengths) < 2**(BYTES_PER_LENGTH_OFFSET * BITS_PER_BYTE) +assert sum(fixed_lengths + variable_lengths) < 2 ** (BYTES_PER_LENGTH_OFFSET * BITS_PER_BYTE) # Interleave offsets of variable-size parts with fixed-size parts -variable_offsets = [serialize(uint32(sum(fixed_lengths + variable_lengths[:i]))) for i in range(len(value))] +variable_offsets = [ + serialize(uint32(sum(fixed_lengths + variable_lengths[:i]))) for i in range(len(value)) +] fixed_parts = [part if part != None else variable_offsets[i] for i, part in enumerate(fixed_parts)] # Return the concatenation of the fixed-size parts (offsets interleaved) with the variable-size parts @@ -165,15 +192,19 @@ return b"".join(fixed_parts + variable_parts) ### Union -A `value` as `Union[T...]` type has properties `value.value` with the contained value, and `value.selector` which indexes the selected `Union` type option `T`. +A `value` as `Union[T...]` type has properties `value.value` with the contained +value, and `value.selector` which indexes the selected `Union` type option `T`. A `Union`: + - May have multiple selectors with the same type. -- Should not use selectors above 127 (i.e. highest bit is set), these are reserved for backwards compatible extensions. +- Should not use selectors above 127 (i.e. highest bit is set), these are + reserved for backwards compatible extensions. - Must have at least 1 type option. - May have `None` as first type option, i.e. `selector == 0` - Must have at least 2 type options if the first is `None` -- Is always considered a variable-length type, even if all type options have an equal fixed-length. +- Is always considered a variable-length type, even if all type options have an + equal fixed-length. ```python if value.value is None: @@ -187,72 +218,163 @@ else: ## Deserialization -Because serialization is an injective function (i.e. two distinct objects of the same type will serialize to different values) any bytestring has at most one object it could deserialize to. - -Deserialization can be implemented using a recursive algorithm. The deserialization of basic objects is easy, and from there we can find a simple recursive algorithm for all fixed-size objects. For variable-size objects we have to do one of the following depending on what kind of object it is: - -* Vector/list of a variable-size object: The serialized data will start with offsets of all the serialized objects (`BYTES_PER_LENGTH_OFFSET` bytes each). - * Using the first offset, we can compute the length of the list (divide by `BYTES_PER_LENGTH_OFFSET`), as it gives us the total number of bytes in the offset data. - * The size of each object in the vector/list can be inferred from the difference of two offsets. To get the size of the last object, the total number of bytes has to be known (it is not generally possible to deserialize an SSZ object of unknown length) -* Containers follow the same principles as vectors, with the difference that there may be fixed-size objects in a container as well. This means the `fixed_parts` data will contain offsets as well as fixed-size objects. -* In the case of bitlists, the length in bits cannot be uniquely inferred from the number of bytes in the object. Because of this, they have a bit at the end that is always set. This bit has to be used to infer the size of the bitlist in bits. -* In the case of unions, the first byte of the deserialization scope is deserialized as type selector, the remainder of the scope is deserialized as the selected type. - -Note that deserialization requires hardening against invalid inputs. A non-exhaustive list: +Because serialization is an injective function (i.e. two distinct objects of the +same type will serialize to different values) any bytestring has at most one +object it could deserialize to. + +Deserialization can be implemented using a recursive algorithm. The +deserialization of basic objects is easy, and from there we can find a simple +recursive algorithm for all fixed-size objects. For variable-size objects we +have to do one of the following depending on what kind of object it is: + +- Vector/list of a variable-size object: The serialized data will start with + offsets of all the serialized objects (`BYTES_PER_LENGTH_OFFSET` bytes each). + - Using the first offset, we can compute the length of the list (divide by + `BYTES_PER_LENGTH_OFFSET`), as it gives us the total number of bytes in the + offset data. + - The size of each object in the vector/list can be inferred from the + difference of two offsets. To get the size of the last object, the total + number of bytes has to be known (it is not generally possible to deserialize + an SSZ object of unknown length) +- Containers follow the same principles as vectors, with the difference that + there may be fixed-size objects in a container as well. This means the + `fixed_parts` data will contain offsets as well as fixed-size objects. +- In the case of bitlists, the length in bits cannot be uniquely inferred from + the number of bytes in the object. Because of this, they have a bit at the end + that is always set. This bit has to be used to infer the size of the bitlist + in bits. +- In the case of unions, the first byte of the deserialization scope is + deserialized as type selector, the remainder of the scope is deserialized as + the selected type. + +Note that deserialization requires hardening against invalid inputs. A +non-exhaustive list: - Offsets: out of order, out of range, mismatching minimum element size. - Scope: Extra unused bytes, not aligned with element size. - More elements than a list limit allows. Part of enforcing consensus. - An out-of-bounds selected index in an `Union` -Efficient algorithms for computing this object can be found in [the implementations](#implementations). +Efficient algorithms for computing this object can be found in +[the implementations](#implementations). ## Merkleization We first define helper functions: -* `size_of(B)`, where `B` is a basic type: the length, in bytes, of the serialized form of the basic type. -* `chunk_count(type)`: calculate the amount of leafs for merkleization of the type. - * all basic types: `1` - * `Bitlist[N]` and `Bitvector[N]`: `(N + 255) // 256` (dividing by chunk size, rounding up) - * `List[B, N]` and `Vector[B, N]`, where `B` is a basic type: `(N * size_of(B) + 31) // 32` (dividing by chunk size, rounding up) - * `List[C, N]` and `Vector[C, N]`, where `C` is a composite type: `N` - * containers: `len(fields)` -* `pack(values)`: Given ordered objects of the same basic type: - 1. Serialize `values` into bytes. - 2. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with zeroes to the next multiple. - 3. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. - 4. Return the chunks. -* `pack_bits(bits)`: Given the bits of bitlist or bitvector, get `bitfield_bytes` by packing them in bytes and aligning to the start. The length-delimiting bit for bitlists is excluded. Then return `pack(bitfield_bytes)`. -* `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power of 2, with 0 mapping to 1. Examples: `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16` -* `merkleize(chunks, limit=None)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, merkleize the chunks, and return the root: - * The merkleization depends on the effective input, which must be padded/limited: - - if no limit: pad the `chunks` with zeroed chunks to `next_pow_of_two(len(chunks))` (virtually for memory efficiency). - - if `limit >= len(chunks)`, pad the `chunks` with zeroed chunks to `next_pow_of_two(limit)` (virtually for memory efficiency). - - if `limit < len(chunks)`: do not merkleize, input exceeds limit. Raise an error instead. - * Then, merkleize the chunks (empty input is padded to 1 zero chunk): - - If `1` chunk: the root is the chunk itself. - - If `> 1` chunks: merkleize as binary tree. -* `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` little-endian serialization) return `hash(root + length)`. -* `mix_in_selector`: Given a Merkle root `root` and a type selector `selector` (`"uint256"` little-endian serialization) return `hash(root + selector)`. - -We now define Merkleization `hash_tree_root(value)` of an object `value` recursively: - -* `merkleize(pack(value))` if `value` is a basic object or a vector of basic objects. -* `merkleize(pack_bits(value), limit=chunk_count(type))` if `value` is a bitvector. -* `mix_in_length(merkleize(pack(value), limit=chunk_count(type)), len(value))` if `value` is a list of basic objects. -* `mix_in_length(merkleize(pack_bits(value), limit=chunk_count(type)), len(value))` if `value` is a bitlist. -* `merkleize([hash_tree_root(element) for element in value])` if `value` is a vector of composite objects or a container. -* `mix_in_length(merkleize([hash_tree_root(element) for element in value], limit=chunk_count(type)), len(value))` if `value` is a list of composite objects. -* `mix_in_selector(hash_tree_root(value.value), value.selector)` if `value` is of union type, and `value.value` is not `None` -* `mix_in_selector(Bytes32(), 0)` if `value` is of union type, and `value.value` is `None` +- `size_of(B)`, where `B` is a basic type: the length, in bytes, of the + serialized form of the basic type. +- `chunk_count(type)`: calculate the amount of leaves for merkleization of the + type. + - all basic types: `1` + - `Bitlist[N]` and `Bitvector[N]`: `(N + 255) // 256` (dividing by chunk size, + rounding up) + - `List[B, N]` and `Vector[B, N]`, where `B` is a basic type: + `(N * size_of(B) + 31) // 32` (dividing by chunk size, rounding up) + - `List[C, N]` and `Vector[C, N]`, where `C` is a composite type: `N` + - containers: `len(fields)` +- `pack(values)`: Given ordered objects of the same basic type: + 1. Serialize `values` into bytes. + 2. If not aligned to a multiple of `BYTES_PER_CHUNK` bytes, right-pad with + zeroes to the next multiple. + 3. Partition the bytes into `BYTES_PER_CHUNK`-byte chunks. + 4. Return the chunks. +- `pack_bits(bits)`: Given the bits of bitlist or bitvector, get + `bitfield_bytes` by packing them in bytes and aligning to the start. The + length-delimiting bit for bitlists is excluded. Then return + `pack(bitfield_bytes)`. +- `next_pow_of_two(i)`: get the next power of 2 of `i`, if not already a power + of 2, with 0 mapping to 1. Examples: + `0->1, 1->1, 2->2, 3->4, 4->4, 6->8, 9->16` +- `merkleize(chunks, limit=None)`: Given ordered `BYTES_PER_CHUNK`-byte chunks, + merkleize the chunks, and return the root: + - The merkleization depends on the effective input, which must be + padded/limited: + - if no limit: pad the `chunks` with zeroed chunks to + `next_pow_of_two(len(chunks))` (virtually for memory efficiency). + - if `limit >= len(chunks)`, pad the `chunks` with zeroed chunks to + `next_pow_of_two(limit)` (virtually for memory efficiency). + - if `limit < len(chunks)`: do not merkleize, input exceeds limit. Raise an + error instead. + - Then, merkleize the chunks (empty input is padded to 1 zero chunk): + - If `1` chunk: the root is the chunk itself. + - If `> 1` chunks: merkleize as binary tree. +- `mix_in_length`: Given a Merkle root `root` and a length `length` (`"uint256"` + little-endian serialization) return `hash(root + length)`. +- `mix_in_selector`: Given a Merkle root `root` and a type selector `selector` + (`"uint256"` little-endian serialization) return `hash(root + selector)`. + +We now define Merkleization `hash_tree_root(value)` of an object `value` +recursively: + +- `merkleize(pack(value))` if `value` is a basic object or a vector of basic + objects. +- `merkleize(pack_bits(value), limit=chunk_count(type))` if `value` is a + bitvector. +- `mix_in_length(merkleize(pack(value), limit=chunk_count(type)), len(value))` + if `value` is a list of basic objects. +- `mix_in_length(merkleize(pack_bits(value), limit=chunk_count(type)), len(value))` + if `value` is a bitlist. +- `merkleize([hash_tree_root(element) for element in value])` if `value` is a + vector of composite objects or a container. +- `mix_in_length(merkleize([hash_tree_root(element) for element in value], limit=chunk_count(type)), len(value))` + if `value` is a list of composite objects. +- `mix_in_selector(hash_tree_root(value.value), value.selector)` if `value` is + of union type, and `value.value` is not `None` +- `mix_in_selector(Bytes32(), 0)` if `value` is of union type, and `value.value` + is `None` ## Summaries and expansions -Let `A` be an object derived from another object `B` by replacing some of the (possibly nested) values of `B` by their `hash_tree_root`. We say `A` is a "summary" of `B`, and that `B` is an "expansion" of `A`. Notice `hash_tree_root(A) == hash_tree_root(B)`. +Let `A` be an object derived from another object `B` by replacing some of the +(possibly nested) values of `B` by their `hash_tree_root`. We say `A` is a +"summary" of `B`, and that `B` is an "expansion" of `A`. Notice +`hash_tree_root(A) == hash_tree_root(B)`. -We similarly define "summary types" and "expansion types". For example, [`BeaconBlock`](../specs/phase0/beacon-chain.md#beaconblock) is an expansion type of [`BeaconBlockHeader`](../specs/phase0/beacon-chain.md#beaconblockheader). Notice that objects expand to at most one object of a given expansion type. For example, `BeaconBlockHeader` objects uniquely expand to `BeaconBlock` objects. +We similarly define "summary types" and "expansion types". For example, +[`BeaconBlock`](../specs/phase0/beacon-chain.md#beaconblock) is an expansion +type of +[`BeaconBlockHeader`](../specs/phase0/beacon-chain.md#beaconblockheader). Notice +that objects expand to at most one object of a given expansion type. For +example, `BeaconBlockHeader` objects uniquely expand to `BeaconBlock` objects. ## Implementations -See https://github.com/ethereum/eth2.0-specs/issues/2138 for a list of current known implementations. +See https://github.com/ethereum/consensus-specs/issues/2138 for a list of +current known implementations. + +## JSON mapping + +The canonical JSON mapping assigns to each SSZ type a corresponding JSON +encoding, enabling an SSZ schema to also define the JSON encoding. + +When decoding JSON data, all fields in the SSZ schema must be present with a +value. Parsers may ignore additional JSON fields. + +| SSZ | JSON | Example | +| ---------------------------- | --------------- | ---------------------------------------- | +| `uintN` | string | `"0"` | +| `byte` | hex-byte-string | `"0x00"` | +| `boolean` | bool | `false` | +| `Container` | object | `{ "field": ... }` | +| `Vector[type, N]` | array | `[element, ...]` | +| `Vector[byte, N]` | hex-byte-string | `"0x1122"` | +| `Bitvector[N]` | hex-byte-string | `"0x1122"` | +| `List[type, N]` | array | `[element, ...]` | +| `List[byte, N]` | hex-byte-string | `"0x1122"` | +| `Bitlist[N]` | hex-byte-string | `"0x1122"` | +| `Union[type_0, type_1, ...]` | selector-object | `{ "selector": number, "data": type_N }` | + +Integers are encoded as strings to avoid loss of precision in 64-bit values. + +Aliases are encoded as their underlying type. + +`hex-byte-string` is a `0x`-prefixed hex encoding of byte data, as it would +appear in an SSZ stream. + +`List` and `Vector` of `byte` (and aliases thereof) are encoded as +`hex-byte-string`. `Bitlist` and `Bitvector` similarly map their SSZ-byte +encodings to a `hex-byte-string`. + +`Union` is encoded as an object with a `selector` and `data` field, where the +contents of `data` change according to the selector. diff --git a/sync/optimistic.md b/sync/optimistic.md new file mode 100644 index 0000000000..c63ca8711c --- /dev/null +++ b/sync/optimistic.md @@ -0,0 +1,447 @@ +# Optimistic Sync + + + +- [Introduction](#introduction) +- [Constants](#constants) +- [Helpers](#helpers) +- [Mechanisms](#mechanisms) + - [When to optimistically import blocks](#when-to-optimistically-import-blocks) + - [How to optimistically import blocks](#how-to-optimistically-import-blocks) + - [How to apply `latestValidHash` when payload status is `INVALID`](#how-to-apply-latestvalidhash-when-payload-status-is-invalid) + - [Execution Engine Errors](#execution-engine-errors) + - [Assumptions about Execution Engine Behaviour](#assumptions-about-execution-engine-behaviour) + - [Re-Orgs](#re-orgs) +- [Fork Choice](#fork-choice) + - [Fork Choice Poisoning](#fork-choice-poisoning) +- [Checkpoint Sync (Weak Subjectivity Sync)](#checkpoint-sync-weak-subjectivity-sync) +- [Validator assignments](#validator-assignments) + - [Block Production](#block-production) + - [Attesting](#attesting) + - [Participating in Sync Committees](#participating-in-sync-committees) +- [Ethereum Beacon APIs](#ethereum-beacon-apis) +- [Design Decision Rationale](#design-decision-rationale) + - [Why sync optimistically?](#why-sync-optimistically) + - [Why `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`?](#why-safe_slots_to_import_optimistically) + - [Transitioning from VALID -> INVALIDATED or INVALIDATED -> VALID](#transitioning-from-valid---invalidated-or-invalidated---valid) + - [What about Light Clients?](#what-about-light-clients) + - [What if `TERMINAL_BLOCK_HASH` is used?](#what-if-terminal_block_hash-is-used) + + + +## Introduction + +In order to provide a syncing execution engine with a partial view of the head +of the chain, it may be desirable for a consensus engine to import beacon blocks +without verifying the execution payloads. This partial sync is called an +*optimistic sync*. + +Optimistic sync is designed to be opt-in and backwards compatible (i.e., +non-optimistic nodes can tolerate optimistic nodes on the network and vice +versa). Optimistic sync is not a fundamental requirement for consensus nodes. +Rather, it's a stop-gap measure to allow execution nodes to sync via established +methods until future Ethereum roadmap items are implemented (e.g., +statelessness). + +## Constants + +| Name | Value | Unit | +| ------------------------------------- | ----- | ----- | +| `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` | `128` | slots | + +*Note: the `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` must be user-configurable. See +[Fork Choice Poisoning](#fork-choice-poisoning).* + +## Helpers + +For brevity, we define two aliases for values of the `status` field on +`PayloadStatusV1`: + +- Alias `NOT_VALIDATED` to: + - `SYNCING` + - `ACCEPTED` +- Alias `INVALIDATED` to: + - `INVALID` + - `INVALID_BLOCK_HASH` + +Let `head: BeaconBlock` be the result of calling of the fork choice algorithm at +the time of block production. Let `head_block_root: Root` be the root of that +block. + +Let `blocks: Dict[Root, BeaconBlock]` and +`block_states: Dict[Root, BeaconState]` be the blocks (and accompanying states) +that have been verified either completely or optimistically. + +Let `optimistic_roots: Set[Root]` be the set of `hash_tree_root(block)` for all +optimistically imported blocks which have only received a `NOT_VALIDATED` +designation from an execution engine (i.e., they are not known to be +`INVALIDATED` or `VALID`). + +Let `current_slot: Slot` be `(time - genesis_time) // SECONDS_PER_SLOT` where +`time` is the UNIX time according to the local system clock. + +```python +@dataclass +class OptimisticStore(object): + optimistic_roots: Set[Root] + head_block_root: Root + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) +``` + +```python +def is_optimistic(opt_store: OptimisticStore, block: BeaconBlock) -> bool: + return hash_tree_root(block) in opt_store.optimistic_roots +``` + +```python +def latest_verified_ancestor(opt_store: OptimisticStore, block: BeaconBlock) -> BeaconBlock: + # It is assumed that the `block` parameter is never an INVALIDATED block. + while True: + if not is_optimistic(opt_store, block) or block.parent_root == Root(): + return block + block = opt_store.blocks[block.parent_root] +``` + +```python +def is_execution_block(block: BeaconBlock) -> bool: + return block.body.execution_payload != ExecutionPayload() +``` + +```python +def is_optimistic_candidate_block( + opt_store: OptimisticStore, current_slot: Slot, block: BeaconBlock +) -> bool: + if is_execution_block(opt_store.blocks[block.parent_root]): + return True + + if block.slot + SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY <= current_slot: + return True + + return False +``` + +Let a node be an *optimistic node* if its fork choice is in one of the following +states: + +1. `is_optimistic(opt_store, head) is True` +2. Blocks from every viable (with respect to FFG) branch have transitioned from + `NOT_VALIDATED` to `INVALIDATED` leaving the block tree without viable + branches + +Let only a validator on an optimistic node be an *optimistic validator*. + +When this specification only defines behaviour for an optimistic node/validator, +but *not* for the non-optimistic case, assume default behaviours without regard +for optimistic sync. + +## Mechanisms + +### When to optimistically import blocks + +A block MAY be optimistically imported when +`is_optimistic_candidate_block(opt_store, current_slot, block)` returns `True`. +This ensures that blocks are only optimistically imported if one or more of the +following are true: + +1. The parent of the block has execution enabled. +2. The current slot (as per the system clock) is at least + `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` ahead of the slot of the block being + imported. + +In effect, there are restrictions on when a *merge block* can be optimistically +imported. The merge block is the first block in any chain where +`is_execution_block(block) == True`. Any descendant of a merge block may be +imported optimistically at any time. + +*See [Fork Choice Poisoning](#fork-choice-poisoning) for the motivations behind +these conditions.* + +### How to optimistically import blocks + +To optimistically import a block: + +- The + [`verify_and_notify_new_payload`](../specs/bellatrix/beacon-chain.md#verify_and_notify_new_payload) + function MUST return `True` if the execution engine returns `NOT_VALIDATED` or + `VALID`. An `INVALIDATED` response MUST return `False`. +- The + [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) + function MUST NOT raise an assertion if both the `pow_block` and `pow_parent` + are unknown to the execution engine. + - All other assertions in + [`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) + (e.g., `TERMINAL_BLOCK_HASH`) MUST prevent an optimistic import. +- The parent of the block MUST NOT have an `INVALIDATED` execution payload. + +In addition to this change in validation, the consensus engine MUST track which +blocks returned `NOT_VALIDATED` and which returned `VALID` for subsequent +processing. + +Optimistically imported blocks MUST pass all verifications included in +`process_block` (withstanding the modifications to +`verify_and_notify_new_payload`). + +A consensus engine MUST be able to retrospectively (i.e., after import) modify +the status of `NOT_VALIDATED` blocks to be either `VALID` or `INVALIDATED` based +upon responses from an execution engine. I.e., perform the following +transitions: + +- `NOT_VALIDATED` -> `VALID` +- `NOT_VALIDATED` -> `INVALIDATED` + +When a block transitions from `NOT_VALIDATED` -> `VALID`, all *ancestors* of the +block MUST also transition from `NOT_VALIDATED` -> `VALID`. Such a block and any +previously `NOT_VALIDATED` ancestors are no longer considered "optimistically +imported". + +When a block transitions from `NOT_VALIDATED` -> `INVALIDATED`, all +*descendants* of the block MUST also transition from `NOT_VALIDATED` -> +`INVALIDATED`. + +When a block transitions from the `NOT_VALIDATED` state, it is removed from the +set of `opt_store.optimistic_roots`. + +When a "merge block" (i.e. the first block which enables execution in a chain) +is declared to be `VALID` by an execution engine (either directly or +indirectly), the full +[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +MUST be run against the merge block. If the block fails +[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block), +the merge block MUST be treated the same as an `INVALIDATED` block (i.e., it and +all its descendants are invalidated and removed from the block tree). + +### How to apply `latestValidHash` when payload status is `INVALID` + +Processing an `INVALID` payload status depends on the `latestValidHash` +parameter. The general approach is as follows: + +1. Consensus engine MUST identify `invalidBlock` as per definition in the table + below. +2. `invalidBlock` and all of its descendants MUST be transitioned from + `NOT_VALIDATED` to `INVALIDATED`. + +| `latestValidHash` | `invalidBlock` | +| :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | +| Execution block hash | The *child* of a block with `body.execution_payload.block_hash == latestValidHash` in the chain containing the block with payload in question | +| `0x00..00` (all zeroes) | The first block with `body.execution_payload != ExecutionPayload()` in the chain containing a block with payload in question | +| `null` | Block with payload in question | + +When `latestValidHash` is a meaningful execution block hash but consensus engine +cannot find a block satisfying +`body.execution_payload.block_hash == latestValidHash`, consensus engine SHOULD +behave the same as if `latestValidHash` was `null`. + +### Execution Engine Errors + +When an execution engine returns an error or fails to respond to a payload +validity request for some block, a consensus engine: + +- MUST NOT optimistically import the block. +- MUST NOT apply the block to the fork choice store. +- MAY queue the block for later processing. + +### Assumptions about Execution Engine Behaviour + +This specification assumes execution engines will only return `NOT_VALIDATED` +when there is insufficient information available to make a `VALID` or +`INVALIDATED` determination on the given `ExecutionPayload` (e.g., the parent +payload is unknown). Specifically, `NOT_VALIDATED` responses should be +fork-specific, in that the search for a block on one chain MUST NOT trigger a +`NOT_VALIDATED` response for another chain. + +### Re-Orgs + +The consensus engine MUST support any chain reorganisation which does *not* +affect the justified checkpoint. + +If the justified checkpoint transitions from `NOT_VALIDATED` -> `INVALIDATED`, a +consensus engine MAY choose to alert the user and force the application to exit. + +## Fork Choice + +Consensus engines MUST support removing blocks from fork choice that transition +from `NOT_VALIDATED` to `INVALIDATED`. Specifically, a block deemed +`INVALIDATED` at any point MUST NOT be included in the canonical chain and the +weights from those `INVALIDATED` blocks MUST NOT be applied to any `VALID` or +`NOT_VALIDATED` ancestors. + +### Fork Choice Poisoning + +During the merge transition it is possible for an attacker to craft a +`BeaconBlock` with an execution payload that references an eternally-unavailable +`body.execution_payload.parent_hash` (i.e., the parent hash is random bytes). In +rare circumstances, it is possible that an attacker can build atop such a block +to trigger justification. If an optimistic node imports this malicious chain, +that node will have a "poisoned" fork choice store, such that the node is unable +to produce a block that descends from the head (due to the invalid chain of +payloads) and the node is unable to produce a block that forks around the head +(due to the justification of the malicious chain). + +If an honest chain exists which justifies a higher epoch than the malicious +chain, that chain will take precedence and revive any poisoned store. Such a +chain, if imported before the malicious chain, will prevent the store from being +poisoned. Therefore, the poisoning attack is temporary if >= 2/3rds of the +network is honest and non-faulty. + +The `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` parameter assumes that the network +will justify a honest chain within some number of slots. With this assumption, +it is acceptable to optimistically import transition blocks during the sync +process. Since there is an assumption that an honest chain with a higher +justified checkpoint exists, any fork choice poisoning will be short-lived and +resolved before that node is required to produce a block. + +However, the assumption that the honest, canonical chain will always justify +within `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` slots is dubious. Therefore, +clients MUST provide the following command line flag to assist with manual +disaster recovery: + +- `--safe-slots-to-import-optimistically`: modifies the + `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. + +## Checkpoint Sync (Weak Subjectivity Sync) + +A consensus engine MAY assume that the `ExecutionPayload` of a block used as an +anchor for checkpoint sync is `VALID` without necessarily providing that payload +to an execution engine. + +## Validator assignments + +An optimistic node is *not* a full node. It is unable to produce blocks, since +an execution engine cannot produce a payload upon an unknown parent. It cannot +faithfully attest to the head block of the chain, since it has not fully +verified that block. + +### Block Production + +An optimistic validator MUST NOT produce a block (i.e., sign across the +`DOMAIN_BEACON_PROPOSER` domain). + +### Attesting + +An optimistic validator MUST NOT participate in attestation (i.e., sign across +the `DOMAIN_BEACON_ATTESTER`, `DOMAIN_SELECTION_PROOF` or +`DOMAIN_AGGREGATE_AND_PROOF` domains). + +### Participating in Sync Committees + +An optimistic validator MUST NOT participate in sync committees (i.e., sign +across the `DOMAIN_SYNC_COMMITTEE`, `DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF` or +`DOMAIN_CONTRIBUTION_AND_PROOF` domains). + +## Ethereum Beacon APIs + +Consensus engines which provide an implementation of the +[Ethereum Beacon APIs](https://github.com/ethereum/beacon-APIs) must take care +to ensure the `execution_optimistic` value is set to `True` whenever the request +references optimistic blocks (and vice-versa). + +## Design Decision Rationale + +### Why sync optimistically? + +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. If not constantly fed with the most recent head, state sync won't be +able to complete because the recent state soon becomes unavailable due to state +trie pruning. + +Optimistic block import (i.e. import when the execution engine *cannot* +currently validate the payload) breaks a deadlock between the execution layer +sync process and importing beacon blocks while the execution engine is syncing. + +Optimistic sync is also an optimal strategy for execution engines using block +execution as a default sync mechanism (e.g. Erigon). Alternatively, a consensus +engine may inform the execution engine with a payload obtained from a checkpoint +block, then wait until the execution layer catches up with it and proceed in +lock step after that. This alternative approach would keep user in limbo for +several hours and would increase time of the sync process as batch sync has more +opportunities for optimisation than the lock step. + +Aforementioned premises make optimistic sync a *generalized* solution for +interaction between consensus and execution engines during the sync process. + +### Why `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`? + +Nodes can only import an optimistic block if their justified checkpoint is +verified or the block is older than `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY`. + +These restraints are applied in order to mitigate an attack where a block which +enables execution (a *transition block*) can reference a junk parent hash. This +makes it impossible for honest nodes to build atop that block. If an attacker +exploits a nuance in fork choice `filter_block_tree`, they can, in some rare +cases, produce a junk block that out-competes all locally produced blocks for +the head. This prevents a node from producing a chain of blocks, therefore +breaking liveness. + +Thankfully, if 2/3rds of validators are not poisoned, they can justify an honest +chain which will un-poison all other nodes. + +Notably, this attack only exists for optimistic nodes. Nodes which fully verify +the transition block will reject a block with a junk parent hash. Therefore, +liveness is unaffected if a vast majority of nodes have fully synced execution +and consensus clients before and during the transition. + +Given all of this, we can say two things: + +1. **BNs which are following the head during the transition shouldn't + optimistically import the transition block.** If 1/3rd of validators + optimistically import the poison block, there will be no remaining nodes to + justify an honest chain. +2. **BNs which are syncing can optimistically import transition blocks.** In + this case a justified chain already exists blocks. The poison block would be + quickly reverted and would have no effect on liveness. + +Astute readers will notice that (2) contains a glaring assumption about network +liveness. This is necessary because a node cannot feasibly ascertain that the +transition block is justified without importing that block and risking +poisoning. Therefore, we use `SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` to say +something along the lines of: *"if the transition block is sufficiently old +enough, then we can just assume that block is honest or there exists an honest +justified chain to out-compete it."* + +Note the use of "feasibly" in the previous paragraph. One can imagine mechanisms +to check that a block is justified before importing it. For example, just keep +processing blocks without adding them to fork choice. However, there are still +edge-cases here (e.g., when to halt and declare there was no justification?) and +how to mitigate implementation complexity. At this point, it's important to +reflect on the attack and how likely it is to happen. It requires some rather +contrived circumstances and it seems very unlikely to occur. Therefore, we need +to consider if adding complexity to avoid an unlikely attack increases or +decreases our total risk. Presently, it appears that +`SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY` sits in a sweet spot for this trade-off. + +### Transitioning from VALID -> INVALIDATED or INVALIDATED -> VALID + +These operations are purposefully omitted. It is outside of the scope of the +specification since it's only possible with a faulty EE. + +Such a scenario requires manual intervention. + +### What about Light Clients? + +An alternative to optimistic sync is to run a light client inside/alongside +beacon nodes that mitigates the need for optimistic sync by providing +tip-of-chain blocks to the execution engine. However, light clients come with +their own set of complexities. Relying on light clients may also restrict nodes +from syncing from genesis, if they so desire. + +A notable thing about optimistic sync is that it's *optional*. Should an +implementation decide to go the light-client route, then they can just ignore +optimistic sync altogether. + +### What if `TERMINAL_BLOCK_HASH` is used? + +If the terminal block hash override is used (i.e., +`TERMINAL_BLOCK_HASH != Hash32()`), the +[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +function will deterministically return `True` or `False`. Whilst it's not +*technically* required retrospectively call +[`validate_merge_block`](../specs/bellatrix/fork-choice.md#validate_merge_block) +on a transition block that matches `TERMINAL_BLOCK_HASH` after an optimistic +sync, doing so will have no effect. For simplicity, the optimistic sync +specification does not define edge-case behaviour for when `TERMINAL_BLOCK_HASH` +is used. diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000000..b2acb66914 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,461 @@ +# Getting Started with Consensus Spec Tests + +## Getting Started + +### Creating the environment + +Use an OS that has Python 3.8 or above. For example, Debian 11 (bullseye) + +1. Install the packages you need: + +```sh +sudo apt install -y make git wget python3-venv gcc python3-dev +``` + +2. Download the latest + [consensus specs](https://github.com/ethereum/consensus-specs) + +```sh +git clone https://github.com/ethereum/consensus-specs.git +cd consensus-specs +``` + +3. Create the specifications and tests: + +```sh +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: + +``` +$ 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 +[`~/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: + +``` +@with_all_phases +``` + +This [decorator](https://book.pythontips.com/en/latest/decorators.html) +specifies that this test is applicable to all the phases of consensus layer +development. These phases are similar to forks (Istanbul, Berlin, London, etc.) +in the execution blockchain. + +``` +@spec_state_test +``` + +This decorator specifies that this test is a state transition test, and that it +does not include a transition between different forks. + +``` +def test_empty_block_transition(spec, state): +``` + +This type of test receives two parameters: + +- `specs`: The protocol specifications +- `state`: The genesis state before the test + +```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. + +```python +pre_eth1_votes = len(state.eth1_data_votes) +pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) +``` + +Store some values to check later that certain updates happened. + +```python +yield "pre", state +``` + +In Python `yield` is used by +[generators](https://wiki.python.org/moin/Generators). However, for our purposes +we can treat it as a partial return statement that doesn't stop the function's +processing, only adds to a list of return values. Here we add two values, the +string `'pre'` and the initial state, to the list of return values. + +[You can read more about test generators and how they are used here](generators). + +```python +block = build_empty_block_for_next_slot(spec, state) +``` + +The state contains the last block, which is necessary for building up the next +block (every block needs to have the root of the previous one in a blockchain). + +```python +signed_block = state_transition_and_sign_block(spec, state, block) +``` + +Create a block signed by the appropriate proposer and advance the state. + +```python +yield "blocks", [signed_block] +yield "post", state +``` + +More `yield` statements. The output of a consensus test is: + +1. `'pre'` +2. The state before the test was run +3. `'blocks'` +4. A list of signed blocks +5. `'post'` +6. The state after the test + +```python +# One vote for the eth1 +assert len(state.eth1_data_votes) == pre_eth1_votes + 1 + +# 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 +``` + +Finally we assertions that test the transition was legitimate. In this case we +have three assertions: + +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. + +## 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 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 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. + +First, we need to find out where the function is located. Run: + +```sh +find . -name '*.py' -exec grep 'def state_transition_and_sign_block' {} \; -print +``` + +And you'll find that the function is defined in +`eth2spec/test/helpers/state.py`. Looking in that file, we see that the second +function is: + +```python +def next_slot(spec, state): + """ + Transition to the next slot. + """ + spec.process_slots(state, state.slot + 1) +``` + +This looks like exactly what we need. So we add this call before we create the +empty block: + +```python +yield "pre", state +next_slot(spec, state) +block = build_empty_block_for_next_slot(spec, state) +``` + +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). + +## Tests Designed to Fail + +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, +`~/consensus-specs/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py`). + +```python +@with_all_phases +@spec_state_test +def test_prev_slot_block_transition(spec, state): + spec.process_slots(state, state.slot + 1) + block = build_empty_block(spec, state, slot=state.slot) +``` + +Build an empty block for the current slot. + +```python +proposer_index = spec.get_beacon_proposer_index(state) +``` + +Get the identity of the current proposer, the one for *this* slot. + +```python +spec.process_slots(state, state.slot + 1) +``` + +Transition to the new slot, which naturally has a different proposer. + +```python +yield "pre", state +expect_assertion_error(lambda: transition_unsigned_block(spec, state, block)) +``` + +Specify that the function `transition_unsigned_block` will cause an assertion +error. You can see this function in +`~/consensus-specs/tests/core/pyspec/eth2spec/test/helpers/block.py`, and one of +the tests is that the block must be for this slot: + +> ```python +> assert state.slot == block.slot +> ``` + +Because we use +[lambda notation](https://www.w3schools.com/python/python_lambda.asp), the test +does not call `transition_unsigned_block` here. Instead, this is a function +parameter that can be called later. + +```python +block.state_root = state.hash_tree_root() +``` + +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). + +```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 for the current state. + +```python +yield "blocks", [signed_block] +yield "post", None # No post state, signifying it errors out +``` + +This is the way we specify that a test is designed to fail - failed tests have +no post state, because the processing mechanism errors out before creating it. + +## Attestation Tests + +The consensus layer doesn't provide any direct functionality to end users. It +does not execute EVM programs or store user data. It exists to provide a secure +source of information about the latest verified block hash of the execution +layer. + +For every slot a validator is randomly selected as the proposer. The proposer +proposes a block for the current head of the consensus layer chain (built on the +previous block). That block 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, 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): +Lets go over it line by line. + +```python +@with_all_phases +@spec_state_test +def test_success(spec, state): + attestation = get_valid_attestation(spec, state, signed=True) +``` + +[This function](https://github.com/ethereum/consensus-specs/blob/30fe7ba1107d976100eb0c3252ca7637b791e43a/tests/core/pyspec/eth2spec/test/helpers/attestations.py#L88-L120) +creates a valid attestation (which can then be modified to make it invalid if +needed). To see an attestation "from the inside" we need to follow it. + +```python +def get_valid_attestation( + spec, state, slot=None, index=None, filter_participant_set=None, signed=False +): ... +``` + +Only two parameters, `spec` and `state` are required. However, there are four +other parameters that can affect the attestation created by this function. + +```python +# If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. +# Thus strictly speaking invalid when no participant is added later. +if slot is None: + slot = state.slot +if index is None: + index = 0 +``` + +Default values. Normally we want to choose the current slot, and out of the +proposers and committees that it can have, we want the first one. + +```python +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) +to see the exact data in an attestation. + +```python +beacon_committee = spec.get_beacon_committee( + state, + attestation_data.slot, + attestation_data.index, +) +``` + +This is the committee that is supposed to approve or reject the proposed block. + +```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, + data=attestation_data, +) +# 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 +``` + +```python +next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) +``` + +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. + +```python +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) +processes the attestation and returns the result. + +### Adding an Attestation Test + +Attestations can't happen in the same block as the one about which they are +attesting, or in a block that is after the block is finalized. This is specified +as part of the specs, in the `process_attestation` function (which is created +from the spec by the `make pyspec` command you ran earlier). Here is the +relevant code fragment: + +```python +def process_attestation(state: BeaconState, attestation: Attestation) -> None: + data = attestation.data + assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state)) + assert data.target.epoch == compute_epoch_at_slot(data.slot) + assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH + ... +``` + +In the last line you can see two conditions being asserted: + +1. `data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot` which verifies + that the attestation doesn't 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 +[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 `test_after_epoch_slots` function. We need two changes: + +1. Call `transition_to_slot_via_block` with one less slot to advance +2. Don't tell `run_attestation_processing` to return an empty post state. + +The modified function is: + +```python +@with_all_phases +@spec_state_test +def test_almost_after_epoch_slots(spec, state): + attestation = get_valid_attestation(spec, state, signed=True) + + # increment to latest inclusion slot (not beyond it) + 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 +make test k=almost_after fork=altair +``` + +You should see it ran successfully (although you might get a warning, you can +ignore it) + +## How are These Tests Used? + +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 +[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. + +______________________________________________________________________ + +Original version by [Ori Pomerantz](mailto:qbzzt1@gmail.com) diff --git a/tests/core/pyspec/README.md b/tests/core/pyspec/README.md index baa1322771..7b4b434f30 100644 --- a/tests/core/pyspec/README.md +++ b/tests/core/pyspec/README.md @@ -1,98 +1,61 @@ # Executable Python Spec (PySpec) The executable Python spec is built from the consensus specifications, - complemented with the necessary helper functions for hashing, BLS, and more. - -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. +complemented with the necessary helper functions for hashing, BLS, and more. +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. ## Py-tests -These tests are not intended for client-consumption. -These tests are testing the spec itself, to verify consistency and provide feedback on modifications of the spec. -However, most of the tests can be run in generator-mode, to output test vectors for client-consumption. +These tests are not intended for client-consumption. These tests are testing the +spec itself, to verify consistency and provide feedback on modifications of the +spec. However, most of the tests can be run in generator-mode, to output test +vectors for client-consumption. ### How to run tests -#### Automated - -Run `make test` from the root of the specs repository (after running `make install_test` if have not before). +To run all tests: -Note that the `make` commands run through the build steps: it runs the `build` output, not the local package source files. - -#### Manual +```shell +make test +``` -See `Dev install` for test pre-requisites. +To run all tests under the minimal preset: -Tests are built for `pytest`. +```shell +make test preset=minimal +``` -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` +Or, to run a specific test function specify `k=`: -Full test usage, with explicit configuration for illustration of options usage: ```shell -(venv) python -m pytest --preset=minimal eth2spec +make test k=test_verify_kzg_proof ``` -Or, to run a specific test file, specify the full path: +Or, to run all tests under a single fork specify `fork=`: + ```shell -(venv) python -m pytest --preset=minimal ./eth2spec/test/phase0/block_processing/test_process_attestation.py +make test fork=phase0 ``` -Or, to run a specific test function (specify the `eth2spec` module, or the script path if the keyword is ambiguous): +Note: these options can be used together, like: + ```shell -(venv) python -m pytest --preset=minimal -k test_success_multi_proposer_index_iterations eth2spec +make test preset=minimal k=test_verify_kzg_proof fork=deneb ``` -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) - ### 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. - -### Advanced - -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. - +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. +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 3bec004b54..b566c6f1a6 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -1.1.0-beta.4 \ No newline at end of file +1.6.0-alpha.3 diff --git a/tests/core/pyspec/eth2spec/__init__.py b/tests/core/pyspec/eth2spec/__init__.py index 1ff5843838..c0f8c38f56 100644 --- a/tests/core/pyspec/eth2spec/__init__.py +++ b/tests/core/pyspec/eth2spec/__init__.py @@ -1,4 +1,5 @@ # See setup.py about usage of VERSION.txt import os -with open(os.path.join(os.path.dirname(__file__), 'VERSION.txt')) as f: + +with open(os.path.join(os.path.dirname(__file__), "VERSION.txt")) as f: __version__ = f.read().strip() diff --git a/tests/core/pyspec/eth2spec/config/README.md b/tests/core/pyspec/eth2spec/config/README.md index c03d890c20..8368569974 100644 --- a/tests/core/pyspec/eth2spec/config/README.md +++ b/tests/core/pyspec/eth2spec/config/README.md @@ -1,9 +1,11 @@ # Consensus specs config util -For run-time configuration, see [Configs documentation](../../../../../configs/README.md). +For run-time configuration, see +[Configs documentation](../../../../../configs/README.md). -For compile-time presets, see [Presets documentation](../../../../../presets/README.md) -and the `build-targets` flag for the `pyspec` distutils command. +For compile-time presets, see +[Presets documentation](../../../../../presets/README.md) and the +`build-targets` flag for the `pyspec` distutils command. ## Config usage: @@ -13,11 +15,14 @@ from eth2spec.phase0 import mainnet as spec from pathlib import Path # To load the default configurations -config_util.load_defaults(Path("consensus-specs/configs")) # change path to point to equivalent of specs `configs` dir. +config_util.load_defaults( + Path("consensus-specs/configs") +) # change path to point to equivalent of specs `configs` dir. # After loading the defaults, a config can be chosen: 'mainnet', 'minimal', or custom network config (by file path) -spec.config = spec.Configuration(**config_util.load_config_file(Path('mytestnet.yaml'))) +spec.config = spec.Configuration(**config_util.load_config_file(Path("mytestnet.yaml"))) ``` -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. +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. diff --git a/tests/core/pyspec/eth2spec/config/config_util.py b/tests/core/pyspec/eth2spec/config/config_util.py index 0d06428ea1..3834cea978 100644 --- a/tests/core/pyspec/eth2spec/config/config_util.py +++ b/tests/core/pyspec/eth2spec/config/config_util.py @@ -1,33 +1,35 @@ +from collections.abc import Iterable from pathlib import Path -from typing import Dict, Iterable, Union, BinaryIO, TextIO, Any +from typing import Any, BinaryIO, TextIO + from ruamel.yaml import YAML -def parse_config_vars(conf: Dict[str, Any]) -> Dict[str, Any]: +def parse_config_vars(conf: dict[str, Any]) -> dict[str, Any]: """ Parses a dict of basic str/int/list types into more detailed python types """ - out: Dict[str, Any] = dict() + out: dict[str, Any] = dict() for k, v in conf.items(): if isinstance(v, list): # Clean up integer values. YAML parser renders lists of ints as list of str out[k] = [int(item) if item.isdigit() else item for item in v] elif isinstance(v, str) and v.startswith("0x"): out[k] = bytes.fromhex(v[2:]) - elif k != 'PRESET_BASE': + elif k != "PRESET_BASE" and k != "CONFIG_NAME": out[k] = int(v) else: out[k] = v return out -def load_preset(preset_files: Iterable[Union[Path, BinaryIO, TextIO]]) -> Dict[str, Any]: +def load_preset(preset_files: Iterable[Path | BinaryIO | TextIO]) -> dict[str, Any]: """ - Loads the a directory of preset files, merges the result into one preset. + Loads a directory of preset files, merges the result into one preset. """ preset = {} for fork_file in preset_files: - yaml = YAML(typ='base') + yaml = YAML(typ="base") fork_preset: dict = yaml.load(fork_file) if fork_preset is None: # for empty YAML files continue @@ -39,25 +41,25 @@ def load_preset(preset_files: Iterable[Union[Path, BinaryIO, TextIO]]) -> Dict[s return parse_config_vars(preset) -def load_config_file(config_path: Union[Path, BinaryIO, TextIO]) -> Dict[str, Any]: +def load_config_file(config_path: Path | BinaryIO | TextIO) -> dict[str, Any]: """ Loads the given configuration file. """ - yaml = YAML(typ='base') + yaml = YAML(typ="base") config_data = yaml.load(config_path) return parse_config_vars(config_data) -mainnet_config_data: Dict[str, Any] -minimal_config_data: Dict[str, Any] +mainnet_config_data: dict[str, Any] +minimal_config_data: dict[str, Any] loaded_defaults = False def load_defaults(spec_configs_path: Path) -> None: global mainnet_config_data, minimal_config_data - mainnet_config_data = load_config_file(spec_configs_path / 'mainnet.yaml') - minimal_config_data = load_config_file(spec_configs_path / 'minimal.yaml') + mainnet_config_data = load_config_file(spec_configs_path / "mainnet.yaml") + minimal_config_data = load_config_file(spec_configs_path / "minimal.yaml") global loaded_defaults loaded_defaults = True diff --git a/tests/core/pyspec/eth2spec/debug/decode.py b/tests/core/pyspec/eth2spec/debug/decode.py index 30bfd487bd..c5db69316d 100644 --- a/tests/core/pyspec/eth2spec/debug/decode.py +++ b/tests/core/pyspec/eth2spec/debug/decode.py @@ -1,15 +1,23 @@ from typing import Any + from eth2spec.utils.ssz.ssz_impl import hash_tree_root from eth2spec.utils.ssz.ssz_typing import ( - uint, Container, List, boolean, - Vector, ByteVector, ByteList, Union, View + boolean, + ByteList, + ByteVector, + Container, + List, + uint, + Union, + Vector, + View, ) def decode(data: Any, typ): - if issubclass(typ, (uint, boolean)): + if issubclass(typ, uint | boolean): return typ(data) - elif issubclass(typ, (List, Vector)): + elif issubclass(typ, List | Vector): return typ(decode(element, typ.element_cls()) for element in data) elif issubclass(typ, ByteVector): return typ(bytes.fromhex(data[2:])) @@ -20,12 +28,13 @@ def decode(data: Any, typ): for field_name, field_type in typ.fields().items(): temp[field_name] = decode(data[field_name], field_type) if field_name + "_hash_tree_root" in data: - assert (data[field_name + "_hash_tree_root"][2:] == - hash_tree_root(temp[field_name]).hex()) + assert ( + data[field_name + "_hash_tree_root"][2:] + == hash_tree_root(temp[field_name]).hex() + ) ret = typ(**temp) if "hash_tree_root" in data: - assert (data["hash_tree_root"][2:] == - hash_tree_root(ret).hex()) + assert data["hash_tree_root"][2:] == hash_tree_root(ret).hex() return ret elif issubclass(typ, Union): selector = int(data["selector"]) diff --git a/tests/core/pyspec/eth2spec/debug/encode.py b/tests/core/pyspec/eth2spec/debug/encode.py index d93f7cf5ef..d121739465 100644 --- a/tests/core/pyspec/eth2spec/debug/encode.py +++ b/tests/core/pyspec/eth2spec/debug/encode.py @@ -1,7 +1,13 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize from eth2spec.utils.ssz.ssz_typing import ( - uint, boolean, - Bitlist, Bitvector, Container, Vector, List, Union + Bitlist, + Bitvector, + boolean, + Container, + List, + uint, + Union, + Vector, ) @@ -13,29 +19,29 @@ def encode(value, include_hash_tree_roots=False): return int(value) elif isinstance(value, boolean): return value == 1 - elif isinstance(value, (Bitlist, Bitvector)): - return '0x' + serialize(value).hex() + elif isinstance(value, Bitlist | Bitvector): + return "0x" + serialize(value).hex() elif isinstance(value, list): # normal python lists return [encode(element, include_hash_tree_roots) for element in value] - elif isinstance(value, (List, Vector)): + elif isinstance(value, List | Vector): return [encode(element, include_hash_tree_roots) for element in value] elif isinstance(value, bytes): # bytes, ByteList, ByteVector - return '0x' + value.hex() + return "0x" + value.hex() elif isinstance(value, Container): ret = {} for field_name in value.fields().keys(): field_value = getattr(value, field_name) ret[field_name] = encode(field_value, include_hash_tree_roots) if include_hash_tree_roots: - ret[field_name + "_hash_tree_root"] = '0x' + hash_tree_root(field_value).hex() + ret[field_name + "_hash_tree_root"] = "0x" + hash_tree_root(field_value).hex() if include_hash_tree_roots: - ret["hash_tree_root"] = '0x' + hash_tree_root(value).hex() + ret["hash_tree_root"] = "0x" + hash_tree_root(value).hex() return ret elif isinstance(value, Union): inner_value = value.value() return { - 'selector': int(value.selector()), - 'value': None if inner_value is None else encode(inner_value, include_hash_tree_roots) + "selector": int(value.selector()), + "value": None if inner_value is None else encode(inner_value, include_hash_tree_roots), } else: raise Exception(f"Type not recognized: value={value}, typ={type(value)}") diff --git a/tests/core/pyspec/eth2spec/debug/random_value.py b/tests/core/pyspec/eth2spec/debug/random_value.py index ff80ee0f4e..cf4ee016d5 100644 --- a/tests/core/pyspec/eth2spec/debug/random_value.py +++ b/tests/core/pyspec/eth2spec/debug/random_value.py @@ -1,11 +1,19 @@ -from random import Random from enum import Enum - -from typing import Type +from random import Random from eth2spec.utils.ssz.ssz_typing import ( - View, BasicView, uint, Container, List, boolean, - Vector, ByteVector, ByteList, Bitlist, Bitvector, Union + BasicView, + Bitlist, + Bitvector, + boolean, + ByteList, + ByteVector, + Container, + List, + uint, + Union, + Vector, + View, ) # in bytes @@ -35,12 +43,14 @@ def is_changing(self): return self.value in [0, 4, 5] -def get_random_ssz_object(rng: Random, - typ: Type[View], - max_bytes_length: int, - max_list_length: int, - mode: RandomizationMode, - chaos: bool) -> View: +def get_random_ssz_object( + rng: Random, + typ: type[View], + max_bytes_length: int, + max_list_length: int, + mode: RandomizationMode, + chaos: bool, +) -> View: """ Create an object for a given type, filled with random data. :param rng: The random number generator to use. @@ -56,27 +66,29 @@ def get_random_ssz_object(rng: Random, if issubclass(typ, ByteList): # ByteList array if mode == RandomizationMode.mode_nil_count: - return typ(b'') + return typ(b"") elif mode == RandomizationMode.mode_max_count: return typ(get_random_bytes_list(rng, min(max_bytes_length, typ.limit()))) elif mode == RandomizationMode.mode_one_count: return typ(get_random_bytes_list(rng, min(1, typ.limit()))) elif mode == RandomizationMode.mode_zero: - return typ(b'\x00' * min(1, typ.limit())) + return typ(b"\x00" * min(1, typ.limit())) elif mode == RandomizationMode.mode_max: - return typ(b'\xff' * min(1, typ.limit())) + return typ(b"\xff" * min(1, typ.limit())) else: - return typ(get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.limit())))) + return typ( + get_random_bytes_list(rng, rng.randint(0, min(max_bytes_length, typ.limit()))) + ) if issubclass(typ, ByteVector): # Random byte vectors can be bigger than max bytes size, e.g. custody chunk data. # No max-bytes-length limitation here. if mode == RandomizationMode.mode_zero: - return typ(b'\x00' * typ.type_byte_length()) + return typ(b"\x00" * typ.type_byte_length()) elif mode == RandomizationMode.mode_max: - return typ(b'\xff' * typ.type_byte_length()) + return typ(b"\xff" * typ.type_byte_length()) else: return typ(get_random_bytes_list(rng, typ.type_byte_length())) - elif issubclass(typ, (boolean, uint)): + elif issubclass(typ, boolean | uint): # Basic types if mode == RandomizationMode.mode_zero: return get_min_basic_value(typ) @@ -84,7 +96,7 @@ def get_random_ssz_object(rng: Random, return get_max_basic_value(typ) else: return get_random_basic_value(rng, typ) - elif issubclass(typ, (Vector, Bitvector)): + elif issubclass(typ, Vector | Bitvector): elem_type = typ.element_cls() if issubclass(typ, Vector) else boolean return typ( get_random_ssz_object(rng, elem_type, max_bytes_length, max_list_length, mode, chaos) @@ -99,8 +111,8 @@ def get_random_ssz_object(rng: Random, elif mode == RandomizationMode.mode_nil_count: length = 0 - if typ.limit() < length: # SSZ imposes a hard limit on lists, we can't put in more than that - length = typ.limit() + # SSZ imposes a hard limit on lists, we can't put in more than that + length = min(length, typ.limit()) elem_type = typ.element_cls() if issubclass(typ, List) else boolean return typ( @@ -110,11 +122,14 @@ def get_random_ssz_object(rng: Random, elif issubclass(typ, Container): fields = typ.fields() # Container - return typ(**{ - field_name: - get_random_ssz_object(rng, field_type, max_bytes_length, max_list_length, mode, chaos) - for field_name, field_type in fields.items() - }) + return typ( + **{ + field_name: get_random_ssz_object( + rng, field_type, max_bytes_length, max_list_length, mode, chaos + ) + for field_name, field_type in fields.items() + } + ) elif issubclass(typ, Union): options = typ.options() selector: int @@ -129,7 +144,9 @@ def get_random_ssz_object(rng: Random, if elem_type is None: elem = None else: - elem = get_random_ssz_object(rng, elem_type, max_bytes_length, max_list_length, mode, chaos) + elem = get_random_ssz_object( + rng, elem_type, max_bytes_length, max_list_length, mode, chaos + ) return typ(selector=selector, value=elem) else: raise Exception(f"Type not recognized: typ={typ}") diff --git a/tests/core/pyspec/eth2spec/gen_helpers/README.md b/tests/core/pyspec/eth2spec/gen_helpers/README.md index bf791ccfea..93da36d125 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/README.md +++ b/tests/core/pyspec/eth2spec/gen_helpers/README.md @@ -4,7 +4,8 @@ A util to quickly write new test suite generators with. -See [Generators documentation](../../../../generators/README.md) for integration details. +See [Generators documentation](../../../../generators/README.md) for integration +details. Options: @@ -17,20 +18,22 @@ 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. ``` ## `gen_from_tests` -This is an util to derive tests from a tests source file. +This is a util to derive tests from a tests source file. -This requires the tests to yield test-case-part outputs. These outputs are then written to the test case directory. -Yielding data is illegal in normal pytests, so it is only done when in "generator mode". -This functionality can be attached to any function by using the `vector_test()` decorator found in `ethspec/tests/utils.py`. +This requires the tests to yield test-case-part outputs. These outputs are then +written to the test case directory. Yielding data is illegal in normal pytests, +so it is only done when in "generator mode". This functionality can be attached +to any function by using the `vector_test()` decorator found in +`ethspec/tests/utils.py`. ## Test-case parts @@ -38,17 +41,25 @@ Test cases consist of parts, which are yielded to the base generator one by one. The yielding pattern is: -2 value style: `yield `. The kind of output will be inferred from the value by the `vector_test()` decorator. +2 value style: `yield `. The kind of output will be inferred +from the value by the `vector_test()` decorator. 3 value style: `yield `. 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 - 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, - as the output will already be encoded to yaml or ssz bytes. This avoids the need to deep-copy the whole object. +- `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 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, 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/gen_helpers/gen_base/args.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/args.py new file mode 100644 index 0000000000..d97aa8bb3d --- /dev/null +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/args.py @@ -0,0 +1,77 @@ +import argparse +import os +import pathlib + + +def create_arg_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + prog="generator", + description="Generate YAML test suite files.", + ) + parser.add_argument( + "-o", + "--output-dir", + dest="output_dir", + required=True, + type=pathlib.Path, + help="Directory into which the generated YAML files will be dumped.", + ) + parser.add_argument( + "--runners", + dest="runners", + nargs="*", + type=str, + default=[], + required=False, + help="Specify runners to run with. Allows all if no runner names are specified.", + ) + parser.add_argument( + "--presets", + dest="presets", + nargs="*", + type=str, + default=[], + required=False, + help="Specify presets to run with. Allows all if no preset names are specified.", + ) + parser.add_argument( + "--forks", + dest="forks", + nargs="*", + type=str, + default=[], + required=False, + help="Specify forks to run with. Allows all if no fork names are specified.", + ) + parser.add_argument( + "--cases", + dest="cases", + nargs="*", + type=str, + default=[], + required=False, + help="Specify test cases to run with. Allows all if no test case names are specified.", + ) + parser.add_argument( + "--modcheck", + action="store_true", + default=False, + help="Check generator modules, do not run any tests.", + ) + parser.add_argument( + "--verbose", + action="store_true", + default=False, + help="Print more information to the console.", + ) + parser.add_argument( + "--threads", + type=int, + default=os.cpu_count(), + help="Generate tests with N threads. Defaults to core count.", + ) + return parser + + +def parse_arguments(): + return create_arg_parser().parse_args() diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/dumper.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/dumper.py new file mode 100644 index 0000000000..4f7ab5d729 --- /dev/null +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/dumper.py @@ -0,0 +1,77 @@ +from eth_utils import encode_hex +from ruamel.yaml import YAML +from snappy import compress + +from eth2spec.test import context + +from .gen_typing import TestCase + + +def get_default_yaml(): + yaml = YAML(pure=True) + yaml.default_flow_style = None + + def _represent_none(self, _): + return self.represent_scalar("tag:yaml.org,2002:null", "null") + + def _represent_str(self, data): + if data.startswith("0x"): + # Without this, a zero-byte hex string is represented without quotes. + return self.represent_scalar("tag:yaml.org,2002:str", data, style="'") + return self.represent_str(data) + + yaml.representer.add_representer(type(None), _represent_none) + yaml.representer.add_representer(str, _represent_str) + + return yaml + + +def get_cfg_yaml(): + # Spec config is using a YAML subset + cfg_yaml = YAML(pure=True) + cfg_yaml.default_flow_style = False # Emit separate line for each key + cfg_yaml.width = 1024 # Do not wrap long lines + + def cfg_represent_bytes(self, data): + return self.represent_int(encode_hex(data)) + + cfg_yaml.representer.add_representer(bytes, cfg_represent_bytes) + + def cfg_represent_quoted_str(self, data): + return self.represent_scalar("tag:yaml.org,2002:str", data, style="'") + + cfg_yaml.representer.add_representer(context.quoted_str, cfg_represent_quoted_str) + return cfg_yaml + + +class Dumper: + """Helper for dumping test case outputs (cfg, data, meta, ssz).""" + + def __init__(self, default_yaml: YAML = None, cfg_yaml: YAML = None): + self.default_yaml = default_yaml or get_default_yaml() + self.cfg_yaml = cfg_yaml or get_cfg_yaml() + + def dump_meta(self, test_case: TestCase, meta: dict) -> None: + if not meta: + return + self._dump_yaml(test_case, "meta", meta, self.default_yaml) + + def dump_cfg(self, test_case: TestCase, name: str, data: any) -> None: + self._dump_yaml(test_case, name, data, self.cfg_yaml) + + def dump_data(self, test_case: TestCase, name: str, data: any) -> None: + self._dump_yaml(test_case, name, data, self.default_yaml) + + def dump_ssz(self, test_case: TestCase, name: str, data: bytes) -> None: + """Compress and write SSZ data for test case.""" + path = test_case.dir / f"{name}.ssz_snappy" + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("wb") as f: + f.write(compress(data)) + + def _dump_yaml(self, test_case: TestCase, name: str, data: any, yaml_encoder: YAML) -> None: + """Helper to write YAML files for test case.""" + path = test_case.dir / f"{name}.yaml" + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("w") as f: + yaml_encoder.dump(data, f) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py index 6850af188d..f55fb94a22 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_runner.py @@ -1,235 +1,199 @@ -import os -import time +import multiprocessing import shutil -import argparse -from pathlib import Path -import sys -from typing import Iterable, AnyStr, Any, Callable -import traceback - -from ruamel.yaml import ( - YAML, -) +import threading +import time +import uuid +from collections.abc import Iterable +from typing import Any -from snappy import compress +from pathos.multiprocessing import ProcessingPool as Pool +from rich import box +from rich.console import Console +from rich.live import Live +from rich.table import Table +from rich.text import Text from eth2spec.test import context from eth2spec.test.exceptions import SkippedTest -from .gen_typing import TestProvider - +from .args import parse_arguments +from .dumper import Dumper +from .gen_typing import TestCase +from .utils import install_sigint_handler, time_since # Flag that the runner does NOT run test via pytest context.is_pytest = False -TIME_THRESHOLD_TO_PRINT = 1.0 # seconds - - -def validate_output_dir(path_str): - path = Path(path_str) - - if not path.exists(): - raise argparse.ArgumentTypeError("Output directory must exist") - - if not path.is_dir(): - raise argparse.ArgumentTypeError("Output path must lead to a directory") - - return path - - -def run_generator(generator_name, test_providers: Iterable[TestProvider]): - """ - Implementation for a general test generator. - :param generator_name: The name of the generator. (lowercase snake_case) - :param test_providers: A list of test provider, - each of these returns a callable that returns an iterable of test cases. - The call to get the iterable may set global configuration, - and the iterable should not be resumed after a pause with a change of that configuration. - :return: - """ - - parser = argparse.ArgumentParser( - prog="gen-" + generator_name, - description=f"Generate YAML test suite files for {generator_name}", - ) - parser.add_argument( - "-o", - "--output-dir", - dest="output_dir", - required=True, - type=validate_output_dir, - help="directory into which the generated YAML files will be dumped" - ) - parser.add_argument( - "-f", - "--force", - action="store_true", - default=False, - help="if set re-generate and overwrite test files if they already exist", - ) - parser.add_argument( - "-l", - "--preset-list", - dest="preset_list", - nargs='*', - type=str, - required=False, - help="specify presets to run with. Allows all if no preset names are specified.", - ) - parser.add_argument( - "-c", - "--collect-only", - action="store_true", - default=False, - help="if set only print tests to generate, do not actually run the test and dump the target data", - ) - - args = parser.parse_args() - output_dir = args.output_dir - if not args.force: - file_mode = "x" - else: - file_mode = "w" - - yaml = YAML(pure=True) - yaml.default_flow_style = None - - log_file = Path(output_dir) / 'testgen_error_log.txt' - - print(f"Generating tests into {output_dir}") - print(f'Error log file: {log_file}') - - presets = args.preset_list - if presets is None: - presets = [] - - if len(presets) != 0: - print(f"Filtering test-generator runs to only include presets: {', '.join(presets)}") - - collect_only = args.collect_only - collected_test_count = 0 - generated_test_count = 0 - skipped_test_count = 0 - provider_start = time.time() - for tprov in test_providers: - if not collect_only: - # runs anything that we don't want to repeat for every test case. - tprov.prepare() - - for test_case in tprov.make_cases(): - case_dir = ( - Path(output_dir) / Path(test_case.preset_name) / Path(test_case.fork_name) - / Path(test_case.runner_name) / Path(test_case.handler_name) - / Path(test_case.suite_name) / Path(test_case.case_name) - ) - incomplete_tag_file = case_dir / "INCOMPLETE" - - collected_test_count += 1 - if collect_only: - print(f"Collected test at: {case_dir}") - continue - - if case_dir.exists(): - if not args.force and not incomplete_tag_file.exists(): - skipped_test_count += 1 - print(f'Skipping already existing test: {case_dir}') - continue - else: - print(f'Warning, output directory {case_dir} already exist,' - f' old files will be deleted and it will generate test vector files with the latest version') - # Clear the existing case_dir folder - shutil.rmtree(case_dir) - - print(f'Generating test: {case_dir}') - test_start = time.time() - - written_part = False - - # Add `INCOMPLETE` tag file to indicate that the test generation has not completed. - case_dir.mkdir(parents=True, exist_ok=True) - with incomplete_tag_file.open("w") as f: - f.write("\n") - - try: - def output_part(out_kind: str, name: str, fn: Callable[[Path, ], None]): - # make sure the test case directory is created before any test part is written. - case_dir.mkdir(parents=True, exist_ok=True) - try: - fn(case_dir) - except IOError as e: - sys.exit(f'Error when dumping test "{case_dir}", part "{name}", kind "{out_kind}": {e}') - - meta = dict() - - try: - for (name, out_kind, data) in test_case.case_fn(): - written_part = True - if out_kind == "meta": - meta[name] = data - if out_kind == "data": - output_part("data", name, dump_yaml_fn(data, name, file_mode, yaml)) - if out_kind == "ssz": - output_part("ssz", name, dump_ssz_fn(data, name, file_mode)) - except SkippedTest as e: - print(e) - skipped_test_count += 1 - shutil.rmtree(case_dir) - continue - - # Once all meta data is collected (if any), write it to a meta data file. - if len(meta) != 0: - written_part = True - output_part("data", "meta", dump_yaml_fn(meta, "meta", file_mode, yaml)) - - if not written_part: - print(f"test case {case_dir} did not produce any test case parts") - except Exception as e: - print(f"ERROR: failed to generate vector(s) for test {case_dir}: {e}") - traceback.print_exc() - # Write to log file - with log_file.open("a+") as f: - f.write(f"ERROR: failed to generate vector(s) for test {case_dir}: {e}") - traceback.print_exc(file=f) - f.write('\n') +def get_shared_prefix(test_cases, min_segments=3): + assert test_cases, "no test cases provided" + + fields = [ + "preset_name", + "fork_name", + "runner_name", + "handler_name", + ] + + prefix = [] + for i, field in enumerate(fields): + values = {getattr(tc, field) for tc in test_cases} + if len(values) == 1: + prefix.append(values.pop()) + elif i < min_segments: + prefix.append("*") + else: + break + + return "::".join(prefix) + + +def execute_test(test_case: TestCase, dumper: Dumper): + """Execute a test and write the outputs to storage.""" + meta: dict[str, Any] = {} + outputs: list[tuple[str, str, Any]] = [] + + try: + for name, kind, data in test_case.case_fn(): + if kind == "meta": + meta[name] = data else: - # If no written_part, the only file was incomplete_tag_file. Clear the existing case_dir folder. - if not written_part: - shutil.rmtree(case_dir) - else: - generated_test_count += 1 - # Only remove `INCOMPLETE` tag file - os.remove(incomplete_tag_file) - test_end = time.time() - span = round(test_end - test_start, 2) - if span > TIME_THRESHOLD_TO_PRINT: - print(f' - generated in {span} seconds') - - provider_end = time.time() - span = round(provider_end - provider_start, 2) - - if collect_only: - print(f"Collected {collected_test_count} tests in total") - else: - summary_message = f"completed generation of {generator_name} with {generated_test_count} tests" - summary_message += f" ({skipped_test_count} skipped tests)" - if span > TIME_THRESHOLD_TO_PRINT: - summary_message += f" in {span} seconds" - print(summary_message) - - -def dump_yaml_fn(data: Any, name: str, file_mode: str, yaml_encoder: YAML): - def dump(case_path: Path): - out_path = case_path / Path(name + '.yaml') - with out_path.open(file_mode) as f: - yaml_encoder.dump(data, f) - return dump - - -def dump_ssz_fn(data: AnyStr, name: str, file_mode: str): - def dump(case_path: Path): - out_path = case_path / Path(name + '.ssz_snappy') - compressed = compress(data) - with out_path.open(file_mode + 'b') as f: # write in raw binary mode - f.write(compressed) - return dump + method = getattr(dumper, f"dump_{kind}", None) + if method is None: + raise ValueError(f"Unknown kind {kind!r}") + outputs.append((name, kind, data)) + except SkippedTest: + # Bail without writing any files + raise + + for name, kind, data in outputs: + method = getattr(dumper, f"dump_{kind}") + method(test_case, name, data) + + if meta: + dumper.dump_meta(test_case, meta) + + +def run_generator(input_test_cases: Iterable[TestCase], args=None): + start_time = time.time() + if args is None: + args = parse_arguments() + + # Bail here if we are checking modules. + if args.modcheck: + return + + def debug_print(msg): + """Only print if verbose is enabled.""" + if args.verbose: + print(msg) + + console = Console() + dumper = Dumper() + + # Gracefully handle Ctrl+C + install_sigint_handler(console) + + test_cases = [] + for test_case in input_test_cases: + # Check if the test case should be filtered out + if len(args.runners) != 0 and test_case.runner_name not in args.runners: + debug_print(f"Filtered: {test_case.get_identifier()}") + continue + if len(args.presets) != 0 and test_case.preset_name not in args.presets: + debug_print(f"Filtered: {test_case.get_identifier()}") + continue + if len(args.forks) != 0 and test_case.fork_name not in args.forks: + debug_print(f"Filtered: {test_case.get_identifier()}") + continue + if len(args.cases) != 0 and not any(s in test_case.case_name for s in args.cases): + debug_print(f"Filtered: {test_case.get_identifier()}") + continue + + # Set the output dir and add this to out list + test_case.set_output_dir(args.output_dir) + if test_case.dir.exists(): + shutil.rmtree(test_case.dir) + test_cases.append(test_case) + + if len(test_cases) == 0: + return + + debug_print(f"Generating tests into {args.output_dir}") + tests_prefix = get_shared_prefix(test_cases) + + def worker_function(data): + """Execute a test case and update active tests.""" + test_case, active_tests = data + key = (uuid.uuid4(), test_case.get_identifier()) + active_tests[key] = time.time() + try: + execute_test(test_case, dumper) + debug_print(f"Generated: {test_case.get_identifier()}") + return "generated" + except SkippedTest: + debug_print(f"Skipped: {test_case.get_identifier()}") + return "skipped" + finally: + del active_tests[key] + + def display_active_tests(active_tests, total_tasks, completed, skipped, width): + """Display a table of active tests.""" + with Live(console=console) as live: + while True: + remaining = total_tasks - completed.value + if remaining == 0: + # Show a final status when the queue is empty + # This is better than showing an empty table + text = Text.from_markup(f"Completed {tests_prefix} in {time_since(start_time)}") + live.update(text) + break + + info = ", ".join( + [ + f"gen={tests_prefix}", + f"threads={args.threads}", + f"total={total_tasks}", + f"skipped={skipped.value}", + f"remaining={remaining}", + f"time={time_since(start_time)}", + ] + ) + column_header = f"Test ({info})" + width = max(width, len(column_header)) + + table = Table(box=box.ROUNDED) + table.add_column(column_header, style="cyan", no_wrap=True, width=width) + table.add_column("Elapsed Time", justify="right", style="magenta") + for k, start in sorted(active_tests.items(), key=lambda x: x[1]): + table.add_row(k[1], f"{time_since(start)}") + live.update(table) + time.sleep(0.25) + + # Generate all of the test cases + with multiprocessing.Manager() as manager: + active_tests = manager.dict() + completed = manager.Value("i", 0) + skipped = manager.Value("i", 0) + width = max([len(t.get_identifier()) for t in test_cases]) + + if not args.verbose: + display_thread = threading.Thread( + target=display_active_tests, + args=(active_tests, len(test_cases), completed, skipped, width), + daemon=True, + ) + display_thread.start() + + # Map each test case to a thread worker + inputs = [(t, active_tests) for t in test_cases] + for result in Pool(processes=args.threads).uimap(worker_function, inputs): + if result == "skipped": + skipped.value += 1 + completed.value += 1 + + if not args.verbose: + display_thread.join() + + elapsed = round(time.time() - start_time, 2) + debug_print(f"Completed generation of {tests_prefix} in {elapsed} seconds") diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py index 669238d1c3..9055ef3c4f 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/gen_typing.py @@ -1,23 +1,23 @@ +from collections.abc import Callable, Iterable +from dataclasses import dataclass +from pathlib import Path from typing import ( Any, - Callable, - Iterable, NewType, - Tuple, ) -from dataclasses import dataclass # Elements: name, out_kind, data # # out_kind is the type of data: +# - "meta" for generic data to collect into a meta data dict +# - "cfg" for a spec config dictionary # - "data" for generic # - "ssz" for SSZ encoded bytes -# - "meta" for generic data to collect into a meta data dict. -TestCasePart = NewType("TestCasePart", Tuple[str, str, Any]) +TestCasePart = NewType("TestCasePart", tuple[str, str, Any]) @dataclass -class TestCase(object): +class TestCase: fork_name: str preset_name: str runner_name: str @@ -25,11 +25,29 @@ class TestCase(object): suite_name: str case_name: str case_fn: Callable[[], Iterable[TestCasePart]] + dir: Path | None = None + def get_identifier(self): + """Return the human readable identifier.""" + return "::".join( + [ + self.preset_name, + self.fork_name, + self.runner_name, + self.handler_name, + self.suite_name, + self.case_name, + ] + ) -@dataclass -class TestProvider(object): - # Prepares the context for the provider as a whole, as opposed to per-test-case changes. - prepare: Callable[[], None] - # Retrieves an iterable of cases, called after prepare() - make_cases: Callable[[], Iterable[TestCase]] + def set_output_dir(self, output_dir: str) -> None: + """Compute and store the output directory on the instance.""" + self.dir = ( + Path(output_dir) + / self.preset_name + / self.fork_name + / self.runner_name + / self.handler_name + / self.suite_name + / self.case_name + ) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_base/utils.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/utils.py new file mode 100644 index 0000000000..be56d5eb6d --- /dev/null +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_base/utils.py @@ -0,0 +1,35 @@ +import functools +import os +import signal +import time + +from rich.console import Console + + +def install_sigint_handler(console: Console) -> None: + """On Ctrl-C, show the cursor and exit immediately.""" + + def _handle_sigint(signum, frame): + console.show_cursor() + os._exit(0) + + signal.signal(signal.SIGINT, _handle_sigint) + + +@functools.cache +def format_seconds(seconds: int) -> str: + """Convert seconds to a more readable time.""" + h, rem = divmod(seconds, 3600) + m, s = divmod(rem, 60) + parts = [] + if h: + parts.append(f"{h}h") + if m: + parts.append(f"{m}m") + parts.append(f"{s}s") + return " ".join(parts) + + +def time_since(start_time: int) -> str: + """Get the duration since some start time.""" + return format_seconds(int(time.time() - start_time)) diff --git a/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py b/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py index 88bc6d601a..f47030235b 100644 --- a/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py +++ b/tests/core/pyspec/eth2spec/gen_helpers/gen_from_tests/gen.py @@ -1,19 +1,29 @@ +from collections.abc import Iterable from importlib import import_module from inspect import getmembers, isfunction -from typing import Any, Callable, Dict, Iterable, Optional, List, Union +from pkgutil import walk_packages +from typing import Any -from eth2spec.utils import bls +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase from eth2spec.test.helpers.constants import ALL_PRESETS, TESTGEN_FORKS -from eth2spec.test.helpers.typing import SpecForkName, PresetBaseName +from eth2spec.test.helpers.typing import PresetBaseName, SpecForkName -from eth2spec.gen_helpers.gen_base import gen_runner -from eth2spec.gen_helpers.gen_base.gen_typing import TestCase, TestProvider +def generate_case_fn(tfn, generator_mode, phase, preset, bls_active): + return lambda: tfn( + generator_mode=generator_mode, phase=phase, preset=preset, bls_active=bls_active + ) -def generate_from_tests(runner_name: str, handler_name: str, src: Any, - fork_name: SpecForkName, preset_name: PresetBaseName, - bls_active: bool = True, - phase: Optional[str]=None) -> Iterable[TestCase]: + +def generate_from_tests( + runner_name: str, + handler_name: str, + src: Any, + fork_name: SpecForkName, + preset_name: PresetBaseName, + bls_active: bool = True, + phase: str | None = None, +) -> Iterable[TestCase]: """ Generate a list of test cases by running tests from the given src in generator-mode. :param runner_name: to categorize the test in general as. @@ -27,21 +37,17 @@ def generate_from_tests(runner_name: str, handler_name: str, src: Any, Set to the pre-fork (w.r.t. fork_name) in multi-fork tests. :return: an iterable of test cases. """ - fn_names = [ - name for (name, _) in getmembers(src, isfunction) - if name.startswith('test_') - ] + fn_names = [name for (name, _) in getmembers(src, isfunction) if name.startswith("test_")] if phase is None: phase = fork_name - print("generating test vectors from tests source: %s" % src.__name__) for name in fn_names: tfn = getattr(src, name) # strip off the `test_` case_name = name - if case_name.startswith('test_'): + if case_name.startswith("test_"): case_name = case_name[5:] yield TestCase( @@ -49,63 +55,54 @@ def generate_from_tests(runner_name: str, handler_name: str, src: Any, preset_name=preset_name, runner_name=runner_name, handler_name=handler_name, - suite_name='pyspec_tests', + suite_name=getattr(tfn, "suite_name", "pyspec_tests"), case_name=case_name, # TODO: with_all_phases and other per-phase tooling, should be replaced with per-fork equivalent. - case_fn=lambda: tfn(generator_mode=True, phase=phase, preset=preset_name, bls_active=bls_active) - ) - - -def get_provider(create_provider_fn: Callable[[SpecForkName, PresetBaseName, str, str], TestProvider], - fork_name: SpecForkName, - preset_name: PresetBaseName, - all_mods: Dict[str, Dict[str, Union[List[str], str]]]) -> Iterable[TestProvider]: - for key, mod_name in all_mods[fork_name].items(): - if not isinstance(mod_name, List): - mod_name = [mod_name] - yield create_provider_fn( - fork_name=fork_name, - preset_name=preset_name, - handler_name=key, - tests_src_mod_name=mod_name, + case_fn=generate_case_fn( + tfn, generator_mode=True, phase=phase, preset=preset_name, bls_active=bls_active + ), ) -def get_create_provider_fn(runner_name: str) -> Callable[[SpecForkName, str, str, PresetBaseName], TestProvider]: - def prepare_fn() -> None: - bls.use_milagro() - return - - def create_provider(fork_name: SpecForkName, preset_name: PresetBaseName, - handler_name: str, tests_src_mod_name: List[str]) -> TestProvider: - def cases_fn() -> Iterable[TestCase]: - for mod_name in tests_src_mod_name: - tests_src = import_module(mod_name) - yield from generate_from_tests( - runner_name=runner_name, - handler_name=handler_name, - src=tests_src, - fork_name=fork_name, - preset_name=preset_name, - ) - - return TestProvider(prepare=prepare_fn, make_cases=cases_fn) - return create_provider - - -def run_state_test_generators(runner_name: str, - all_mods: Dict[str, Dict[str, str]], - presets: Iterable[PresetBaseName] = ALL_PRESETS, - forks: Iterable[SpecForkName] = TESTGEN_FORKS) -> None: +def get_expected_modules(package, absolute=False): """ - Generate all available state tests of `TESTGEN_FORKS` forks of `ALL_PRESETS` presets of the given runner. + Return all modules (which are not packages) inside the given package. """ - for preset_name in presets: - for fork_name in forks: - if fork_name in all_mods: - gen_runner.run_generator(runner_name, get_provider( - create_provider_fn=get_create_provider_fn(runner_name), - fork_name=fork_name, - preset_name=preset_name, - all_mods=all_mods, - )) + modules = [] + eth2spec = import_module("eth2spec") + prefix = eth2spec.__name__ + "." + for _, modname, ispkg in walk_packages(eth2spec.__path__, prefix): + s = package if absolute else f".{package}." + # Skip modules in the unittests package. + # These are not associated with generators. + if ".unittests." in modname: + continue + if s in modname and not ispkg: + modules.append(modname) + return modules + + +def default_handler_name_fn(mod): + return mod.split(".")[-1].replace("test_", "") + + +def get_test_cases_for( + runner_name: str, + pkg: str = None, + handler_name_fn=default_handler_name_fn, +) -> Iterable[TestCase]: + test_cases = [] + for preset in ALL_PRESETS: + for fork in TESTGEN_FORKS: + for mod in get_expected_modules(pkg or runner_name): + tests_src = import_module(mod) + test_cases.extend( + generate_from_tests( + runner_name=runner_name, + handler_name=handler_name_fn(mod), + src=tests_src, + fork_name=fork, + preset_name=preset, + ) + ) + return test_cases diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/__init__.py b/tests/core/pyspec/eth2spec/py.typed similarity index 100% rename from tests/core/pyspec/eth2spec/test/custody_game/block_processing/__init__.py rename to tests/core/pyspec/eth2spec/py.typed diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py index 8a2deabb11..3ae4061a36 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate.py @@ -1,37 +1,44 @@ import random + +from eth2spec.test.context import ( + always_bls, + default_activation_threshold, + default_balances_electra, + single_phase, + spec_state_test, + spec_test, + with_altair_and_later, + with_custom_state, + with_presets, +) from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) +from eth2spec.test.helpers.constants import ( + MAINNET, + MINIMAL, +) from eth2spec.test.helpers.state import ( + next_epoch_via_block, state_transition_and_sign_block, transition_to, - next_epoch_via_block, -) -from eth2spec.test.helpers.constants import ( - MAINNET, MINIMAL, ) from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, compute_committee_indices, - run_sync_committee_processing, run_successful_sync_committee_test, + run_sync_committee_processing, ) from eth2spec.test.helpers.voluntary_exits import ( get_unslashed_exited_validators, ) -from eth2spec.test.context import ( - with_altair_and_later, - with_presets, - spec_state_test, - always_bls, -) @with_altair_and_later @spec_state_test @always_bls def test_invalid_signature_bad_domain(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -43,7 +50,7 @@ def test_invalid_signature_bad_domain(spec, state): committee_indices, # full committee signs block_root=block.parent_root, domain_type=spec.DOMAIN_BEACON_ATTESTER, # Incorrect domain - ) + ), ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -52,7 +59,7 @@ def test_invalid_signature_bad_domain(spec, state): @spec_state_test @always_bls def test_invalid_signature_missing_participant(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(2020) random_participant = rng.choice(committee_indices) @@ -66,7 +73,7 @@ def test_invalid_signature_missing_participant(spec, state): block.slot - 1, committee_indices, # full committee signs block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -79,10 +86,11 @@ def test_invalid_signature_no_participants(spec, state): # No participants is an allowed case, but needs a specific signature, not the full-zeroed signature. block.body.sync_aggregate = spec.SyncAggregate( sync_committee_bits=[False] * len(block.body.sync_aggregate.sync_committee_bits), - sync_committee_signature=b'\x00' * 96 + sync_committee_signature=b"\x00" * 96, ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) + # No-participants, with valid signature, is tested in test_sync_committee_rewards_empty_participants already. @@ -94,7 +102,7 @@ def test_invalid_signature_infinite_signature_with_all_participants(spec, state) # Include all participants, try the special-case signature for no-participants block.body.sync_aggregate = spec.SyncAggregate( sync_committee_bits=[True] * len(block.body.sync_aggregate.sync_committee_bits), - sync_committee_signature=spec.G2_POINT_AT_INFINITY + sync_committee_signature=spec.G2_POINT_AT_INFINITY, ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -106,8 +114,9 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat block = build_empty_block_for_next_slot(spec, state) # Try include a single participant with the special-case signature for no-participants. block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits=[True] + ([False] * (len(block.body.sync_aggregate.sync_committee_bits) - 1)), - sync_committee_signature=spec.G2_POINT_AT_INFINITY + sync_committee_bits=[True] + + ([False] * (len(block.body.sync_aggregate.sync_committee_bits) - 1)), + sync_committee_signature=spec.G2_POINT_AT_INFINITY, ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -116,7 +125,7 @@ def test_invalid_signature_infinite_signature_with_single_participant(spec, stat @spec_state_test @always_bls def test_invalid_signature_extra_participant(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(3030) random_participant = rng.choice(committee_indices) @@ -130,24 +139,30 @@ def test_invalid_signature_extra_participant(spec, state): block.slot - 1, [index for index in committee_indices if index != random_participant], block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) +def is_duplicate_sync_committee(committee_indices): + dup = {v for v in committee_indices if committee_indices.count(v) > 1} + return len(dup) > 0 + + @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) +@single_phase def test_sync_committee_rewards_nonduplicate_committee(spec, state): - committee_indices = compute_committee_indices(spec, state) - committee_size = len(committee_indices) - committee_bits = [True] * committee_size - active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + committee_indices = compute_committee_indices(state) # Preconditions of this test case - assert active_validator_count > spec.SYNC_COMMITTEE_SIZE - assert committee_size == len(set(committee_indices)) + assert not is_duplicate_sync_committee(committee_indices) + + committee_size = len(committee_indices) + committee_bits = [True] * committee_size yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) @@ -156,14 +171,13 @@ def test_sync_committee_rewards_nonduplicate_committee(spec, state): @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state): - committee_indices = compute_committee_indices(spec, state) - committee_size = len(committee_indices) - committee_bits = [False] * committee_size - active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + committee_indices = compute_committee_indices(state) # Preconditions of this test case - assert active_validator_count < spec.SYNC_COMMITTEE_SIZE - assert committee_size > len(set(committee_indices)) + assert is_duplicate_sync_committee(committee_indices) + + committee_size = len(committee_indices) + committee_bits = [False] * committee_size yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) @@ -172,15 +186,14 @@ def test_sync_committee_rewards_duplicate_committee_no_participation(spec, state @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_half_participation(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) + + # Preconditions of this test case + assert is_duplicate_sync_committee(committee_indices) + committee_size = len(committee_indices) committee_bits = [True] * (committee_size // 2) + [False] * (committee_size // 2) assert len(committee_bits) == committee_size - active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) - - # Preconditions of this test case - assert active_validator_count < spec.SYNC_COMMITTEE_SIZE - assert committee_size > len(set(committee_indices)) yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) @@ -189,23 +202,137 @@ def test_sync_committee_rewards_duplicate_committee_half_participation(spec, sta @with_presets([MAINNET], reason="to create duplicate committee") @spec_state_test def test_sync_committee_rewards_duplicate_committee_full_participation(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) + + # Preconditions of this test case + assert is_duplicate_sync_committee(committee_indices) + committee_size = len(committee_indices) committee_bits = [True] * committee_size - active_validator_count = len(spec.get_active_validator_indices(state, spec.get_current_epoch(state))) + + yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) + + +def _run_sync_committee_selected_twice( + spec, + state, + pre_balance, + participate_first_position, + participate_second_position, + skip_reward_validation=False, +): + committee_indices = compute_committee_indices(state) # Preconditions of this test case - assert active_validator_count < spec.SYNC_COMMITTEE_SIZE - assert committee_size > len(set(committee_indices)) + assert is_duplicate_sync_committee(committee_indices) - yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) + committee_size = len(committee_indices) + committee_bits = [False] * committee_size + + # Find duplicate indices that get selected twice + dup = {v for v in committee_indices if committee_indices.count(v) == 2} + assert len(dup) > 0 + validator_index = dup.pop() + positions = [i for i, v in enumerate(committee_indices) if v == validator_index] + committee_bits[positions[0]] = participate_first_position + committee_bits[positions[1]] = participate_second_position + + # Set validator's balance + state.balances[validator_index] = pre_balance + state.validators[validator_index].effective_balance = min( + pre_balance - pre_balance % spec.EFFECTIVE_BALANCE_INCREMENT, + spec.MAX_EFFECTIVE_BALANCE, + ) + + yield from run_successful_sync_committee_test( + spec, + state, + committee_indices, + committee_bits, + skip_reward_validation=skip_reward_validation, + ) + + return validator_index + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_sync_committee_rewards_duplicate_committee_zero_balance_only_participate_first_one( + spec, state +): + validator_index = yield from _run_sync_committee_selected_twice( + spec, + state, + pre_balance=0, + participate_first_position=True, + participate_second_position=False, + ) + + # The validator gets reward first (balance > 0) and then gets the same amount of penalty (balance == 0) + assert state.balances[validator_index] == 0 + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_sync_committee_rewards_duplicate_committee_zero_balance_only_participate_second_one( + spec, state +): + # Skip `validate_sync_committee_rewards` because it doesn't handle the balance computation order + # inside the for loop + validator_index = yield from _run_sync_committee_selected_twice( + spec, + state, + pre_balance=0, + participate_first_position=False, + participate_second_position=True, + skip_reward_validation=True, + ) + + # The validator gets penalty first (balance is still 0) and then gets reward (balance > 0) + assert state.balances[validator_index] > 0 + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_sync_committee_rewards_duplicate_committee_max_effective_balance_only_participate_first_one( + spec, state +): + validator_index = yield from _run_sync_committee_selected_twice( + spec, + state, + pre_balance=spec.MAX_EFFECTIVE_BALANCE, + participate_first_position=True, + participate_second_position=False, + ) + + assert state.balances[validator_index] == spec.MAX_EFFECTIVE_BALANCE + + +@with_altair_and_later +@with_presets([MAINNET], reason="to create duplicate committee") +@spec_state_test +def test_sync_committee_rewards_duplicate_committee_max_effective_balance_only_participate_second_one( + spec, state +): + validator_index = yield from _run_sync_committee_selected_twice( + spec, + state, + pre_balance=spec.MAX_EFFECTIVE_BALANCE, + participate_first_position=False, + participate_second_position=True, + ) + + assert state.balances[validator_index] == spec.MAX_EFFECTIVE_BALANCE @with_altair_and_later @spec_state_test @always_bls def test_sync_committee_rewards_not_full_participants(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(1010) committee_bits = [rng.choice([True, False]) for _ in committee_indices] @@ -216,7 +343,7 @@ def test_sync_committee_rewards_not_full_participants(spec, state): @spec_state_test @always_bls def test_sync_committee_rewards_empty_participants(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) committee_bits = [False for _ in committee_indices] yield from run_successful_sync_committee_test(spec, state, committee_indices, committee_bits) @@ -226,7 +353,7 @@ def test_sync_committee_rewards_empty_participants(spec, state): @spec_state_test @always_bls def test_invalid_signature_past_block(spec, state): - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) for _ in range(2): # NOTE: need to transition twice to move beyond the degenerate case at genesis @@ -240,7 +367,7 @@ def test_invalid_signature_past_block(spec, state): block.slot - 1, committee_indices, block_root=block.parent_root, - ) + ), ) state_transition_and_sign_block(spec, state, block) @@ -254,7 +381,7 @@ def test_invalid_signature_past_block(spec, state): state, invalid_block.slot - 2, committee_indices, - ) + ), ) yield from run_sync_committee_processing(spec, state, invalid_block, expect_exception=True) @@ -273,14 +400,18 @@ def test_invalid_signature_previous_committee(spec, state): current_epoch = spec.get_current_epoch(state) old_sync_committee = state.next_sync_committee - epoch_in_future_sync_commitee_period = current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - slot_in_future_sync_committee_period = epoch_in_future_sync_commitee_period * spec.SLOTS_PER_EPOCH + epoch_in_future_sync_committee_period = ( + current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + ) + slot_in_future_sync_committee_period = ( + epoch_in_future_sync_committee_period * spec.SLOTS_PER_EPOCH + ) transition_to(spec, state, slot_in_future_sync_committee_period) # Use the previous sync committee to produce the signature. # Ensure that the pubkey sets are different. assert set(old_sync_committee.pubkeys) != set(state.current_sync_committee.pubkeys) - committee_indices = compute_committee_indices(spec, state, old_sync_committee) + committee_indices = compute_committee_indices(state, old_sync_committee) block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -291,7 +422,7 @@ def test_invalid_signature_previous_committee(spec, state): block.slot - 1, committee_indices, block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block, expect_exception=True) @@ -311,8 +442,12 @@ def test_valid_signature_future_committee(spec, state): old_current_sync_committee = state.current_sync_committee old_next_sync_committee = state.next_sync_committee - epoch_in_future_sync_committee_period = current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - slot_in_future_sync_committee_period = epoch_in_future_sync_committee_period * spec.SLOTS_PER_EPOCH + epoch_in_future_sync_committee_period = ( + current_epoch + 2 * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + ) + slot_in_future_sync_committee_period = ( + epoch_in_future_sync_committee_period * spec.SLOTS_PER_EPOCH + ) transition_to(spec, state, slot_in_future_sync_committee_period) sync_committee = state.current_sync_committee @@ -322,7 +457,7 @@ def test_valid_signature_future_committee(spec, state): assert sync_committee != old_current_sync_committee assert sync_committee != old_next_sync_committee - committee_indices = compute_committee_indices(spec, state, sync_committee) + committee_indices = compute_committee_indices(state, sync_committee) block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( @@ -333,7 +468,7 @@ def test_valid_signature_future_committee(spec, state): block.slot - 1, committee_indices, block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block) @@ -344,7 +479,7 @@ def test_valid_signature_future_committee(spec, state): @always_bls @with_presets([MINIMAL], reason="prefer short search to find matching proposer") def test_proposer_in_committee_without_participation(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(state, state.current_sync_committee) # NOTE: seem to reliably be getting a matching proposer in the first epoch w/ ``MINIMAL`` preset. for _ in range(spec.SLOTS_PER_EPOCH): @@ -367,17 +502,22 @@ def test_proposer_in_committee_without_participation(spec, state): block.slot - 1, participants, block_root=block.parent_root, - ) + ), ) if proposer_is_in_sync_committee: - assert state.validators[block.proposer_index].pubkey in state.current_sync_committee.pubkeys + assert ( + state.validators[block.proposer_index].pubkey + in state.current_sync_committee.pubkeys + ) yield from run_sync_committee_processing(spec, state, block) break else: state_transition_and_sign_block(spec, state, block) else: - raise AssertionError("failed to find a proposer in the sync committee set; check test setup") + raise AssertionError( + "failed to find a proposer in the sync committee set; check test setup" + ) @with_altair_and_later @@ -385,7 +525,7 @@ def test_proposer_in_committee_without_participation(spec, state): @always_bls @with_presets([MINIMAL], reason="prefer short search to find matching proposer") def test_proposer_in_committee_with_participation(spec, state): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(state, state.current_sync_committee) participation = [True for _ in committee_indices] # NOTE: seem to reliably be getting a matching proposer in the first epoch w/ ``MINIMAL`` preset. @@ -404,11 +544,14 @@ def test_proposer_in_committee_with_participation(spec, state): block.slot - 1, committee_indices, block_root=block.parent_root, - ) + ), ) if proposer_is_in_sync_committee: - assert state.validators[block.proposer_index].pubkey in state.current_sync_committee.pubkeys + assert ( + state.validators[block.proposer_index].pubkey + in state.current_sync_committee.pubkeys + ) yield from run_sync_committee_processing(spec, state, block) return else: @@ -416,12 +559,9 @@ def test_proposer_in_committee_with_participation(spec, state): raise AssertionError("failed to find a proposer in the sync committee set; check test setup") -def _exit_validator_from_committee_and_transition_state(spec, - state, - committee_indices, - rng, - target_epoch_provider, - withdrawable_offset=1): +def _exit_validator_from_committee_and_transition_state( + spec, state, committee_indices, rng, target_epoch_provider, withdrawable_offset=1 +): exited_validator_index = rng.sample(committee_indices, 1)[0] validator = state.validators[exited_validator_index] current_epoch = spec.get_current_epoch(state) @@ -451,7 +591,7 @@ def test_sync_committee_with_participating_exited_member(spec, state): for _ in range(3): next_epoch_via_block(spec, state) - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(1010) exited_index = _exit_validator_from_committee_and_transition_state( @@ -474,7 +614,7 @@ def test_sync_committee_with_participating_exited_member(spec, state): block.slot - 1, committee_indices, # full committee signs block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block) @@ -490,7 +630,7 @@ def test_sync_committee_with_nonparticipating_exited_member(spec, state): for _ in range(3): next_epoch_via_block(spec, state) - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(1010) exited_index = _exit_validator_from_committee_and_transition_state( @@ -517,7 +657,7 @@ def test_sync_committee_with_nonparticipating_exited_member(spec, state): block.slot - 1, committee_indices, # with exited validator removed block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block) @@ -533,7 +673,7 @@ def test_sync_committee_with_participating_withdrawable_member(spec, state): for _ in range(3): next_epoch_via_block(spec, state) - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(1010) exited_index = _exit_validator_from_committee_and_transition_state( @@ -556,7 +696,7 @@ def test_sync_committee_with_participating_withdrawable_member(spec, state): block.slot - 1, committee_indices, # full committee signs block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block) @@ -572,7 +712,7 @@ def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): for _ in range(3): next_epoch_via_block(spec, state) - committee_indices = compute_committee_indices(spec, state) + committee_indices = compute_committee_indices(state) rng = random.Random(1010) exited_index = _exit_validator_from_committee_and_transition_state( @@ -599,6 +739,6 @@ def test_sync_committee_with_nonparticipating_withdrawable_member(spec, state): block.slot - 1, committee_indices, # with withdrawable validator removed block_root=block.parent_root, - ) + ), ) yield from run_sync_committee_processing(spec, state, block) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py index 903df40819..f83f7eb3e5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/sync_aggregate/test_process_sync_aggregate_random.py @@ -1,6 +1,20 @@ import random + +from eth2spec.test.context import ( + default_activation_threshold, + default_balances_electra, + misc_balances, + misc_balances_electra, + single_phase, + spec_state_test, + spec_test, + with_altair_and_later, + with_custom_state, + with_presets, +) from eth2spec.test.helpers.constants import ( - MAINNET, MINIMAL, + MAINNET, + MINIMAL, ) from eth2spec.test.helpers.random import ( randomize_state, @@ -15,20 +29,12 @@ from eth2spec.test.helpers.voluntary_exits import ( get_unslashed_exited_validators, ) -from eth2spec.test.context import ( - with_altair_and_later, - spec_state_test, - default_activation_threshold, - misc_balances, - single_phase, - with_custom_state, - with_presets, - spec_test, -) -def _test_harness_for_randomized_test_case(spec, state, expect_duplicates=False, participation_fn=None): - committee_indices = compute_committee_indices(spec, state) +def _test_harness_for_randomized_test_case( + spec, state, expect_duplicates=False, participation_fn=None +): + committee_indices = compute_committee_indices(state) if participation_fn: participating_indices = participation_fn(committee_indices) @@ -132,7 +138,9 @@ def test_random_with_exits_with_duplicates(spec, state): @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) +@single_phase def test_random_only_one_participant_without_duplicates(spec, state): rng = random.Random(501) yield from _test_harness_for_randomized_test_case( @@ -144,7 +152,9 @@ def test_random_only_one_participant_without_duplicates(spec, state): @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) +@single_phase def test_random_low_participation_without_duplicates(spec, state): rng = random.Random(601) yield from _test_harness_for_randomized_test_case( @@ -156,7 +166,9 @@ def test_random_low_participation_without_duplicates(spec, state): @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) +@single_phase def test_random_high_participation_without_duplicates(spec, state): rng = random.Random(701) yield from _test_harness_for_randomized_test_case( @@ -168,7 +180,9 @@ def test_random_high_participation_without_duplicates(spec, state): @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) +@single_phase def test_random_all_but_one_participating_without_duplicates(spec, state): rng = random.Random(801) yield from _test_harness_for_randomized_test_case( @@ -181,7 +195,7 @@ def test_random_all_but_one_participating_without_duplicates(spec, state): @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") @spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=default_activation_threshold) +@with_custom_state(balances_fn=misc_balances_electra, threshold_fn=default_activation_threshold) @single_phase def test_random_misc_balances_and_half_participation_without_duplicates(spec, state): rng = random.Random(1501) @@ -194,7 +208,8 @@ def test_random_misc_balances_and_half_participation_without_duplicates(spec, st @with_altair_and_later @with_presets([MINIMAL], reason="to create nonduplicate committee") -@spec_state_test +@spec_test +@with_custom_state(balances_fn=default_balances_electra, threshold_fn=default_activation_threshold) @single_phase def test_random_with_exits_without_duplicates(spec, state): rng = random.Random(1502) diff --git a/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_deposit.py new file mode 100644 index 0000000000..81881c08dc --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/block_processing/test_process_deposit.py @@ -0,0 +1,39 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_altair_and_later, + with_phases, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, +) +from eth2spec.test.helpers.deposits import ( + run_deposit_processing_with_specific_fork_version, +) + + +@with_phases([ALTAIR]) +@spec_state_test +@always_bls +def test_effective_deposit_with_previous_fork_version(spec, state): + assert state.fork.previous_version != state.fork.current_version + + # It's only effective in Altair because the default `fork_version` of `compute_domain` is `GENESIS_FORK_VERSION`. + # Therefore it's just a normal `DepositMessage`. + yield from run_deposit_processing_with_specific_fork_version( + spec, + state, + fork_version=state.fork.previous_version, + ) + + +@with_altair_and_later +@spec_state_test +@always_bls +def test_ineffective_deposit_with_current_fork_version(spec, state): + yield from run_deposit_processing_with_specific_fork_version( + spec, + state, + fork_version=state.fork.current_version, + effective=False, + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py index 2a971f4f08..bfdff20a66 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_inactivity_updates.py @@ -1,19 +1,10 @@ from random import Random from eth2spec.test.context import spec_state_test, with_altair_and_later -from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores, zero_inactivity_scores -from eth2spec.test.helpers.state import ( - next_epoch, - next_epoch_via_block, - set_full_participation, - set_empty_participation, -) -from eth2spec.test.helpers.voluntary_exits import ( - exit_validators, - get_exited_validators -) -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.inactivity_scores import ( + randomize_inactivity_scores, + zero_inactivity_scores, ) from eth2spec.test.helpers.random import ( randomize_attestation_participation, @@ -21,10 +12,17 @@ randomize_state, ) from eth2spec.test.helpers.rewards import leaking +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, + set_empty_participation, + set_full_participation, +) +from eth2spec.test.helpers.voluntary_exits import exit_validators, get_exited_validators def run_process_inactivity_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_inactivity_updates') + yield from run_epoch_processing_with(spec, state, "process_inactivity_updates") @with_altair_and_later @@ -50,8 +48,19 @@ def test_genesis_random_scores(spec, state): # Thus all of following tests all go past genesis epoch to test core functionality # -def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_scores_fn=None, rng=Random(10101)): - next_epoch_via_block(spec, state) + +def run_inactivity_scores_test( + spec, state, participation_fn=None, inactivity_scores_fn=None, rng=Random(10101) +): + while True: + try: + next_epoch_via_block(spec, state) + except AssertionError: + # If the proposer is slashed, we skip this epoch and try to propose block at the next epoch + next_epoch(spec, state) + else: + break + if participation_fn is not None: participation_fn(spec, state, rng=rng) if inactivity_scores_fn is not None: @@ -62,7 +71,9 @@ def run_inactivity_scores_test(spec, state, participation_fn=None, inactivity_sc @with_altair_and_later @spec_state_test def test_all_zero_inactivity_scores_empty_participation(spec, state): - yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + yield from run_inactivity_scores_test( + spec, state, set_empty_participation, zero_inactivity_scores + ) assert set(state.inactivity_scores) == set([0]) @@ -70,7 +81,9 @@ def test_all_zero_inactivity_scores_empty_participation(spec, state): @spec_state_test @leaking() def test_all_zero_inactivity_scores_empty_participation_leaking(spec, state): - yield from run_inactivity_scores_test(spec, state, set_empty_participation, zero_inactivity_scores) + yield from run_inactivity_scores_test( + spec, state, set_empty_participation, zero_inactivity_scores + ) # Should still in be leak assert spec.is_in_inactivity_leak(state) @@ -83,8 +96,11 @@ def test_all_zero_inactivity_scores_empty_participation_leaking(spec, state): @spec_state_test def test_all_zero_inactivity_scores_random_participation(spec, state): yield from run_inactivity_scores_test( - spec, state, - randomize_attestation_participation, zero_inactivity_scores, rng=Random(5555), + spec, + state, + randomize_attestation_participation, + zero_inactivity_scores, + rng=Random(5555), ) assert set(state.inactivity_scores) == set([0]) @@ -95,8 +111,11 @@ def test_all_zero_inactivity_scores_random_participation(spec, state): def test_all_zero_inactivity_scores_random_participation_leaking(spec, state): # Only randomize participation in previous epoch to remain in leak yield from run_inactivity_scores_test( - spec, state, - randomize_previous_epoch_participation, zero_inactivity_scores, rng=Random(5555), + spec, + state, + randomize_previous_epoch_participation, + zero_inactivity_scores, + rng=Random(5555), ) # Check still in leak @@ -110,8 +129,10 @@ def test_all_zero_inactivity_scores_random_participation_leaking(spec, state): @spec_state_test def test_all_zero_inactivity_scores_full_participation(spec, state): yield from run_inactivity_scores_test( - spec, state, - set_full_participation, zero_inactivity_scores, + spec, + state, + set_full_participation, + zero_inactivity_scores, ) assert set(state.inactivity_scores) == set([0]) @@ -123,8 +144,10 @@ def test_all_zero_inactivity_scores_full_participation(spec, state): def test_all_zero_inactivity_scores_full_participation_leaking(spec, state): # Only set full participation in previous epoch to remain in leak yield from run_inactivity_scores_test( - spec, state, - set_full_participation, zero_inactivity_scores, + spec, + state, + set_full_participation, + zero_inactivity_scores, ) # Check still in leak @@ -137,8 +160,11 @@ def test_all_zero_inactivity_scores_full_participation_leaking(spec, state): @spec_state_test def test_random_inactivity_scores_empty_participation(spec, state): yield from run_inactivity_scores_test( - spec, state, - set_empty_participation, randomize_inactivity_scores, Random(9999), + spec, + state, + set_empty_participation, + randomize_inactivity_scores, + Random(9999), ) @@ -147,8 +173,11 @@ def test_random_inactivity_scores_empty_participation(spec, state): @leaking() def test_random_inactivity_scores_empty_participation_leaking(spec, state): yield from run_inactivity_scores_test( - spec, state, - set_empty_participation, randomize_inactivity_scores, Random(9999), + spec, + state, + set_empty_participation, + randomize_inactivity_scores, + Random(9999), ) # Check still in leak @@ -159,8 +188,11 @@ def test_random_inactivity_scores_empty_participation_leaking(spec, state): @spec_state_test def test_random_inactivity_scores_random_participation(spec, state): yield from run_inactivity_scores_test( - spec, state, - randomize_attestation_participation, randomize_inactivity_scores, Random(22222), + spec, + state, + randomize_attestation_participation, + randomize_inactivity_scores, + Random(22222), ) @@ -168,10 +200,13 @@ def test_random_inactivity_scores_random_participation(spec, state): @spec_state_test @leaking() def test_random_inactivity_scores_random_participation_leaking(spec, state): - # Only randompize participation in previous epoch to remain in leak + # Only randomize participation in previous epoch to remain in leak yield from run_inactivity_scores_test( - spec, state, - randomize_previous_epoch_participation, randomize_inactivity_scores, Random(22222), + spec, + state, + randomize_previous_epoch_participation, + randomize_inactivity_scores, + Random(22222), ) # Check still in leak @@ -182,8 +217,11 @@ def test_random_inactivity_scores_random_participation_leaking(spec, state): @spec_state_test def test_random_inactivity_scores_full_participation(spec, state): yield from run_inactivity_scores_test( - spec, state, - set_full_participation, randomize_inactivity_scores, Random(33333), + spec, + state, + set_full_participation, + randomize_inactivity_scores, + Random(33333), ) @@ -193,8 +231,11 @@ def test_random_inactivity_scores_full_participation(spec, state): def test_random_inactivity_scores_full_participation_leaking(spec, state): # Only set full participation in previous epoch to remain in leak yield from run_inactivity_scores_test( - spec, state, - set_full_participation, randomize_inactivity_scores, Random(33333), + spec, + state, + set_full_participation, + randomize_inactivity_scores, + Random(33333), ) # Check still in leak @@ -209,7 +250,7 @@ def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(404 next_epoch_via_block(spec, future_state) proposer_index = spec.get_beacon_proposer_index(future_state) - # Slash ~1/4 of validaors + # Slash ~1/4 of validators for validator_index in range(len(state.validators)): if rng.choice(range(4)) == 0 and validator_index != proposer_index: spec.slash_validator(state, validator_index) @@ -220,8 +261,10 @@ def slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(404 def test_some_slashed_zero_scores_full_participation(spec, state): slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(33429)) yield from run_inactivity_scores_test( - spec, state, - set_full_participation, zero_inactivity_scores, + spec, + state, + set_full_participation, + zero_inactivity_scores, ) assert set(state.inactivity_scores) == set([0]) @@ -233,8 +276,10 @@ def test_some_slashed_zero_scores_full_participation(spec, state): def test_some_slashed_zero_scores_full_participation_leaking(spec, state): slash_some_validators_for_inactivity_scores_test(spec, state, rng=Random(332243)) yield from run_inactivity_scores_test( - spec, state, - set_full_participation, zero_inactivity_scores, + spec, + state, + set_full_participation, + zero_inactivity_scores, ) # Check still in leak @@ -254,8 +299,11 @@ def test_some_slashed_full_random(spec, state): rng = Random(1010222) slash_some_validators_for_inactivity_scores_test(spec, state, rng=rng) yield from run_inactivity_scores_test( - spec, state, - randomize_attestation_participation, randomize_inactivity_scores, rng=rng, + spec, + state, + randomize_attestation_participation, + randomize_inactivity_scores, + rng=rng, ) @@ -266,8 +314,11 @@ def test_some_slashed_full_random_leaking(spec, state): rng = Random(1102233) slash_some_validators_for_inactivity_scores_test(spec, state, rng=rng) yield from run_inactivity_scores_test( - spec, state, - randomize_previous_epoch_participation, randomize_inactivity_scores, rng=rng, + spec, + state, + randomize_previous_epoch_participation, + randomize_inactivity_scores, + rng=rng, ) # Check still in leak @@ -303,8 +354,10 @@ def test_some_exited_full_random_leaking(spec, state): previous_scores = state.inactivity_scores.copy() yield from run_inactivity_scores_test( - spec, state, - randomize_previous_epoch_participation, rng=rng, + spec, + state, + randomize_previous_epoch_participation, + rng=rng, ) # ensure exited validators have their score "frozen" at exit @@ -360,7 +413,7 @@ def test_randomized_state(spec, state): their inactivity score does not change. """ rng = Random(10011001) - _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) + yield from _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) @with_altair_and_later @@ -374,6 +427,6 @@ def test_randomized_state_leaking(spec, state): (refer ``get_eligible_validator_indices`). """ rng = Random(10011002) - _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) + yield from _run_randomized_state_test_for_inactivity_updates(spec, state, rng=rng) # Check still in leak assert spec.is_in_inactivity_leak(state) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py index 8aa44bc56c..049fda229b 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_participation_flag_updates.py @@ -1,15 +1,16 @@ from random import Random -from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( + single_phase, + spec_state_test, + spec_test, with_altair_and_later, with_custom_state, - spec_test, spec_state_test, with_presets, - single_phase, ) -from eth2spec.test.helpers.state import next_epoch_via_block +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.state import next_epoch_via_block def get_full_flags(spec): @@ -21,7 +22,7 @@ def get_full_flags(spec): def run_process_participation_flag_updates(spec, state): old = state.current_epoch_participation.copy() - yield from run_epoch_processing_with(spec, state, 'process_participation_flag_updates') + yield from run_epoch_processing_with(spec, state, "process_participation_flag_updates") assert state.current_epoch_participation == [0] * len(state.validators) assert state.previous_epoch_participation == old @@ -71,11 +72,15 @@ def test_current_filled(spec, state): def random_flags(spec, state, seed: int, previous=True, current=True): rng = Random(seed) count = len(state.validators) - max_flag_value_excl = 2**len(spec.PARTICIPATION_FLAG_WEIGHTS) + max_flag_value_excl = 2 ** len(spec.PARTICIPATION_FLAG_WEIGHTS) if previous: - state.previous_epoch_participation = [rng.randrange(0, max_flag_value_excl) for _ in range(count)] + state.previous_epoch_participation = [ + rng.randrange(0, max_flag_value_excl) for _ in range(count) + ] if current: - state.current_epoch_participation = [rng.randrange(0, max_flag_value_excl) for _ in range(count)] + state.current_epoch_participation = [ + rng.randrange(0, max_flag_value_excl) for _ in range(count) + ] @with_altair_and_later @@ -129,15 +134,22 @@ def test_previous_epoch_zeroed(spec, state): def custom_validator_count(factor: float): def initializer(spec): - num_validators = spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE + num_validators = ( + spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE + ) return [spec.MAX_EFFECTIVE_BALANCE] * int(float(int(num_validators)) * factor) + return initializer @with_altair_and_later -@with_presets([MINIMAL], reason="mainnet config requires too many pre-generated public/private keys") +@with_presets( + [MINIMAL], reason="mainnet config requires too many pre-generated public/private keys" +) @spec_test -@with_custom_state(balances_fn=custom_validator_count(1.3), threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=custom_validator_count(1.3), threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @single_phase def test_slightly_larger_random(spec, state): next_epoch_via_block(spec, state) @@ -146,9 +158,13 @@ def test_slightly_larger_random(spec, state): @with_altair_and_later -@with_presets([MINIMAL], reason="mainnet config requires too many pre-generated public/private keys") +@with_presets( + [MINIMAL], reason="mainnet config requires too many pre-generated public/private keys" +) @spec_test -@with_custom_state(balances_fn=custom_validator_count(2.6), threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=custom_validator_count(2.6), threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @single_phase def test_large_random(spec, state): next_epoch_via_block(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py index 1667a12b22..0a540b5d41 100644 --- a/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py +++ b/tests/core/pyspec/eth2spec/test/altair/epoch_processing/test_process_sync_committee_updates.py @@ -1,30 +1,30 @@ from eth2spec.test.context import ( always_bls, + misc_balances, + single_phase, spec_state_test, spec_test, with_altair_and_later, - with_presets, with_custom_state, - single_phase, - misc_balances, + with_presets, ) from eth2spec.test.helpers.constants import MINIMAL -from eth2spec.test.helpers.state import transition_to from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, ) - +from eth2spec.test.helpers.state import transition_to # # Note: # Calculating sync committees requires pubkey aggregation, thus all tests are generated with `always_bls` # + def run_sync_committees_progress_test(spec, state): first_sync_committee = state.current_sync_committee.copy() second_sync_committee = state.next_sync_committee.copy() - current_period = spec.get_current_epoch(state) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + current_period = spec.compute_sync_committee_period(spec.get_current_epoch(state)) next_period = current_period + 1 next_period_start_epoch = next_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD next_period_start_slot = next_period_start_epoch * spec.SLOTS_PER_EPOCH @@ -35,7 +35,7 @@ def run_sync_committees_progress_test(spec, state): assert state.current_sync_committee == first_sync_committee assert state.next_sync_committee == second_sync_committee - yield from run_epoch_processing_with(spec, state, 'process_sync_committee_updates') + yield from run_epoch_processing_with(spec, state, "process_sync_committee_updates") # Can compute the third committee having computed final balances in the last epoch # of this `EPOCHS_PER_SYNC_COMMITTEE_PERIOD` @@ -79,7 +79,9 @@ def test_sync_committees_progress_not_genesis(spec, state): @with_altair_and_later -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase @always_bls @@ -92,7 +94,9 @@ def test_sync_committees_progress_misc_balances_genesis(spec, state): @with_altair_and_later -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase @always_bls @@ -110,7 +114,7 @@ def test_sync_committees_progress_misc_balances_not_genesis(spec, state): @spec_state_test @always_bls @with_presets([MINIMAL], reason="too slow") -def test_sync_committees_no_progress_not_boundary(spec, state): +def test_sync_committees_no_progress_not_at_period_boundary(spec, state): assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH slot_not_at_period_boundary = state.slot + spec.SLOTS_PER_EPOCH transition_to(spec, state, slot_not_at_period_boundary) @@ -118,7 +122,7 @@ def test_sync_committees_no_progress_not_boundary(spec, state): first_sync_committee = state.current_sync_committee.copy() second_sync_committee = state.next_sync_committee.copy() - yield from run_epoch_processing_with(spec, state, 'process_sync_committee_updates') + yield from run_epoch_processing_with(spec, state, "process_sync_committee_updates") # Ensure assignments have not changed: assert state.current_sync_committee == first_sync_committee diff --git a/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_basic.py b/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_basic.py index 6c212afbc1..f02d7e3f5a 100644 --- a/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_basic.py +++ b/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_basic.py @@ -1,23 +1,27 @@ from eth2spec.test.context import ( - with_phases, + large_validator_set, + low_balances, + misc_balances, + spec_test, with_custom_state, + with_phases, with_presets, - spec_test, with_state, - low_balances, misc_balances, large_validator_set, + with_state, +) +from eth2spec.test.helpers.altair.fork import ( + ALTAIR_FORK_TEST_META_TAGS, + run_fork_test, ) -from eth2spec.test.utils import with_meta_tags from eth2spec.test.helpers.constants import ( - PHASE0, ALTAIR, + ALTAIR, MINIMAL, + PHASE0, ) from eth2spec.test.helpers.state import ( next_epoch, next_epoch_via_block, ) -from eth2spec.test.helpers.altair.fork import ( - ALTAIR_FORK_TEST_META_TAGS, - run_fork_test, -) +from eth2spec.test.utils import with_meta_tags @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) @@ -65,7 +69,9 @@ def test_fork_random_low_balances(spec, phases, state): @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @with_meta_tags(ALTAIR_FORK_TEST_META_TAGS) def test_fork_random_misc_balances(spec, phases, state): @@ -73,9 +79,13 @@ def test_fork_random_misc_balances(spec, phases, state): @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @with_meta_tags(ALTAIR_FORK_TEST_META_TAGS) def test_fork_random_large_validator_set(spec, phases, state): diff --git a/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_random.py b/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_random.py index 530261df6d..32ba880f85 100644 --- a/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/fork/test_altair_fork_random.py @@ -1,25 +1,29 @@ from random import Random from eth2spec.test.context import ( - with_phases, + large_validator_set, + low_balances, + misc_balances, + spec_test, with_custom_state, + with_phases, with_presets, - spec_test, with_state, - low_balances, misc_balances, large_validator_set, -) -from eth2spec.test.utils import with_meta_tags -from eth2spec.test.helpers.constants import ( - PHASE0, ALTAIR, - MINIMAL, + with_state, ) from eth2spec.test.helpers.altair.fork import ( ALTAIR_FORK_TEST_META_TAGS, run_fork_test, ) +from eth2spec.test.helpers.constants import ( + ALTAIR, + MINIMAL, + PHASE0, +) from eth2spec.test.helpers.random import ( - randomize_state, randomize_attestation_participation, + randomize_state, ) +from eth2spec.test.utils import with_meta_tags @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) @@ -65,7 +69,9 @@ def test_altair_fork_random_3(spec, phases, state): def test_altair_fork_random_duplicate_attestations(spec, phases, state): randomize_state(spec, state, rng=Random(1111)) # Note: `run_fork_test` empties `current_epoch_attestations` - state.previous_epoch_attestations = state.previous_epoch_attestations + state.previous_epoch_attestations + state.previous_epoch_attestations = ( + state.previous_epoch_attestations + state.previous_epoch_attestations + ) yield from run_fork_test(phases[ALTAIR], state) @@ -87,7 +93,9 @@ def test_altair_fork_random_mismatched_attestations(spec, phases, state): # Note: `run_fork_test` empties `current_epoch_attestations` # Use pending attestations from both random states in a single state for testing - state_0.previous_epoch_attestations = state_0.previous_epoch_attestations + state_1.previous_epoch_attestations + state_0.previous_epoch_attestations = ( + state_0.previous_epoch_attestations + state_1.previous_epoch_attestations + ) yield from run_fork_test(phases[ALTAIR], state_0) @@ -102,7 +110,9 @@ def test_altair_fork_random_low_balances(spec, phases, state): @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) @spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @with_meta_tags(ALTAIR_FORK_TEST_META_TAGS) def test_altair_fork_random_misc_balances(spec, phases, state): randomize_state(spec, state, rng=Random(6060)) @@ -110,10 +120,14 @@ def test_altair_fork_random_misc_balances(spec, phases, state): @with_phases(phases=[PHASE0], other_phases=[ALTAIR]) -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @with_meta_tags(ALTAIR_FORK_TEST_META_TAGS) def test_altair_fork_random_large_validator_set(spec, phases, state): randomize_state(spec, state, rng=Random(7070)) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/altair/light_client/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/altair/light_client/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_data_collection.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_data_collection.py new file mode 100644 index 0000000000..14c25bbc51 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_data_collection.py @@ -0,0 +1,272 @@ +from eth2spec.test.context import ( + spec_state_test_with_matching_config, + with_light_client, + with_presets, +) +from eth2spec.test.helpers.constants import ( + MINIMAL, +) +from eth2spec.test.helpers.light_client_data_collection import ( + add_new_block, + BlockID, + finish_lc_data_collection_test, + get_lc_bootstrap_block_id, + get_lc_update_attested_block_id, + get_light_client_bootstrap, + get_light_client_finality_update, + get_light_client_optimistic_update, + get_light_client_update_for_period, + select_new_head, + setup_lc_data_collection_test, +) + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_light_client_data_collection(spec, state): + # Start test + test = yield from setup_lc_data_collection_test(spec, state) + + # Genesis block is post Altair and is finalized, so can be used as bootstrap + genesis_bid = BlockID( + slot=state.slot, root=spec.BeaconBlock(state_root=state.hash_tree_root()).hash_tree_root() + ) + assert ( + get_lc_bootstrap_block_id(get_light_client_bootstrap(test, genesis_bid.root).data) + == genesis_bid + ) + + # No blocks have been imported, so no other light client data is available + period = spec.compute_sync_committee_period_at_slot(state.slot) + assert get_light_client_update_for_period(test, period).spec is None + assert get_light_client_finality_update(test).spec is None + assert get_light_client_optimistic_update(test).spec is None + + # Start branch A with a block that has an empty sync aggregate + spec_a, state_a, bid_1 = yield from add_new_block(test, spec, state, slot=1) + yield from select_new_head(test, spec_a, bid_1) + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert get_light_client_update_for_period(test, period).spec is None + assert get_light_client_finality_update(test).spec is None + assert get_light_client_optimistic_update(test).spec is None + + # Start branch B with a block that has 1 participant + spec_b, state_b, bid_2 = yield from add_new_block( + test, spec, state, slot=2, num_sync_participants=1 + ) + yield from select_new_head(test, spec_b, bid_2) + period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == genesis_bid + ) + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == genesis_bid + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) + == genesis_bid + ) + + # Build on branch A, once more with an empty sync aggregate + spec_a, state_a, bid_3 = yield from add_new_block(test, spec_a, state_a, slot=3) + yield from select_new_head(test, spec_a, bid_3) + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert get_light_client_update_for_period(test, period).spec is None + assert get_light_client_finality_update(test).spec is None + assert get_light_client_optimistic_update(test).spec is None + + # Build on branch B, this time with an empty sync aggregate + spec_b, state_b, bid_4 = yield from add_new_block(test, spec_b, state_b, slot=4) + yield from select_new_head(test, spec_b, bid_4) + period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == genesis_bid + ) + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == genesis_bid + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) + == genesis_bid + ) + + # Build on branch B, once more with 1 participant + spec_b, state_b, bid_5 = yield from add_new_block( + test, spec_b, state_b, slot=5, num_sync_participants=1 + ) + yield from select_new_head(test, spec_b, bid_5) + period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == genesis_bid + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_4 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_4 + + # Build on branch B, this time with 3 participants + spec_b, state_b, bid_6 = yield from add_new_block( + test, spec_b, state_b, slot=6, num_sync_participants=3 + ) + yield from select_new_head(test, spec_b, bid_6) + period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_5 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_5 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_5 + + # Build on branch A, with 2 participants + spec_a, state_a, bid_7 = yield from add_new_block( + test, spec_a, state_a, slot=7, num_sync_participants=2 + ) + yield from select_new_head(test, spec_a, bid_7) + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_3 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_3 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_3 + + # Branch A: epoch 1, slot 5 + slot = spec_a.compute_start_slot_at_epoch(1) + 5 + spec_a, state_a, bid_1_5 = yield from add_new_block( + test, spec_a, state_a, slot=slot, num_sync_participants=4 + ) + yield from select_new_head(test, spec_a, bid_1_5) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_7 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_7 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_7 + + # Branch B: epoch 2, slot 4 + slot = spec_b.compute_start_slot_at_epoch(2) + 4 + spec_b, state_b, bid_2_4 = yield from add_new_block( + test, spec_b, state_b, slot=slot, num_sync_participants=5 + ) + yield from select_new_head(test, spec_b, bid_2_4) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_6 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_6 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_6 + + # Branch A: epoch 3, slot 0 + slot = spec_a.compute_start_slot_at_epoch(3) + 0 + spec_a, state_a, bid_3_0 = yield from add_new_block( + test, spec_a, state_a, slot=slot, num_sync_participants=6 + ) + yield from select_new_head(test, spec_a, bid_3_0) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + assert get_light_client_bootstrap(test, bid_3_0.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_1_5 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_1_5 + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_1_5 + + # Branch A: fill epoch + for i in range(1, spec_a.SLOTS_PER_EPOCH): + spec_a, state_a, bid_a = yield from add_new_block(test, spec_a, state_a) + yield from select_new_head(test, spec_a, bid_a) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + assert get_light_client_bootstrap(test, bid_3_0.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_1_5 + ) + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_1_5 + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) + == bid_1_5 + ) + assert state_a.slot == spec_a.compute_start_slot_at_epoch(4) - 1 + bid_3_n = bid_a + + # Branch A: epoch 4, slot 0 + slot = spec_a.compute_start_slot_at_epoch(4) + 0 + spec_a, state_a, bid_4_0 = yield from add_new_block( + test, spec_a, state_a, slot=slot, num_sync_participants=6 + ) + yield from select_new_head(test, spec_a, bid_4_0) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + assert get_light_client_bootstrap(test, bid_3_0.root).spec is None + assert get_light_client_bootstrap(test, bid_4_0.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_1_5 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_3_n + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_3_n + + # Branch A: fill epoch + for i in range(1, spec_a.SLOTS_PER_EPOCH): + spec_a, state_a, bid_a = yield from add_new_block(test, spec_a, state_a) + yield from select_new_head(test, spec_a, bid_a) + assert get_light_client_bootstrap(test, bid_7.root).spec is None + assert get_light_client_bootstrap(test, bid_1_5.root).spec is None + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + assert get_light_client_bootstrap(test, bid_3_0.root).spec is None + assert get_light_client_bootstrap(test, bid_4_0.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_1_5 + ) + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_3_n + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) + == bid_3_n + ) + assert state_a.slot == spec_a.compute_start_slot_at_epoch(5) - 1 + bid_4_n = bid_a + + # Branch A: epoch 6, slot 2 + slot = spec_a.compute_start_slot_at_epoch(6) + 2 + spec_a, state_a, bid_6_2 = yield from add_new_block( + test, spec_a, state_a, slot=slot, num_sync_participants=6 + ) + yield from select_new_head(test, spec_a, bid_6_2) + assert get_lc_bootstrap_block_id(get_light_client_bootstrap(test, bid_7.root).data) == bid_7 + assert get_lc_bootstrap_block_id(get_light_client_bootstrap(test, bid_1_5.root).data) == bid_1_5 + assert get_light_client_bootstrap(test, bid_2_4.root).spec is None + assert get_lc_bootstrap_block_id(get_light_client_bootstrap(test, bid_3_0.root).data) == bid_3_0 + assert get_light_client_bootstrap(test, bid_4_0.root).spec is None + period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, period).data) + == bid_1_5 + ) + assert get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bid_4_n + assert get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bid_4_n + + # Finish test + yield from finish_lc_data_collection_test(test) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py new file mode 100644 index 0000000000..a4a7c191ab --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_single_merkle_proof.py @@ -0,0 +1,83 @@ +from eth2spec.test.context import ( + spec_state_test, + with_light_client, + with_test_suite_name, +) +from eth2spec.test.helpers.light_client import ( + latest_current_sync_committee_gindex, + latest_finalized_root_gindex, + latest_next_sync_committee_gindex, +) + + +@with_test_suite_name("BeaconState") +@with_light_client +@spec_state_test +def test_current_sync_committee_merkle_proof(spec, state): + yield "object", state + gindex = latest_current_sync_committee_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) + yield ( + "proof", + { + "leaf": "0x" + state.current_sync_committee.hash_tree_root().hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in branch], + }, + ) + assert spec.is_valid_merkle_branch( + leaf=state.current_sync_committee.hash_tree_root(), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=state.hash_tree_root(), + ) + + +@with_test_suite_name("BeaconState") +@with_light_client +@spec_state_test +def test_next_sync_committee_merkle_proof(spec, state): + yield "object", state + gindex = latest_next_sync_committee_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) + yield ( + "proof", + { + "leaf": "0x" + state.next_sync_committee.hash_tree_root().hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in branch], + }, + ) + assert spec.is_valid_merkle_branch( + leaf=state.next_sync_committee.hash_tree_root(), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=state.hash_tree_root(), + ) + + +@with_test_suite_name("BeaconState") +@with_light_client +@spec_state_test +def test_finality_root_merkle_proof(spec, state): + yield "object", state + gindex = latest_finalized_root_gindex(spec) + branch = spec.compute_merkle_proof(state, gindex) + yield ( + "proof", + { + "leaf": "0x" + state.finalized_checkpoint.root.hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in branch], + }, + ) + + assert spec.is_valid_merkle_branch( + leaf=state.finalized_checkpoint.root, + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=state.hash_tree_root(), + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py new file mode 100644 index 0000000000..185c5fc5ee --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_sync.py @@ -0,0 +1,530 @@ +from eth2spec.test.context import ( + spec_state_test_with_matching_config, + spec_test, + with_all_phases_from_to, + with_light_client, + with_matching_spec_config, + with_presets, + with_state, +) +from eth2spec.test.helpers.attestations import ( + next_slots_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + CAPELLA, + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.light_client import ( + compute_start_slot_at_next_sync_committee_period, + get_sync_aggregate, +) +from eth2spec.test.helpers.light_client_sync import ( + emit_force_update, + emit_update, + finish_lc_sync_test, + setup_lc_sync_test, +) +from eth2spec.test.helpers.state import ( + next_slots, + transition_to, +) + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_light_client_sync(spec, state): + # Start test + test = yield from setup_lc_sync_test(spec, state) + + # Initial `LightClientUpdate`, populating `store.next_sync_committee` + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Advance to next sync committee period + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Edge case: Signature in next period + # ``` + # | + # +-----------+ +----------+ | +-----------+ + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | + # +-----------+ +----------+ | +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 2) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Edge case: Finalized header not included + # ``` + # | + # + - - - - - + | +----------+ +-----------+ + # ¦ finalized ¦ <-- (2 epochs) -- | attested | <-- | signature | + # + - - - - - + | +----------+ +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block=None + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Non-finalized case: Attested `next_sync_committee` is not finalized + # ``` + # | + # +-----------+ | +----------+ +-----------+ + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | + # +-----------+ | +----------+ +-----------+ + # | + # | + # sync committee + # period boundary + # ``` + attested_block = block.copy() + attested_state = state.copy() + store_state = attested_state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Force-update using timeout + # ``` + # | + # +-----------+ | +----------+ + # | finalized | <-- (2 epochs) -- | attested | + # +-----------+ | +----------+ + # | ^ + # | \ + # sync committee `--- store.finalized_header + # period boundary + # ``` + attested_block = block.copy() + attested_state = state.copy() + next_slots(spec, state, spec.UPDATE_TIMEOUT - 1) + yield from emit_force_update(test, spec, state) + assert test.store.finalized_header.beacon.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == store_state.slot + + # Edge case: Finalized header not included, after force-update + # ``` + # | | + # + - - - - - + | +--+ +----------+ | +-----------+ + # ¦ finalized ¦ <-- (2 epochs) -- | | <-- | attested | <-- | signature | + # + - - - - - + | +--+ +----------+ | +-----------+ + # | / | + # | store.fin | + # sync committee sync committee + # period boundary period boundary + # ``` + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block=None + ) + assert test.store.finalized_header.beacon.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Edge case: Finalized header older than store + # ``` + # | | + # +-----------+ | +--+ | +----------+ +-----------+ + # | finalized | <-- (2 epochs) -- | | <-- | attested | <-- | signature | + # +-----------+ | +--+ | +----------+ +-----------+ + # | / | + # | store.fin | + # sync committee sync committee + # period boundary period boundary + # ``` + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == store_state.slot + assert test.store.next_sync_committee == store_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + yield from emit_force_update(test, spec, state) + assert test.store.finalized_header.beacon.slot == attested_state.slot + assert test.store.next_sync_committee == attested_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Advance to next sync committee period + # ``` + # | + # +-----------+ +----------+ +-----------+ | + # | finalized | <-- (2 epochs) -- | attested | <-- | signature | | + # +-----------+ +----------+ +-----------+ | + # | + # | + # sync committee + # period boundary + # ``` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_supply_sync_committee_from_past_update(spec, state): + # Advance the chain, so that a `LightClientUpdate` from the past is available + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2 - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + past_state = state.copy() + + # Start test + test = yield from setup_lc_sync_test(spec, state) + assert not spec.is_next_sync_committee_known(test.store) + + # Apply `LightClientUpdate` from the past, populating `store.next_sync_committee` + yield from emit_update( + test, spec, past_state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_advance_finality_without_sync_committee(spec, state): + # Start test + test = yield from setup_lc_sync_test(spec, state) + + # Initial `LightClientUpdate`, populating `store.next_sync_committee` + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Advance finality into next sync committee period, but omit `next_sync_committee` + transition_to(spec, state, compute_start_slot_at_next_sync_committee_period(spec, state)) + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) + justified_block = state_transition_with_full_block(spec, state, True, True) + justified_state = state.copy() + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Advance finality once more, with `next_sync_committee` still unknown + past_state = finalized_state + finalized_block = justified_block + finalized_state = justified_state + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 2, True, True) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + + # Apply `LightClientUpdate` without `finalized_header` nor `next_sync_committee` + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, None, with_next=False + ) + assert test.store.finalized_header.beacon.slot == past_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Apply `LightClientUpdate` with `finalized_header` but no `next_sync_committee` + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, with_next=False + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert not spec.is_next_sync_committee_known(test.store) + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Apply full `LightClientUpdate`, supplying `next_sync_committee` + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_light_client_sync_no_force_update(spec, state): + """Test that force update does not occur before timeout threshold is reached. + + This test verifies that even with a best_valid_update present, the light client + will not perform a force update until sufficient time (UPDATE_TIMEOUT slots) has passed + since the last finalized header. + + Test progression: + ``` + + + +-----------+ +----------+ +-----------+ +---------+ + | finalized | <-- (2 epochs) -- | attested | <-- | signature | <-- (N slots) -- | current | + +-----------+ +----------+ +-----------+ +---------+ + ^ | ^ + | | | + | V | + | best_valid_update | + | | + +------------------- (UPDATE_TIMEOUT) ------------------------------------------+ + + Delays: + * finalized to attested: 2 epochs + * attested to signature: 1 slot + * signature to current slot (N): UPDATE_TIMEOUT - (2 * SLOTS_PER_EPOCH + 1) slots + ``` + + Key points: + * best_valid_update created at signature block + * advance to just before timeout threshold + * verify force update does not occur + """ + test = yield from setup_lc_sync_test(spec, state) + + next_slots(spec, state, spec.SLOTS_PER_EPOCH - 1) + finalized_block = state_transition_with_full_block(spec, state, True, True) + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + + # Create initial update to set up store state + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block + ) + assert test.store.best_valid_update is None + + # Create a best_valid_update by emitting an update without a finalized_header + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block=None + ) + assert test.store.best_valid_update == update + + # Advance just short of timeout + next_slots(spec, state, spec.UPDATE_TIMEOUT - (2 * spec.SLOTS_PER_EPOCH + 1)) + + # Verify force update conditions + current_slot = state.slot + assert test.store.best_valid_update is not None + assert not (current_slot > test.store.finalized_header.beacon.slot + spec.UPDATE_TIMEOUT) + + # Try force update + yield from emit_force_update(test, spec, state) + # Store should remain unchanged since timeout wasn't reached + assert test.store.finalized_header.beacon.slot == finalized_state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +def run_lc_sync_test_upgraded_store_with_legacy_data(spec, phases, state, fork): + # Start test (Legacy bootstrap with an upgraded store) + test = yield from setup_lc_sync_test(spec, state, phases[fork], phases) + + # Initial `LightClientUpdate` (check that the upgraded store can process it) + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +@with_all_phases_from_to(ALTAIR, CAPELLA, other_phases=[CAPELLA]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_store_with_legacy_data(spec, phases, state): + yield from run_lc_sync_test_upgraded_store_with_legacy_data(spec, phases, state, CAPELLA) + + +@with_all_phases_from_to(ALTAIR, DENEB, other_phases=[CAPELLA, DENEB]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=DENEB) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_store_with_legacy_data(spec, phases, state): + yield from run_lc_sync_test_upgraded_store_with_legacy_data(spec, phases, state, DENEB) + + +@with_all_phases_from_to(ALTAIR, ELECTRA, other_phases=[CAPELLA, DENEB, ELECTRA]) +@spec_test +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_electra_store_with_legacy_data(spec, phases, state): + yield from run_lc_sync_test_upgraded_store_with_legacy_data(spec, phases, state, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py new file mode 100644 index 0000000000..e8075dc9b3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/light_client/test_update_ranking.py @@ -0,0 +1,150 @@ +from eth2spec.test.context import ( + spec_state_test, + with_light_client, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + next_slots_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.light_client import ( + create_update, +) +from eth2spec.test.helpers.state import ( + next_slots, +) + + +def create_test_update( + spec, test, with_next, with_finality, participation_rate, signature_slot=None +): + attested_state, attested_block, finalized_block = test + return create_update( + spec, + attested_state, + attested_block, + finalized_block, + with_next, + with_finality, + participation_rate, + signature_slot=signature_slot, + ) + + +@with_light_client +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_update_ranking(spec, state): + # Set up blocks and states: + # - `sig_finalized` / `sig_attested` --> Only signature in next sync committee period + # - `att_finalized` / `att_attested` --> Attested header also in next sync committee period + # - `fin_finalized` / `fin_attested` --> Finalized header also in next sync committee period + # - `lat_finalized` / `lat_attested` --> Like `fin`, but at a later `attested_header.beacon.slot` + next_slots( + spec, state, spec.compute_start_slot_at_epoch(spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 3) - 1 + ) + sig_finalized_block = state_transition_with_full_block(spec, state, True, True) + _, _, state = next_slots_with_attestations(spec, state, spec.SLOTS_PER_EPOCH - 1, True, True) + att_finalized_block = state_transition_with_full_block(spec, state, True, True) + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 2, True, True + ) + sig_attested_block = state_transition_with_full_block(spec, state, True, True) + sig_attested_state = state.copy() + att_attested_block = state_transition_with_full_block(spec, state, True, True) + att_attested_state = state.copy() + fin_finalized_block = att_attested_block + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + fin_attested_block = state_transition_with_full_block(spec, state, True, True) + fin_attested_state = state.copy() + lat_finalized_block = fin_finalized_block + lat_attested_block = state_transition_with_full_block(spec, state, True, True) + lat_attested_state = state.copy() + sig = (sig_attested_state, sig_attested_block, sig_finalized_block) + att = (att_attested_state, att_attested_block, att_finalized_block) + fin = (fin_attested_state, fin_attested_block, fin_finalized_block) + lat = (lat_attested_state, lat_attested_block, lat_finalized_block) + + # Create updates (in descending order of quality) + updates = [ + # Updates with sync committee finality + create_test_update(spec, fin, with_next=1, with_finality=1, participation_rate=1.0), + create_test_update(spec, lat, with_next=1, with_finality=1, participation_rate=1.0), + create_test_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.8), + create_test_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.8), + # Updates without sync committee finality + create_test_update(spec, att, with_next=1, with_finality=1, participation_rate=1.0), + create_test_update(spec, att, with_next=1, with_finality=1, participation_rate=0.8), + # Updates without indication of any finality + create_test_update(spec, att, with_next=1, with_finality=0, participation_rate=1.0), + create_test_update(spec, fin, with_next=1, with_finality=0, participation_rate=1.0), + create_test_update(spec, lat, with_next=1, with_finality=0, participation_rate=1.0), + create_test_update(spec, att, with_next=1, with_finality=0, participation_rate=0.8), + create_test_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.8), + create_test_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.8), + # Updates with sync committee finality but no `next_sync_committee` + create_test_update(spec, sig, with_next=0, with_finality=1, participation_rate=1.0), + create_test_update(spec, fin, with_next=0, with_finality=1, participation_rate=1.0), + create_test_update(spec, lat, with_next=0, with_finality=1, participation_rate=1.0), + create_test_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.8), + create_test_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.8), + create_test_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.8), + # Updates without sync committee finality and also no `next_sync_committee` + create_test_update(spec, att, with_next=0, with_finality=1, participation_rate=1.0), + create_test_update(spec, att, with_next=0, with_finality=1, participation_rate=0.8), + # Updates without indication of any finality nor `next_sync_committee` + create_test_update(spec, sig, with_next=0, with_finality=0, participation_rate=1.0), + create_test_update(spec, att, with_next=0, with_finality=0, participation_rate=1.0), + create_test_update(spec, fin, with_next=0, with_finality=0, participation_rate=1.0), + create_test_update(spec, lat, with_next=0, with_finality=0, participation_rate=1.0), + create_test_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.8), + create_test_update(spec, att, with_next=0, with_finality=0, participation_rate=0.8), + create_test_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.8), + create_test_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.8), + # Updates with low sync committee participation + create_test_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.4), + create_test_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.4), + create_test_update(spec, att, with_next=1, with_finality=1, participation_rate=0.4), + create_test_update(spec, att, with_next=1, with_finality=0, participation_rate=0.4), + create_test_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.4), + create_test_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.4), + create_test_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.4), + create_test_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.4), + create_test_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.4), + create_test_update(spec, att, with_next=0, with_finality=1, participation_rate=0.4), + create_test_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.4), + create_test_update(spec, att, with_next=0, with_finality=0, participation_rate=0.4), + create_test_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.4), + create_test_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.4), + # Updates with very low sync committee participation + create_test_update(spec, fin, with_next=1, with_finality=1, participation_rate=0.2), + create_test_update(spec, lat, with_next=1, with_finality=1, participation_rate=0.2), + create_test_update(spec, att, with_next=1, with_finality=1, participation_rate=0.2), + create_test_update(spec, att, with_next=1, with_finality=0, participation_rate=0.2), + create_test_update(spec, fin, with_next=1, with_finality=0, participation_rate=0.2), + create_test_update(spec, lat, with_next=1, with_finality=0, participation_rate=0.2), + create_test_update(spec, sig, with_next=0, with_finality=1, participation_rate=0.2), + create_test_update(spec, fin, with_next=0, with_finality=1, participation_rate=0.2), + create_test_update(spec, lat, with_next=0, with_finality=1, participation_rate=0.2), + create_test_update(spec, att, with_next=0, with_finality=1, participation_rate=0.2), + create_test_update(spec, sig, with_next=0, with_finality=0, participation_rate=0.2), + create_test_update(spec, att, with_next=0, with_finality=0, participation_rate=0.2), + create_test_update(spec, fin, with_next=0, with_finality=0, participation_rate=0.2), + create_test_update(spec, lat, with_next=0, with_finality=0, participation_rate=0.2), + # Test signature_slot tiebreaker: identical update but with later signature_slot + create_test_update( + spec, + lat, + with_next=0, + with_finality=0, + participation_rate=0.2, + signature_slot=lat_attested_state.slot + 2, + ), + ] + yield "updates", updates + + for i in range(len(updates) - 1): + assert spec.is_better_update(updates[i], updates[i + 1]) diff --git a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py index 2250101bdb..9523447565 100644 --- a/tests/core/pyspec/eth2spec/test/altair/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/altair/random/test_random.py @@ -4,19 +4,17 @@ See the README for that generator for more information. """ -from eth2spec.test.helpers.constants import ALTAIR from eth2spec.test.context import ( + always_bls, misc_balances_in_default_range_with_many_validators, - with_phases, - zero_activation_threshold, only_generator, -) -from eth2spec.test.context import ( - always_bls, + single_phase, spec_test, with_custom_state, - single_phase, + with_phases, + zero_activation_threshold, ) +from eth2spec.test.helpers.constants import ALTAIR from eth2spec.test.utils.randomized_block_tests import ( run_generated_randomized_test, ) @@ -26,7 +24,7 @@ @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -40,7 +38,53 @@ def test_randomized_0(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -52,7 +96,7 @@ def test_randomized_0(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -66,7 +110,53 @@ def test_randomized_1(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -78,7 +168,7 @@ def test_randomized_1(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -92,7 +182,53 @@ def test_randomized_2(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -104,7 +240,7 @@ def test_randomized_2(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -118,7 +254,53 @@ def test_randomized_3(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -130,7 +312,7 @@ def test_randomized_3(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -144,7 +326,53 @@ def test_randomized_4(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -156,7 +384,7 @@ def test_randomized_4(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -170,7 +398,53 @@ def test_randomized_5(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -182,7 +456,7 @@ def test_randomized_5(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -196,7 +470,53 @@ def test_randomized_6(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -208,7 +528,7 @@ def test_randomized_6(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -222,7 +542,53 @@ def test_randomized_7(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -234,7 +600,7 @@ def test_randomized_7(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -248,7 +614,53 @@ def test_randomized_8(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -260,7 +672,7 @@ def test_randomized_8(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -274,7 +686,53 @@ def test_randomized_9(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -286,7 +744,7 @@ def test_randomized_9(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -300,7 +758,53 @@ def test_randomized_10(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -312,7 +816,7 @@ def test_randomized_10(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -326,7 +830,53 @@ def test_randomized_11(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -338,7 +888,7 @@ def test_randomized_11(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -352,7 +902,53 @@ def test_randomized_12(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -364,7 +960,7 @@ def test_randomized_12(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -378,7 +974,53 @@ def test_randomized_13(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -390,7 +1032,7 @@ def test_randomized_13(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -404,7 +1046,53 @@ def test_randomized_14(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -416,7 +1104,7 @@ def test_randomized_14(spec, state): @with_phases([ALTAIR]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -430,7 +1118,53 @@ def test_randomized_15(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block_altair_with_cycling_sync_committee_participation - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block_altair_with_cycling_sync_committee_participation', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state_altair'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_altair_with_cycling_sync_committee_participation", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_altair", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py index 80ef91d0f0..c2c1699677 100644 --- a/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py +++ b/tests/core/pyspec/eth2spec/test/altair/rewards/test_inactivity_scores.py @@ -1,16 +1,17 @@ from random import Random +import eth2spec.test.helpers.rewards as rewards_helpers from eth2spec.test.context import ( - with_altair_and_later, - spec_test, + low_balances, + misc_balances, + single_phase, spec_state_test, + spec_test, + with_altair_and_later, with_custom_state, - single_phase, - low_balances, misc_balances, ) from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores from eth2spec.test.helpers.rewards import leaking -import eth2spec.test.helpers.rewards as rewards_helpers @with_altair_and_later @@ -63,7 +64,9 @@ def test_random_inactivity_scores_low_balances_1(spec, state): @with_altair_and_later -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase def test_full_random_misc_balances(spec, state): @@ -75,6 +78,7 @@ def test_full_random_misc_balances(spec, state): # Leaking variants # + @with_altair_and_later @spec_state_test @leaking() diff --git a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py index 6628917623..7be8ca714e 100644 --- a/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/altair/sanity/test_blocks.py @@ -1,84 +1,88 @@ from random import Random -from eth2spec.test.helpers.state import ( - state_transition_and_sign_block, - next_epoch, - set_full_participation_previous_epoch, +from eth2spec.test.context import ( + spec_state_test, + with_altair_and_later, ) from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, build_empty_block, + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores +from eth2spec.test.helpers.rewards import leaking +from eth2spec.test.helpers.state import ( + next_epoch, + set_full_participation_previous_epoch, + state_transition_and_sign_block, ) from eth2spec.test.helpers.sync_committee import ( compute_aggregate_sync_committee_signature, ) -from eth2spec.test.context import ( - with_altair_and_later, - spec_state_test, -) -from eth2spec.test.helpers.rewards import leaking -from eth2spec.test.helpers.inactivity_scores import randomize_inactivity_scores def run_sync_committee_sanity_test(spec, state, fraction_full=1.0, rng=Random(454545)): all_pubkeys = [v.pubkey for v in state.validators] committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - participants = rng.sample(committee, int(len(committee) * fraction_full)) + selected_indices = rng.sample(range(len(committee)), int(len(committee) * fraction_full)) + sync_committee_bits = [i in selected_indices for i in range(len(committee))] + participants = [ + validator_index for i, validator_index in enumerate(committee) if sync_committee_bits[i] + ] - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( - sync_committee_bits=[index in participants for index in committee], + sync_committee_bits=sync_committee_bits, sync_committee_signature=compute_aggregate_sync_committee_signature( spec, state, block.slot - 1, participants, - ) + ), ) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state @with_altair_and_later @spec_state_test -def test_full_sync_committee_committee(spec, state): +def test_sync_committee_committee__full(spec, state): next_epoch(spec, state) yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0) @with_altair_and_later @spec_state_test -def test_half_sync_committee_committee(spec, state): +def test_sync_committee_committee__half(spec, state): next_epoch(spec, state) yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(1212)) @with_altair_and_later @spec_state_test -def test_empty_sync_committee_committee(spec, state): +def test_sync_committee_committee__empty(spec, state): next_epoch(spec, state) yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0) @with_altair_and_later @spec_state_test -def test_full_sync_committee_committee_genesis(spec, state): +def test_sync_committee_committee_genesis__full(spec, state): yield from run_sync_committee_sanity_test(spec, state, fraction_full=1.0) @with_altair_and_later @spec_state_test -def test_half_sync_committee_committee_genesis(spec, state): +def test_sync_committee_committee_genesis__half(spec, state): yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.5, rng=Random(2323)) @with_altair_and_later @spec_state_test -def test_empty_sync_committee_committee_genesis(spec, state): +def test_sync_committee_committee_genesis__empty(spec, state): yield from run_sync_committee_sanity_test(spec, state, fraction_full=0.0) @@ -93,14 +97,14 @@ def test_inactivity_scores_leaking(spec, state): previous_inactivity_scores = state.inactivity_scores.copy() - yield 'pre', state + yield "pre", state # Block transition to next epoch block = build_empty_block(spec, state, slot=state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state # No participation during a leak so all scores should increase for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): @@ -119,7 +123,7 @@ def test_inactivity_scores_full_participation_leaking(spec, state): previous_inactivity_scores = state.inactivity_scores.copy() - yield 'pre', state + yield "pre", state # Block transition to next epoch block = build_empty_block(spec, state, slot=state.slot + spec.SLOTS_PER_EPOCH) @@ -127,9 +131,9 @@ def test_inactivity_scores_full_participation_leaking(spec, state): assert spec.is_in_inactivity_leak(state) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state - # Full particiaption during a leak so all scores should decrease by 1 + # Full participation during a leak so all scores should decrease by 1 for pre, post in zip(previous_inactivity_scores, state.inactivity_scores): assert post == pre - 1 diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py new file mode 100644 index 0000000000..e5c649660a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_activations_and_exits.py @@ -0,0 +1,249 @@ +import random + +from eth2spec.test.context import ( + ALTAIR, + ForkMeta, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) +from eth2spec.test.helpers.random import ( + exit_random_validators, + set_some_activations, + set_some_new_deposits, +) + +# +# Exit +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@with_presets( + [MINIMAL], + reason="only test with enough validators such that at least one exited index is not in sync committee", +) +def test_transition_with_one_fourth_exiting_validators_exit_post_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + 1/4 validators initiated voluntary exit before the fork, + and are exiting but still active *after* the fork transition. + """ + exited_indices = exit_random_validators( + spec, + state, + rng=random.Random(5566), + fraction=0.25, + exit_epoch=10, + from_epoch=spec.get_current_epoch(state), + ) + + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch < validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # ensure that some of the current sync committee members are exiting + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + assert any(set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + assert any(set(exited_pubkeys).difference(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + # check state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_one_fourth_exiting_validators_exit_at_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + 1/4 validators initiated voluntary exit before the fork, + and being exited and inactive *right after* the fork transition. + """ + exited_indices = exit_random_validators( + spec, + state, + rng=random.Random(5566), + fraction=0.25, + exit_epoch=fork_epoch, + from_epoch=spec.get_current_epoch(state), + ) + + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert len(exited_indices) > 0 + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert fork_epoch == validator.exit_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + for index in exited_indices: + validator = state.validators[index] + assert not validator.slashed + assert not post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + exited_pubkeys = [state.validators[index].pubkey for index in exited_indices] + some_sync_committee_exited = any( + set(exited_pubkeys).intersection(list(state.current_sync_committee.pubkeys)) + ) + if post_spec.fork == ALTAIR: + # in Altair fork, the sync committee members would be set with only active validators + assert not some_sync_committee_exited + else: + assert some_sync_committee_exited + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state + + +# +# Activation +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_non_empty_activation_queue( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create some deposits before the transition + """ + transition_until_fork(spec, state, fork_epoch) + + deposited_indices = set_some_new_deposits(spec, state, rng=random.Random(5566)) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(deposited_indices) > 0 + for validator_index in deposited_indices: + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_activation_at_fork_epoch( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create some deposits before the transition + """ + transition_until_fork(spec, state, fork_epoch) + + selected_indices = set_some_activations( + spec, state, rng=random.Random(5566), activation_epoch=fork_epoch + ) + + assert spec.get_current_epoch(state) < fork_epoch + assert len(selected_indices) > 0 + for validator_index in selected_indices: + validator = state.validators[validator_index] + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert validator.activation_epoch == fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + # now they are active + for validator_index in selected_indices: + validator = state.validators[validator_index] + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py new file mode 100644 index 0000000000..f90ce0d103 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_leaking.py @@ -0,0 +1,82 @@ +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=7) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_leaking_pre_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts before the fork transition in this case. + """ + transition_until_fork(spec, state, fork_epoch) + + assert spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=6) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_leaking_at_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Leaking starts at epoch 6 (MIN_EPOCHS_TO_INACTIVITY_PENALTY + 2). + The leaking starts at the fork transition in this case. + """ + transition_until_fork(spec, state, fork_epoch) + + assert not spec.is_in_inactivity_leak(state) + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post transition state + assert spec.is_in_inactivity_leak(state) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py new file mode 100644 index 0000000000..b630a5d32e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_operations.py @@ -0,0 +1,239 @@ +from eth2spec.test.context import ( + always_bls, + ForkMeta, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + OperationType, + run_transition_with_operation, +) + +# +# PROPOSER_SLASHING +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_proposer_slashing_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.PROPOSER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_proposer_slashing_right_before_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create an attester slashing right *before* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.PROPOSER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# ATTESTER_SLASHING +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_attester_slashing_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.ATTESTER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_attester_slashing_right_before_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create an attester slashing right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.ATTESTER_SLASHING, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# DEPOSIT +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_deposit_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a deposit right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.DEPOSIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_deposit_right_before_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a deposit right *before* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.DEPOSIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +# +# VOLUNTARY_EXIT +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@with_presets([MINIMAL], reason="too slow") +def test_transition_with_voluntary_exit_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a voluntary exit right *after* the transition. + fork_epoch=66 because minimal preset `SHARD_COMMITTEE_PERIOD` is 64 epochs. + """ + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.VOLUNTARY_EXIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@with_presets([MINIMAL], reason="too slow") +def test_transition_with_voluntary_exit_right_before_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a voluntary exit right *before* the transition. + fork_epoch=66 because minimal preset `SHARD_COMMITTEE_PERIOD` is 64 epochs. + """ + # Fast forward to the future epoch so that validator can do voluntary exit + state.slot = spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.VOLUNTARY_EXIT, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py new file mode 100644 index 0000000000..697573e699 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_slashing.py @@ -0,0 +1,81 @@ +import random + +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) +from eth2spec.test.helpers.random import ( + slash_random_validators, +) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=1) + for pre, post in ALL_PRE_POST_FORKS + ] +) +@with_presets( + [MINIMAL], + reason="only test with enough validators such that at least one exited index is not in sync committee", +) +def test_transition_with_one_fourth_slashed_active_validators_pre_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + 1/4 validators are slashed but still active at the fork transition. + """ + # slash 1/4 validators + slashed_indices = slash_random_validators(spec, state, rng=random.Random(5566), fraction=0.25) + assert len(slashed_indices) > 0 + + # check if some validators are slashed but still active + for validator_index in slashed_indices: + validator = state.validators[validator_index] + assert validator.slashed + assert spec.is_active_validator(validator, spec.get_current_epoch(state)) + assert not spec.is_in_inactivity_leak(state) + + transition_until_fork(spec, state, fork_epoch) + + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) + + # ensure that some of the current sync committee members are slashed + slashed_pubkeys = [state.validators[index].pubkey for index in slashed_indices] + assert any(set(slashed_pubkeys).intersection(list(state.current_sync_committee.pubkeys))) + assert any(set(slashed_pubkeys).difference(list(state.current_sync_committee.pubkeys))) + + # continue regular state transition with new spec into next epoch + # since the proposer might have been slashed, here we only create blocks with non-slashed proposers + blocks = [] + transition_to_next_epoch_and_append_blocks( + post_spec, + state, + post_tag, + blocks, + only_last_block=True, + ignoring_proposers=slashed_indices, + ) + + # check post state + for validator in state.validators: + assert post_spec.is_active_validator(validator, post_spec.get_current_epoch(state)) + assert not post_spec.is_in_inactivity_leak(state) + + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py index 62740df4ed..4c5b17e9a7 100644 --- a/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py +++ b/tests/core/pyspec/eth2spec/test/altair/transition/test_transition.py @@ -1,86 +1,64 @@ import random -from eth2spec.test.context import fork_transition_test -from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.state import state_transition_and_sign_block, next_slot, next_epoch_via_signed_block -from eth2spec.test.helpers.block import build_empty_block_for_next_slot, build_empty_block, sign_block -from eth2spec.test.helpers.attestations import next_slots_with_attestations - - -def _state_transition_and_sign_block_at_slot(spec, state): - """ - Cribbed from ``transition_unsigned_block`` helper - where the early parts of the state transition have already - been applied to ``state``. - - Used to produce a block during an irregular state transition. - """ - block = build_empty_block(spec, state) - - assert state.latest_block_header.slot < block.slot - assert state.slot == block.slot - spec.process_block(state, block) - block.state_root = state.hash_tree_root() - return sign_block(spec, state, block) - - -def _all_blocks(_): - return True - - -def _skip_slots(*slots): - """ - Skip making a block if its slot is - passed as an argument to this filter - """ - def f(state_at_prior_slot): - return state_at_prior_slot.slot + 1 not in slots - return f - - -def _no_blocks(_): - return False - - -def _only_at(slot): - """ - Only produce a block if its slot is ``slot``. - """ - def f(state_at_prior_slot): - return state_at_prior_slot.slot + 1 == slot - return f - - -def _state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks): - assert state.slot < to_slot - while state.slot < to_slot: - should_make_block = block_filter(state) - if should_make_block: - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - yield signed_block - else: - next_slot(spec, state) - -def _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=True): - spec.process_slots(state, state.slot + 1) +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.attestations import next_slots_with_attestations +from eth2spec.test.helpers.constants import ( + ALL_PRE_POST_FORKS, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + no_blocks, + only_at, + skip_slots, + state_transition_across_slots, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) +from eth2spec.test.helpers.random import ( + randomize_state, +) +from eth2spec.test.helpers.state import ( + next_epoch_via_signed_block, +) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_simple_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert spec.get_current_epoch(state) < fork_epoch - assert state.slot % spec.SLOTS_PER_EPOCH == 0 - assert spec.get_current_epoch(state) == fork_epoch + yield "pre", state - state = post_spec.upgrade_to_altair(state) + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) - assert state.fork.epoch == fork_epoch - assert state.fork.previous_version == post_spec.config.GENESIS_FORK_VERSION - assert state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) - if with_block: - return state, _state_transition_and_sign_block_at_slot(post_spec, state) - else: - return state, None + yield "blocks", blocks + yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -93,21 +71,14 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag # regular state transition until fork: to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 blocks = [] - blocks.extend([ - pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) - ]) + blocks.extend([pre_tag(block) for block in state_transition_across_slots(spec, state, to_slot)]) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 @@ -120,7 +91,50 @@ def test_normal_transition(state, fork_epoch, spec, post_spec, pre_tag, post_tag yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=8) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_randomized_state(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + randomize_state(spec, state) + + transition_until_fork(spec, state, fork_epoch) + + # check pre state + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + # since there are slashed validators, set with_block=False here + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) + slashed_indices = [ + index for index, validator in enumerate(state.validators) if validator.slashed + ] + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, + state, + post_tag, + blocks, + only_last_block=True, + ignoring_proposers=slashed_indices, + ) + + yield "blocks", blocks + yield "post", state + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -134,35 +148,37 @@ def test_transition_missing_first_post_block(state, fork_epoch, spec, post_spec, # regular state transition until fork: to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 blocks = [] - blocks.extend([ - pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) - ]) + blocks.extend([pre_tag(block) for block in state_transition_across_slots(spec, state, to_slot)]) # irregular state transition to handle fork: - state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 slots_with_blocks = [block.message.slot for block in blocks] assert len(set(slots_with_blocks)) == len(slots_with_blocks) - expected_slots = set(range(1, state.slot + 1)).difference(set([fork_epoch * spec.SLOTS_PER_EPOCH])) + expected_slots = set(range(1, state.slot + 1)).difference( + set([fork_epoch * spec.SLOTS_PER_EPOCH]) + ) assert expected_slots == set(slots_with_blocks) yield "blocks", blocks yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_spec, pre_tag, post_tag): +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_missing_last_pre_fork_block( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, producing blocks for every slot along the way except for the last block @@ -176,21 +192,21 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp last_slot_of_pre_fork = fork_epoch * spec.SLOTS_PER_EPOCH - 1 to_slot = last_slot_of_pre_fork blocks = [] - blocks.extend([ - pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot, block_filter=_skip_slots(last_slot_of_pre_fork)) - ]) + blocks.extend( + [ + pre_tag(block) + for block in state_transition_across_slots( + spec, state, to_slot, block_filter=skip_slots(last_slot_of_pre_fork) + ) + ] + ) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch - to_slot = post_spec.SLOTS_PER_EPOCH + state.slot - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot) - ]) + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 @@ -204,7 +220,12 @@ def test_transition_missing_last_pre_fork_block(state, fork_epoch, spec, post_sp yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, @@ -219,21 +240,27 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr last_slot_of_pre_fork = fork_epoch * spec.SLOTS_PER_EPOCH - 1 to_slot = last_slot_of_pre_fork blocks = [] - blocks.extend([ - pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot, block_filter=_no_blocks) - ]) + blocks.extend( + [ + pre_tag(block) + for block in state_transition_across_slots(spec, state, to_slot, block_filter=no_blocks) + ] + ) # irregular state transition to handle fork: - state, _ = _do_altair_fork(state, spec, post_spec, fork_epoch, with_block=False) + state, _ = do_fork(state, spec, post_spec, fork_epoch, with_block=False) # continue regular state transition with new spec into next epoch to_slot = post_spec.SLOTS_PER_EPOCH + state.slot last_slot = (fork_epoch + 1) * post_spec.SLOTS_PER_EPOCH - blocks.extend([ - post_tag(block) for block in - _state_transition_across_slots(post_spec, state, to_slot, block_filter=_only_at(last_slot)) - ]) + blocks.extend( + [ + post_tag(block) + for block in state_transition_across_slots( + post_spec, state, to_slot, block_filter=only_at(last_slot) + ) + ] + ) assert state.slot % post_spec.SLOTS_PER_EPOCH == 0 assert post_spec.get_current_epoch(state) == fork_epoch + 1 @@ -246,14 +273,16 @@ def test_transition_only_blocks_post_fork(state, fork_epoch, spec, post_spec, pr yield "post", state -def _run_transition_test_with_attestations(state, - fork_epoch, - spec, - post_spec, - pre_tag, - post_tag, - participation_fn=None, - expect_finality=True): +def _run_transition_test_with_attestations( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + participation_fn=None, + expect_finality=True, +): yield "pre", state current_epoch = spec.get_current_epoch(state) @@ -292,7 +321,7 @@ def _run_transition_test_with_attestations(state, assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0 # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition with new spec into next epoch @@ -320,27 +349,45 @@ def _run_transition_test_with_attestations(state, assert len(blocks) == (fork_epoch + 3) * post_spec.SLOTS_PER_EPOCH + 1 assert len(blocks) == len(set(blocks)) - blocks_without_attestations = [block for block in blocks if len(block.message.body.attestations) == 0] + blocks_without_attestations = [ + block for block in blocks if len(block.message.body.attestations) == 0 + ] assert len(blocks_without_attestations) == 2 slots_without_attestations = [b.message.slot for b in blocks_without_attestations] - assert set(slots_without_attestations) == set([spec.SLOTS_PER_EPOCH, fork_epoch * spec.SLOTS_PER_EPOCH]) + assert set(slots_without_attestations) == set( + [spec.SLOTS_PER_EPOCH, fork_epoch * spec.SLOTS_PER_EPOCH] + ) yield "blocks", blocks yield "post", state -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) + for pre, post in ALL_PRE_POST_FORKS + ] +) def test_transition_with_finality(state, fork_epoch, spec, post_spec, pre_tag, post_tag): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, including attestations so as to produce finality through the fork boundary. """ - yield from _run_transition_test_with_attestations(state, fork_epoch, spec, post_spec, pre_tag, post_tag) + yield from _run_transition_test_with_attestations( + state, fork_epoch, spec, post_spec, pre_tag, post_tag + ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) -def test_transition_with_random_three_quarters_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_random_three_quarters_participation( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): """ Transition from the initial ``state`` to the epoch after the ``fork_epoch``, including attestations so as to produce finality through the fork boundary. @@ -353,21 +400,22 @@ def _drop_random_quarter(_slot, _index, indices): assert committee_len >= 4 filter_len = committee_len // 4 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) yield from _run_transition_test_with_attestations( - state, - fork_epoch, - spec, - post_spec, - pre_tag, - post_tag, - participation_fn=_drop_random_quarter + state, fork_epoch, spec, post_spec, pre_tag, post_tag, participation_fn=_drop_random_quarter ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=3) -def test_transition_with_random_half_participation(state, fork_epoch, spec, post_spec, pre_tag, post_tag): +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_random_half_participation( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): rng = random.Random(2020) def _drop_random_half(_slot, _index, indices): @@ -376,7 +424,7 @@ def _drop_random_half(_slot, _index, indices): assert committee_len >= 2 filter_len = committee_len // 2 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) yield from _run_transition_test_with_attestations( state, @@ -386,12 +434,19 @@ def _drop_random_half(_slot, _index, indices): pre_tag, post_tag, participation_fn=_drop_random_half, - expect_finality=False + expect_finality=False, ) -@fork_transition_test(PHASE0, ALTAIR, fork_epoch=2) -def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spec, post_spec, pre_tag, post_tag): +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=3) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_transition_with_no_attestations_until_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): """ Transition from the initial ``state`` to the ``fork_epoch`` with no attestations, then transition forward with enough attestations to finalize the fork epoch. @@ -403,13 +458,10 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe # regular state transition until fork: to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 blocks = [] - blocks.extend([ - pre_tag(block) for block in - _state_transition_across_slots(spec, state, to_slot) - ]) + blocks.extend([pre_tag(block) for block in state_transition_across_slots(spec, state, to_slot)]) # irregular state transition to handle fork: - state, block = _do_altair_fork(state, spec, post_spec, fork_epoch) + state, block = do_fork(state, spec, post_spec, fork_epoch) blocks.append(post_tag(block)) # continue regular state transition but add attestations @@ -434,3 +486,44 @@ def test_transition_with_no_attestations_until_after_fork(state, fork_epoch, spe yield "blocks", blocks yield "post", state + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in ALL_PRE_POST_FORKS + ] +) +def test_non_empty_historical_roots(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Test with non-empty pre-state `state.historical_roots`. + + Since Capella froze `historical_roots`, Capella spec doesn't invoke `process_historical_roots_update` anymore. + Therefore, we need to fill in `historical_roots` with non-empty value. + """ + # fill in historical_roots with non-empty values + pre_historical_roots = [b"\x56" * 32] + state.historical_roots = pre_historical_roots + + transition_until_fork(spec, state, fork_epoch) + # check pre state + assert spec.get_current_epoch(state) < fork_epoch + assert len(state.historical_roots) > 0 + + yield "pre", state + + # irregular state transition to handle fork: + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state + + assert len(state.historical_roots) > 0 + assert state.historical_roots == pre_historical_roots diff --git a/tests/core/pyspec/eth2spec/test/custody_game/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/custody_game/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/altair/unittests/light_client/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py new file mode 100644 index 0000000000..cef9f4177d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/light_client/test_sync_protocol.py @@ -0,0 +1,178 @@ +from copy import deepcopy + +from eth2spec.test.context import ( + spec_state_test_with_matching_config, + with_light_client, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.light_client import ( + create_update, +) +from eth2spec.test.helpers.state import ( + next_slots, +) + + +def setup_test(spec, state): + trusted_block = spec.SignedBeaconBlock() + trusted_block.message.state_root = state.hash_tree_root() + trusted_block_root = trusted_block.message.hash_tree_root() + bootstrap = spec.create_light_client_bootstrap(state, trusted_block) + store = spec.initialize_light_client_store(trusted_block_root, bootstrap) + store.next_sync_committee = state.next_sync_committee + + return (trusted_block, store) + + +@with_light_client +@spec_state_test_with_matching_config +def test_process_light_client_update_not_timeout(spec, state): + genesis_block, store = setup_test(spec, state) + + # Block at slot 1 doesn't increase sync committee period, so it won't force update store.finalized_header + attested_block = state_transition_with_full_block(spec, state, False, False) + signature_slot = state.slot + 1 + + # Ensure that finality checkpoint is genesis + assert state.finalized_checkpoint.epoch == 0 + + update = create_update( + spec, + attested_state=state, + attested_block=attested_block, + finalized_block=genesis_block, + with_next=False, + with_finality=False, + participation_rate=1.0, + ) + + pre_store = deepcopy(store) + + spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root) + + assert store.finalized_header == pre_store.finalized_header + assert store.best_valid_update == update + assert store.optimistic_header == update.attested_header + assert store.current_max_active_participants > 0 + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_process_light_client_update_at_period_boundary(spec, state): + genesis_block, store = setup_test(spec, state) + + # Forward to slot before next sync committee period so that next block is final one in period + next_slots(spec, state, spec.UPDATE_TIMEOUT - 2) + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.beacon.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) + assert store_period == update_period + + attested_block = state_transition_with_full_block(spec, state, False, False) + signature_slot = state.slot + 1 + + update = create_update( + spec, + attested_state=state, + attested_block=attested_block, + finalized_block=genesis_block, + with_next=False, + with_finality=False, + participation_rate=1.0, + ) + + pre_store = deepcopy(store) + + spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root) + + assert store.finalized_header == pre_store.finalized_header + assert store.best_valid_update == update + assert store.optimistic_header == update.attested_header + assert store.current_max_active_participants > 0 + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_process_light_client_update_timeout(spec, state): + genesis_block, store = setup_test(spec, state) + + # Forward to next sync committee period + next_slots(spec, state, spec.UPDATE_TIMEOUT) + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.beacon.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) + assert store_period + 1 == update_period + + attested_block = state_transition_with_full_block(spec, state, False, False) + signature_slot = state.slot + 1 + + update = create_update( + spec, + attested_state=state, + attested_block=attested_block, + finalized_block=genesis_block, + with_next=True, + with_finality=False, + participation_rate=1.0, + ) + + pre_store = deepcopy(store) + + spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root) + + assert store.finalized_header == pre_store.finalized_header + assert store.best_valid_update == update + assert store.optimistic_header == update.attested_header + assert store.current_max_active_participants > 0 + + +@with_light_client +@spec_state_test_with_matching_config +@with_presets([MINIMAL], reason="too slow") +def test_process_light_client_update_finality_updated(spec, state): + _, store = setup_test(spec, state) + + # Change finality + blocks = [] + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2) + for epoch in range(3): + prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, True, True) + blocks += new_blocks + # Ensure that finality checkpoint has changed + assert state.finalized_checkpoint.epoch == 3 + # Ensure that it's same period + store_period = spec.compute_sync_committee_period_at_slot(store.optimistic_header.beacon.slot) + update_period = spec.compute_sync_committee_period_at_slot(state.slot) + assert store_period == update_period + + attested_block = blocks[-1] + signature_slot = state.slot + 1 + + # Updated finality + finalized_block = blocks[spec.SLOTS_PER_EPOCH - 1] + assert finalized_block.message.slot == spec.compute_start_slot_at_epoch( + state.finalized_checkpoint.epoch + ) + assert finalized_block.message.hash_tree_root() == state.finalized_checkpoint.root + + update = create_update( + spec, + attested_state=state, + attested_block=attested_block, + finalized_block=finalized_block, + with_next=False, + with_finality=True, + participation_rate=1.0, + ) + + spec.process_light_client_update(store, update, signature_slot, state.genesis_validators_root) + + assert store.finalized_header == update.finalized_header + assert store.best_valid_update is None + assert store.optimistic_header == update.attested_header + assert store.current_max_active_participants > 0 diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py b/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py index 37d50611dc..2e553b33a8 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/networking/test_networking.py @@ -1,6 +1,6 @@ from eth2spec.test.context import ( - with_altair_and_later, spec_state_test, + with_altair_and_later, ) from eth2spec.test.helpers.state import ( transition_to, @@ -14,16 +14,15 @@ def test_get_sync_subcommittee_pubkeys_current_sync_committee(state, spec): transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) - assert ( - spec.compute_sync_committee_period(spec.get_current_epoch(state)) - == spec.compute_sync_committee_period(next_slot_epoch) - ) + assert spec.compute_sync_committee_period( + spec.get_current_epoch(state) + ) == spec.compute_sync_committee_period(next_slot_epoch) sync_committee = state.current_sync_committee sync_subcommittee_size = spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT subcommittee_index = 1 i = subcommittee_index * sync_subcommittee_size - expect = sync_committee.pubkeys[i:i + sync_subcommittee_size] + expect = sync_committee.pubkeys[i : i + sync_subcommittee_size] assert spec.get_sync_subcommittee_pubkeys(state, subcommittee_index) == expect @@ -34,14 +33,13 @@ def test_get_sync_subcommittee_pubkeys_next_sync_committee(state, spec): transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1) next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) - assert ( - spec.compute_sync_committee_period(spec.get_current_epoch(state)) - != spec.compute_sync_committee_period(next_slot_epoch) - ) + assert spec.compute_sync_committee_period( + spec.get_current_epoch(state) + ) != spec.compute_sync_committee_period(next_slot_epoch) sync_committee = state.next_sync_committee sync_subcommittee_size = spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT subcommittee_index = 1 i = subcommittee_index * sync_subcommittee_size - expect = sync_committee.pubkeys[i:i + sync_subcommittee_size] + expect = sync_committee.pubkeys[i : i + sync_subcommittee_size] assert spec.get_sync_subcommittee_pubkeys(state, subcommittee_index) == expect diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_invariants.py index 4443f97e0b..91be0a0a61 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_invariants.py @@ -1,11 +1,10 @@ from eth2spec.test.context import ( spec_state_test, - with_phases, + with_altair_and_later, ) -from eth2spec.test.helpers.constants import ALTAIR -@with_phases([ALTAIR]) +@with_altair_and_later @spec_state_test def test_weight_denominator(spec, state): assert ( @@ -17,7 +16,7 @@ def test_weight_denominator(spec, state): ) == spec.WEIGHT_DENOMINATOR -@with_phases([ALTAIR]) +@with_altair_and_later @spec_state_test def test_inactivity_score(spec, state): assert spec.config.INACTIVITY_SCORE_BIAS <= spec.config.INACTIVITY_SCORE_RECOVERY_RATE diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_override.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_override.py index f1503c39f1..7c2e999cf5 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_override.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/test_config_override.py @@ -1,19 +1,93 @@ -from eth2spec.test.context import spec_configured_state_test, with_phases -from eth2spec.test.helpers.constants import ALTAIR +from eth2spec.test.context import ( + spec_configured_state_test, + spec_state_test_with_matching_config, + spec_test, + with_all_phases, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_state, +) +from eth2spec.test.helpers.constants import ( + ALL_PHASES, + ALTAIR, + BELLATRIX, + PHASE0, +) +from eth2spec.test.helpers.forks import is_post_fork @with_phases([ALTAIR]) -@spec_configured_state_test({ - 'GENESIS_FORK_VERSION': '0x12345678', - 'ALTAIR_FORK_VERSION': '0x11111111', - 'ALTAIR_FORK_EPOCH': 4 -}) +@spec_configured_state_test( + { + "GENESIS_FORK_VERSION": "0x12345678", + "ALTAIR_FORK_VERSION": "0x11111111", + "ALTAIR_FORK_EPOCH": 4, + } +) def test_config_override(spec, state): assert spec.config.ALTAIR_FORK_EPOCH == 4 - assert spec.config.GENESIS_FORK_VERSION != spec.Version('0x00000000') - assert spec.config.GENESIS_FORK_VERSION == spec.Version('0x12345678') - assert spec.config.ALTAIR_FORK_VERSION == spec.Version('0x11111111') - assert state.fork.current_version == spec.Version('0x11111111') + assert spec.config.GENESIS_FORK_VERSION != spec.Version("0x00000000") + assert spec.config.GENESIS_FORK_VERSION == spec.Version("0x12345678") + assert spec.config.ALTAIR_FORK_VERSION == spec.Version("0x11111111") + assert state.fork.current_version == spec.Version("0x11111111") # TODO: it would be nice if the create_genesis_state actually outputs a state # for the fork with a slot that matches at least the fork boundary. # assert spec.get_current_epoch(state) >= 4 + + +@with_all_phases +@spec_state_test_with_matching_config +def test_config_override_matching_fork_epochs(spec, state): + # Fork schedule must be consistent with state fork + epoch = spec.get_current_epoch(state) + if is_post_fork(spec.fork, ALTAIR): + assert state.fork.current_version == spec.compute_fork_version(epoch) + else: + assert state.fork.current_version == spec.config.GENESIS_FORK_VERSION + + # Identify state fork + state_fork = None + for fork in [fork for fork in ALL_PHASES if is_post_fork(spec.fork, fork)]: + if fork == PHASE0: + fork_version_field = "GENESIS_FORK_VERSION" + else: + fork_version_field = fork.upper() + "_FORK_VERSION" + if state.fork.current_version == getattr(spec.config, fork_version_field): + state_fork = fork + break + assert state_fork is not None + + # Check that all prior forks have already been triggered + for fork in [fork for fork in ALL_PHASES if is_post_fork(state_fork, fork)]: + if fork == PHASE0: + continue + fork_epoch_field = fork.upper() + "_FORK_EPOCH" + assert getattr(spec.config, fork_epoch_field) <= epoch + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_config_overrides( + { + "ALTAIR_FORK_VERSION": "0x11111111", + "BELLATRIX_FORK_EPOCH": 4, + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=BELLATRIX) +def test_config_override_across_phases(spec, phases, state): + assert state.fork.current_version == spec.config.ALTAIR_FORK_VERSION + + assert spec.config.ALTAIR_FORK_VERSION == spec.Version("0x11111111") + assert spec.config.ALTAIR_FORK_EPOCH == 0 + assert not hasattr(spec.config, "BELLATRIX_FORK_EPOCH") + + assert phases[ALTAIR].config.ALTAIR_FORK_VERSION == spec.Version("0x11111111") + assert phases[ALTAIR].config.ALTAIR_FORK_EPOCH == 0 + assert not hasattr(phases[ALTAIR].config, "BELLATRIX_FORK_EPOCH") + + assert phases[ALTAIR].config.ALTAIR_FORK_VERSION == spec.Version("0x11111111") + assert phases[BELLATRIX].config.ALTAIR_FORK_EPOCH == 0 + assert phases[BELLATRIX].config.BELLATRIX_FORK_EPOCH == 4 diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_helpers.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_helpers.py deleted file mode 100644 index c837f06c32..0000000000 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_helpers.py +++ /dev/null @@ -1,35 +0,0 @@ -from eth2spec.test.context import ( - spec_state_test, - with_phases, -) -from eth2spec.test.helpers.constants import ALTAIR -from eth2spec.test.helpers.merkle import build_proof - - -@with_phases([ALTAIR]) -@spec_state_test -def test_next_sync_committee_tree(spec, state): - state.next_sync_committee: object = spec.SyncCommittee( - pubkeys=[state.validators[i]for i in range(spec.SYNC_COMMITTEE_SIZE)] - ) - next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) - assert spec.is_valid_merkle_branch( - leaf=state.next_sync_committee.hash_tree_root(), - branch=next_sync_committee_branch, - depth=spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX), - index=spec.get_subtree_index(spec.NEXT_SYNC_COMMITTEE_INDEX), - root=state.hash_tree_root(), - ) - - -@with_phases([ALTAIR]) -@spec_state_test -def test_finality_root_tree(spec, state): - finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX) - assert spec.is_valid_merkle_branch( - leaf=state.finalized_checkpoint.root, - branch=finality_branch, - depth=spec.floorlog2(spec.FINALIZED_ROOT_INDEX), - index=spec.get_subtree_index(spec.FINALIZED_ROOT_INDEX), - root=state.hash_tree_root(), - ) diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py b/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py deleted file mode 100644 index c69957de53..0000000000 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/test_sync_protocol.py +++ /dev/null @@ -1,221 +0,0 @@ -from eth2spec.test.context import ( - spec_state_test, - with_presets, - with_phases, -) -from eth2spec.test.helpers.attestations import next_epoch_with_attestations -from eth2spec.test.helpers.block import ( - build_empty_block, - build_empty_block_for_next_slot, -) -from eth2spec.test.helpers.constants import ( - ALTAIR, - MINIMAL, -) -from eth2spec.test.helpers.state import ( - next_slots, - state_transition_and_sign_block, -) -from eth2spec.test.helpers.sync_committee import ( - compute_aggregate_sync_committee_signature, -) -from eth2spec.test.helpers.merkle import build_proof - - -@with_phases([ALTAIR]) -@spec_state_test -def test_process_light_client_update_not_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) - - # Block at slot 1 doesn't increase sync committee period, so it won't update snapshot - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - block_header = spec.BeaconBlockHeader( - slot=signed_block.message.slot, - proposer_index=signed_block.message.proposer_index, - parent_root=signed_block.message.parent_root, - state_root=signed_block.message.state_root, - body_root=signed_block.message.body.hash_tree_root(), - ) - # Sync committee signing the header - all_pubkeys = [v.pubkey for v in state.validators] - committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - sync_committee_bits = [True] * len(committee) - sync_committee_signature = compute_aggregate_sync_committee_signature( - spec, - state, - block_header.slot, - committee, - ) - next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] - - # Ensure that finality checkpoint is genesis - assert state.finalized_checkpoint.epoch == 0 - # Finality is unchanged - finality_header = spec.BeaconBlockHeader() - finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] - - update = spec.LightClientUpdate( - header=block_header, - next_sync_committee=state.next_sync_committee, - next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, - finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, - fork_version=state.fork.current_version, - ) - - spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - - assert len(store.valid_updates) == 1 - assert store.valid_updates.pop() == update - assert store.snapshot == pre_snapshot - - -@with_phases([ALTAIR]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -def test_process_light_client_update_timeout(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) - - # Forward to next sync committee period - next_slots(spec, state, spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD)) - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert snapshot_period + 1 == update_period - - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - block_header = spec.BeaconBlockHeader( - slot=signed_block.message.slot, - proposer_index=signed_block.message.proposer_index, - parent_root=signed_block.message.parent_root, - state_root=signed_block.message.state_root, - body_root=signed_block.message.body.hash_tree_root(), - ) - - # Sync committee signing the finalized_block_header - all_pubkeys = [v.pubkey for v in state.validators] - committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - sync_committee_bits = [True] * len(committee) - sync_committee_signature = compute_aggregate_sync_committee_signature( - spec, - state, - block_header.slot, - committee, - block_root=spec.Root(block_header.hash_tree_root()), - ) - - # Sync committee is updated - next_sync_committee_branch = build_proof(state.get_backing(), spec.NEXT_SYNC_COMMITTEE_INDEX) - # Finality is unchanged - finality_header = spec.BeaconBlockHeader() - finality_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.FINALIZED_ROOT_INDEX))] - - update = spec.LightClientUpdate( - header=block_header, - next_sync_committee=state.next_sync_committee, - next_sync_committee_branch=next_sync_committee_branch, - finality_header=finality_header, - finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, - fork_version=state.fork.current_version, - ) - - spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header - - -@with_phases([ALTAIR]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -def test_process_light_client_update_finality_updated(spec, state): - pre_snapshot = spec.LightClientSnapshot( - header=spec.BeaconBlockHeader(), - current_sync_committee=state.current_sync_committee, - next_sync_committee=state.next_sync_committee, - ) - store = spec.LightClientStore( - snapshot=pre_snapshot, - valid_updates=set(), - ) - - # Change finality - blocks = [] - next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2) - for epoch in range(3): - prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, True, True) - blocks += new_blocks - # Ensure that finality checkpoint has changed - assert state.finalized_checkpoint.epoch == 3 - # Ensure that it's same period - snapshot_period = spec.compute_epoch_at_slot(pre_snapshot.header.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - update_period = spec.compute_epoch_at_slot(state.slot) // spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - assert snapshot_period == update_period - - # Updated sync_committee and finality - next_sync_committee_branch = [spec.Bytes32() for _ in range(spec.floorlog2(spec.NEXT_SYNC_COMMITTEE_INDEX))] - finalized_block_header = blocks[spec.SLOTS_PER_EPOCH - 1].message - assert finalized_block_header.slot == spec.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch) - assert finalized_block_header.hash_tree_root() == state.finalized_checkpoint.root - finality_branch = build_proof(state.get_backing(), spec.FINALIZED_ROOT_INDEX) - - # Build block header - block = build_empty_block(spec, state) - block_header = spec.BeaconBlockHeader( - slot=block.slot, - proposer_index=block.proposer_index, - parent_root=block.parent_root, - state_root=state.hash_tree_root(), - body_root=block.body.hash_tree_root(), - ) - - # Sync committee signing the finalized_block_header - all_pubkeys = [v.pubkey for v in state.validators] - committee = [all_pubkeys.index(pubkey) for pubkey in state.current_sync_committee.pubkeys] - sync_committee_bits = [True] * len(committee) - sync_committee_signature = compute_aggregate_sync_committee_signature( - spec, - state, - block_header.slot, - committee, - block_root=spec.Root(block_header.hash_tree_root()), - ) - - update = spec.LightClientUpdate( - header=finalized_block_header, - next_sync_committee=state.next_sync_committee, - next_sync_committee_branch=next_sync_committee_branch, - finality_header=block_header, # block_header is the signed header - finality_branch=finality_branch, - sync_committee_bits=sync_committee_bits, - sync_committee_signature=sync_committee_signature, - fork_version=state.fork.current_version, - ) - - spec.process_light_client_update(store, update, state.slot, state.genesis_validators_root) - - # snapshot has been updated - assert len(store.valid_updates) == 0 - assert store.snapshot.header == update.header diff --git a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py index dd9214040e..4bd095464c 100644 --- a/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py +++ b/tests/core/pyspec/eth2spec/test/altair/unittests/validator/test_validator.py @@ -1,28 +1,30 @@ import random from collections import defaultdict -from eth2spec.utils.ssz.ssz_typing import Bitvector -from eth2spec.utils import bls -from eth2spec.test.helpers.block import build_empty_block -from eth2spec.test.helpers.keys import pubkey_to_privkey, privkeys, pubkeys -from eth2spec.test.helpers.state import transition_to -from eth2spec.test.helpers.sync_committee import compute_sync_committee_signature + from eth2spec.test.context import ( always_bls, spec_state_test, + with_all_phases_from_except, with_altair_and_later, with_presets, ) +from eth2spec.test.helpers.block import build_empty_block from eth2spec.test.helpers.constants import ( + ALTAIR, + EIP7805, MAINNET, MINIMAL, ) +from eth2spec.test.helpers.keys import privkeys, pubkey_to_privkey, pubkeys +from eth2spec.test.helpers.state import transition_to +from eth2spec.test.helpers.sync_committee import compute_sync_committee_signature +from eth2spec.utils import bls +from eth2spec.utils.ssz.ssz_typing import Bitvector rng = random.Random(1337) -def ensure_assignments_in_sync_committee( - spec, state, epoch, sync_committee, active_pubkeys -): +def ensure_assignments_in_sync_committee(spec, state, epoch, sync_committee, active_pubkeys): assert len(sync_committee.pubkeys) >= 3 some_pubkeys = rng.sample(sync_committee.pubkeys, 3) for pubkey in some_pubkeys: @@ -49,25 +51,20 @@ def test_is_assigned_to_sync_committee(spec, state): ) sync_committee_pubkeys = set( - list(state.current_sync_committee.pubkeys) - + list(state.next_sync_committee.pubkeys) + list(state.current_sync_committee.pubkeys) + list(state.next_sync_committee.pubkeys) ) disqualified_pubkeys = set( filter(lambda key: key not in sync_committee_pubkeys, active_pubkeys) ) - # NOTE: only check `disqualified_pubkeys` if SYNC_COMMITEE_SIZE < validator count + # NOTE: only check `disqualified_pubkeys` if SYNC_COMMITTEE_SIZE < validator count if disqualified_pubkeys: sample_size = 3 assert validator_count >= sample_size - some_pubkeys = rng.sample(disqualified_pubkeys, sample_size) + some_pubkeys = rng.sample(sorted(disqualified_pubkeys), sample_size) for pubkey in some_pubkeys: validator_index = active_pubkeys.index(pubkey) - is_current = spec.is_assigned_to_sync_committee( - state, query_epoch, validator_index - ) - is_next = spec.is_assigned_to_sync_committee( - state, next_query_epoch, validator_index - ) + is_current = spec.is_assigned_to_sync_committee(state, query_epoch, validator_index) + is_next = spec.is_assigned_to_sync_committee(state, next_query_epoch, validator_index) is_current_or_next = is_current or is_next assert not is_current_or_next @@ -81,9 +78,7 @@ def _get_sync_committee_signature( index_in_subcommittee, ): subcommittee_size = spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT - sync_committee_index = ( - subcommittee_index * subcommittee_size + index_in_subcommittee - ) + sync_committee_index = subcommittee_index * subcommittee_size + index_in_subcommittee pubkey = state.current_sync_committee.pubkeys[sync_committee_index] privkey = pubkey_to_privkey[pubkey] @@ -105,9 +100,7 @@ def test_process_sync_committee_contributions(spec, state): block = build_empty_block(spec, state) previous_slot = state.slot - 1 target_block_root = spec.get_block_root_at_slot(state, previous_slot) - aggregation_bits = Bitvector[ - spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT - ]() + aggregation_bits = Bitvector[spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT]() aggregation_index = 0 aggregation_bits[aggregation_index] = True @@ -132,19 +125,17 @@ def test_process_sync_committee_contributions(spec, state): # and that after processing, it is no longer empty assert len(block.body.sync_aggregate.sync_committee_bits) != 0 - assert ( - block.body.sync_aggregate.sync_committee_signature != spec.G2_POINT_AT_INFINITY - ) + assert block.body.sync_aggregate.sync_committee_signature != spec.G2_POINT_AT_INFINITY # moreover, ensure the sync aggregate is valid if the block is accepted spec.process_block(state, block) -@with_altair_and_later +@with_all_phases_from_except(ALTAIR, [EIP7805]) @spec_state_test @always_bls def test_get_sync_committee_message(spec, state): validator_index = 0 - block_root = spec.Root(b'\x12' * 32) + block_root = spec.Root(b"\x12" * 32) sync_committee_message = spec.get_sync_committee_message( state=state, block_root=block_root, @@ -172,7 +163,7 @@ def _subnet_for_sync_committee_index(spec, i): def _get_expected_subnets_by_pubkey(sync_committee_members): # Build deduplicated set for each pubkey expected_subnets_by_pubkey = defaultdict(set) - for (subnet, pubkey) in sync_committee_members: + for subnet, pubkey in sync_committee_members: expected_subnets_by_pubkey[pubkey].add(subnet) return expected_subnets_by_pubkey @@ -185,10 +176,9 @@ def test_compute_subnets_for_sync_committee(state, spec): transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD) next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) - assert ( - spec.compute_sync_committee_period(spec.get_current_epoch(state)) - == spec.compute_sync_committee_period(next_slot_epoch) - ) + assert spec.compute_sync_committee_period( + spec.get_current_epoch(state) + ) == spec.compute_sync_committee_period(next_slot_epoch) some_sync_committee_members = list( ( _subnet_for_sync_committee_index(spec, i), @@ -214,10 +204,9 @@ def test_compute_subnets_for_sync_committee_slot_period_boundary(state, spec): transition_to(spec, state, spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD - 1) next_slot_epoch = spec.compute_epoch_at_slot(state.slot + 1) - assert ( - spec.compute_sync_committee_period(spec.get_current_epoch(state)) - != spec.compute_sync_committee_period(next_slot_epoch) - ) + assert spec.compute_sync_committee_period( + spec.get_current_epoch(state) + ) != spec.compute_sync_committee_period(next_slot_epoch) some_sync_committee_members = list( ( _subnet_for_sync_committee_index(spec, i), @@ -249,7 +238,9 @@ def test_get_sync_committee_selection_proof(spec, state): privkey, ) - domain = spec.get_domain(state, spec.DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, spec.compute_epoch_at_slot(slot)) + domain = spec.get_domain( + state, spec.DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF, spec.compute_epoch_at_slot(slot) + ) signing_data = spec.SyncAggregatorSelectionData( slot=slot, subcommittee_index=subcommittee_index, @@ -285,10 +276,12 @@ def test_get_contribution_and_proof(spec, state): privkey = privkeys[3] contribution = spec.SyncCommitteeContribution( slot=10, - beacon_block_root=b'\x12' * 32, + beacon_block_root=b"\x12" * 32, subcommittee_index=1, - aggregation_bits=spec.Bitvector[spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT](), - signature=b'\x32' * 96, + aggregation_bits=spec.Bitvector[ + spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT + ](), + signature=b"\x32" * 96, ) selection_proof = spec.get_sync_committee_selection_proof( state, @@ -320,12 +313,14 @@ def test_get_contribution_and_proof_signature(spec, state): aggregator_index=10, contribution=spec.SyncCommitteeContribution( slot=10, - beacon_block_root=b'\x12' * 32, + beacon_block_root=b"\x12" * 32, subcommittee_index=1, - aggregation_bits=spec.Bitvector[spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT](), - signature=b'\x34' * 96, + aggregation_bits=spec.Bitvector[ + spec.SYNC_COMMITTEE_SIZE // spec.SYNC_COMMITTEE_SUBNET_COUNT + ](), + signature=b"\x34" * 96, ), - selection_proof=b'\x56' * 96, + selection_proof=b"\x56" * 96, ) contribution_and_proof_signature = spec.get_contribution_and_proof_signature( state, @@ -333,6 +328,8 @@ def test_get_contribution_and_proof_signature(spec, state): privkey, ) contribution = contribution_and_proof.contribution - domain = spec.get_domain(state, spec.DOMAIN_CONTRIBUTION_AND_PROOF, spec.compute_epoch_at_slot(contribution.slot)) + domain = spec.get_domain( + state, spec.DOMAIN_CONTRIBUTION_AND_PROOF, spec.compute_epoch_at_slot(contribution.slot) + ) signing_root = spec.compute_signing_root(contribution_and_proof, domain) assert bls.Verify(pubkey, signing_root, contribution_and_proof_signature) diff --git a/tests/core/pyspec/eth2spec/test/helpers/merge/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/helpers/merge/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/merge/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/block_processing/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_deposit.py new file mode 100644 index 0000000000..d3751cab11 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_deposit.py @@ -0,0 +1,41 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_bellatrix_and_later, +) +from eth2spec.test.helpers.deposits import ( + run_deposit_processing_with_specific_fork_version, +) + + +@with_bellatrix_and_later +@spec_state_test +@always_bls +def test_ineffective_deposit_with_previous_fork_version(spec, state): + # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION`. + # It's an ineffective deposit because it fails at BLS sig verification. + # NOTE: it was effective in Altair. + assert state.fork.previous_version != state.fork.current_version + + yield from run_deposit_processing_with_specific_fork_version( + spec, + state, + fork_version=state.fork.previous_version, + effective=False, + ) + + +@with_bellatrix_and_later +@spec_state_test +@always_bls +def test_effective_deposit_with_genesis_fork_version(spec, state): + assert spec.config.GENESIS_FORK_VERSION not in ( + state.fork.previous_version, + state.fork.current_version, + ) + + yield from run_deposit_processing_with_specific_fork_version( + spec, + state, + fork_version=spec.config.GENESIS_FORK_VERSION, + ) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py new file mode 100644 index 0000000000..b720fe94dc --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_execution_payload.py @@ -0,0 +1,432 @@ +from random import Random + +from eth2spec.test.context import ( + expect_assertion_error, + spec_state_test, + with_all_phases_from_except, + with_bellatrix_and_later, + with_phases, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + EIP7732, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_randomized_execution_payload, + build_state_with_complete_transition, + build_state_with_incomplete_transition, + compute_el_block_hash, + get_execution_payload_header, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.keys import privkeys +from eth2spec.test.helpers.state import next_slot + + +def run_execution_payload_processing( + spec, state, execution_payload, valid=True, execution_valid=True +): + """ + Run ``process_execution_payload``, yielding: + - pre-state ('pre') + - execution payload ('execution_payload') + - execution details, to mock EVM execution ('execution.yml', a dict with 'execution_valid' key and boolean value) + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # Before Deneb, only `body.execution_payload` matters. `BeaconBlockBody` is just a wrapper. + # after EIP-7732 the execution payload is no longer in the body + if is_post_eip7732(spec): + envelope = spec.ExecutionPayloadEnvelope( + payload=execution_payload, + beacon_block_root=state.latest_block_header.hash_tree_root(), + payload_withheld=False, + ) + post_state = state.copy() + post_state.latest_block_hash = execution_payload.block_hash + post_state.latest_full_slot = state.slot + envelope.state_root = post_state.hash_tree_root() + privkey = privkeys[envelope.builder_index] + signature = spec.get_execution_payload_envelope_signature( + state, + envelope, + privkey, + ) + signed_envelope = spec.SignedExecutionPayloadEnvelope( + message=envelope, + signature=signature, + ) + else: + body = spec.BeaconBlockBody(execution_payload=execution_payload) + + yield "pre", state + yield "execution", {"execution_valid": execution_valid} + if not is_post_eip7732(spec): + yield "body", body + + called_new_block = False + + class TestEngine(spec.NoopExecutionEngine): + def verify_and_notify_new_payload(self, new_payload_request) -> bool: + nonlocal called_new_block + called_new_block = True + assert new_payload_request.execution_payload == execution_payload + return execution_valid + + if not valid: + if is_post_eip7732(spec): + expect_assertion_error( + lambda: spec.process_execution_payload(state, signed_envelope, TestEngine()) + ) + else: + expect_assertion_error( + lambda: spec.process_execution_payload(state, body, TestEngine()) + ) + yield "post", None + return + + if is_post_eip7732(spec): + spec.process_execution_payload(state, signed_envelope, TestEngine()) + else: + spec.process_execution_payload(state, body, TestEngine()) + + # Make sure we called the engine + assert called_new_block + + yield "post", state + + if is_post_eip7732(spec): + assert state.latest_full_slot == state.slot + assert state.latest_block_hash == execution_payload.block_hash + else: + assert state.latest_execution_payload_header == get_execution_payload_header( + spec, state, body.execution_payload + ) + + +def run_success_test(spec, state): + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_success_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + + yield from run_success_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_success_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + + yield from run_success_test(spec, state) + + +def run_gap_slot_test(spec, state): + next_slot(spec, state) + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_success_first_payload_with_gap_slot(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_gap_slot_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_success_regular_payload_with_gap_slot(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_gap_slot_test(spec, state) + + +def run_bad_execution_test(spec, state): + # completely valid payload, but execution itself fails (e.g. block exceeds gas limit) + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_execution_payload_processing( + spec, state, execution_payload, valid=False, execution_valid=False + ) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_invalid_bad_execution_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_bad_execution_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_invalid_bad_execution_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_bad_execution_test(spec, state) + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_bad_parent_hash_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.parent_hash = b"\x55" * 32 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_bad_parent_hash_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.parent_hash = spec.Hash32() + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +def run_bad_prev_randao_test(spec, state): + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.prev_randao = b"\x42" * 32 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_bad_prev_randao_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_bad_prev_randao_test(spec, state) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_bad_pre_randao_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_bad_prev_randao_test(spec, state) + + +def run_bad_everything_test(spec, state): + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.parent_hash = spec.Hash32() + execution_payload.prev_randao = spec.Bytes32() + execution_payload.timestamp = 0 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_bad_everything_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_bad_everything_test(spec, state) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_bad_everything_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_bad_everything_test(spec, state) + + +def run_bad_timestamp_test(spec, state, is_future): + next_slot(spec, state) + + # execution payload + execution_payload = build_empty_execution_payload(spec, state) + if is_future: + timestamp = execution_payload.timestamp + 1 + else: + timestamp = execution_payload.timestamp - 1 + execution_payload.timestamp = timestamp + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_future_timestamp_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_bad_timestamp_test(spec, state, is_future=True) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_future_timestamp_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_bad_timestamp_test(spec, state, is_future=True) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_past_timestamp_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_bad_timestamp_test(spec, state, is_future=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_invalid_past_timestamp_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_bad_timestamp_test(spec, state, is_future=False) + + +def run_non_empty_extra_data_test(spec, state): + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.extra_data = b"\x45" * 12 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + assert state.latest_execution_payload_header.extra_data == execution_payload.extra_data + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_non_empty_extra_data_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_non_empty_extra_data_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_non_empty_extra_data_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_non_empty_extra_data_test(spec, state) + + +def run_non_empty_transactions_test(spec, state): + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + num_transactions = 2 + execution_payload.transactions = [ + spec.Transaction(b"\x99" * 128) for _ in range(num_transactions) + ] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + assert ( + state.latest_execution_payload_header.transactions_root + == execution_payload.transactions.hash_tree_root() + ) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_non_empty_transactions_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_non_empty_extra_data_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_non_empty_transactions_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_non_empty_extra_data_test(spec, state) + + +def run_zero_length_transaction_test(spec, state): + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.transactions = [spec.Transaction(b"")] + assert len(execution_payload.transactions[0]) == 0 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload) + assert ( + state.latest_execution_payload_header.transactions_root + == execution_payload.transactions.hash_tree_root() + ) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_zero_length_transaction_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + yield from run_zero_length_transaction_test(spec, state) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_zero_length_transaction_regular_payload(spec, state): + state = build_state_with_complete_transition(spec, state) + yield from run_zero_length_transaction_test(spec, state) + + +def run_randomized_non_validated_execution_fields_test(spec, state, rng, execution_valid=True): + next_slot(spec, state) + execution_payload = build_randomized_execution_payload(spec, state, rng) + + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + state.latest_execution_payload_header.gas_limit = execution_payload.gas_limit + state.latest_block_hash = execution_payload.parent_hash + + yield from run_execution_payload_processing( + spec, state, execution_payload, valid=execution_valid, execution_valid=execution_valid + ) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_randomized_non_validated_execution_fields_first_payload__execution_valid(spec, state): + rng = Random(1111) + state = build_state_with_incomplete_transition(spec, state) + yield from run_randomized_non_validated_execution_fields_test(spec, state, rng) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_randomized_non_validated_execution_fields_regular_payload__execution_valid(spec, state): + rng = Random(2222) + state = build_state_with_complete_transition(spec, state) + yield from run_randomized_non_validated_execution_fields_test(spec, state, rng) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_invalid_randomized_non_validated_execution_fields_first_payload__execution_invalid( + spec, state +): + rng = Random(3333) + state = build_state_with_incomplete_transition(spec, state) + yield from run_randomized_non_validated_execution_fields_test( + spec, state, rng, execution_valid=False + ) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_invalid_randomized_non_validated_execution_fields_regular_payload__execution_invalid( + spec, state +): + rng = Random(4444) + state = build_state_with_complete_transition(spec, state) + yield from run_randomized_non_validated_execution_fields_test( + spec, state, rng, execution_valid=False + ) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py new file mode 100644 index 0000000000..99a81ee15f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/block_processing/test_process_voluntary_exit.py @@ -0,0 +1,139 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_bellatrix_and_later, + with_phases, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, +) +from eth2spec.test.helpers.keys import pubkey_to_privkey +from eth2spec.test.helpers.state import ( + next_epoch, +) +from eth2spec.test.helpers.voluntary_exits import ( + run_voluntary_exit_processing, + sign_voluntary_exit, +) + +BELLATRIX_AND_CAPELLA = [BELLATRIX, CAPELLA] + + +def run_voluntary_exit_processing_test(spec, state, fork_version, is_before_fork_epoch, valid=True): + # create a fork + next_epoch(spec, state) + state.fork.epoch = spec.get_current_epoch(state) + + voluntary_exit_epoch = 0 if is_before_fork_epoch else state.fork.epoch + + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + + voluntary_exit = spec.VoluntaryExit( + epoch=voluntary_exit_epoch, + validator_index=validator_index, + ) + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + voluntary_exit, + privkey, + fork_version=fork_version, + ) + + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=valid) + + +@with_bellatrix_and_later +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_current_fork_version_is_before_fork_epoch(spec, state): + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.current_version, + is_before_fork_epoch=True, + valid=False, + ) + + +@with_phases(BELLATRIX_AND_CAPELLA) +@spec_state_test +@always_bls +def test_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state): + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.current_version, + is_before_fork_epoch=False, + ) + + +@with_phases([BELLATRIX, CAPELLA]) +@spec_state_test +@always_bls +def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state): + assert state.fork.previous_version != state.fork.current_version + + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=True, + ) + + +@with_phases(BELLATRIX_AND_CAPELLA) +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state): + assert state.fork.previous_version != state.fork.current_version + + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=False, + valid=False, + ) + + +@with_bellatrix_and_later +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_genesis_fork_version_is_before_fork_epoch(spec, state): + assert spec.config.GENESIS_FORK_VERSION not in ( + state.fork.previous_version, + state.fork.current_version, + ) + + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=spec.config.GENESIS_FORK_VERSION, + is_before_fork_epoch=True, + valid=False, + ) + + +@with_bellatrix_and_later +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_genesis_fork_version_not_is_before_fork_epoch(spec, state): + assert spec.config.GENESIS_FORK_VERSION not in ( + state.fork.previous_version, + state.fork.current_version, + ) + + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=spec.config.GENESIS_FORK_VERSION, + is_before_fork_epoch=False, + valid=False, + ) diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/block_processing/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_basic.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_basic.py new file mode 100644 index 0000000000..23d6a6d766 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_basic.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.bellatrix.fork import ( + BELLATRIX_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + BELLATRIX, + MINIMAL, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[BELLATRIX], state) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py new file mode 100644 index 0000000000..778a333712 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork/test_bellatrix_fork_random.py @@ -0,0 +1,94 @@ +from random import Random + +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.bellatrix.fork import ( + BELLATRIX_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + BELLATRIX, + MINIMAL, +) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_state +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@spec_test +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[BELLATRIX], state) + + +@with_phases(phases=[ALTAIR], other_phases=[BELLATRIX]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(BELLATRIX_FORK_TEST_META_TAGS) +def test_bellatrix_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[BELLATRIX], state) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/fork/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py new file mode 100644 index 0000000000..bc7d473d1d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_on_merge_block.py @@ -0,0 +1,199 @@ +from eth2spec.test.context import BELLATRIX, spec_state_test, with_phases +from eth2spec.test.exceptions import BlockNotFoundException +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.execution_payload import ( + build_state_with_incomplete_transition, + compute_el_block_hash, +) +from eth2spec.test.helpers.fork_choice import ( + add_pow_block, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block, +) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.utils.ssz.ssz_typing import uint256 + + +def with_pow_block_patch(spec, blocks, func): + def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock: + for block in blocks: + if block.block_hash == hash: + return block + raise BlockNotFoundException() + + get_pow_block_backup = spec.get_pow_block + spec.get_pow_block = get_pow_block + + class AtomicBoolean: + value = False + + is_called = AtomicBoolean() + + def wrap(flag: AtomicBoolean): + yield from func() + flag.value = True + + try: + yield from wrap(is_called) + finally: + spec.get_pow_block = get_pow_block_backup + assert is_called.value + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_all_valid(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_random_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_block = prepare_random_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps, merge_block=True) + # valid + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield "steps", test_steps + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_block_lookup_failed(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block = prepare_random_pow_block(spec) + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_blocks = [pow_block] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block( + spec, + store, + signed_block, + test_steps, + valid=False, + merge_block=True, + block_not_found=True, + ) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield "steps", test_steps + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_too_early_for_merge(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_random_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + pow_block = prepare_random_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block( + spec, store, signed_block, test_steps, valid=False, merge_block=True + ) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield "steps", test_steps + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_too_late_for_merge(spec, state): + test_steps = [] + # Initialization + state = build_state_with_incomplete_transition(spec, state) + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + pow_block_parent = prepare_random_pow_block(spec) + pow_block_parent.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_block = prepare_random_pow_block(spec) + pow_block.parent_hash = pow_block_parent.block_hash + pow_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + pow_blocks = [pow_block, pow_block_parent] + for pb in pow_blocks: + yield from add_pow_block(spec, store, pb, test_steps) + + def run_func(): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = pow_block.block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block( + spec, store, signed_block, test_steps, valid=False, merge_block=True + ) + + yield from with_pow_block_patch(spec, pow_blocks, run_func) + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_should_override_forkchoice_update.py b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_should_override_forkchoice_update.py new file mode 100644 index 0000000000..206a46197f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/fork_choice/test_should_override_forkchoice_update.py @@ -0,0 +1,194 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestations_at_slot, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + EIP7732, + MINIMAL, +) +from eth2spec.test.helpers.fork_choice import ( + apply_next_epoch_with_attestations, + apply_next_slots_with_attestations, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + output_store_checks, + tick_and_add_block, + tick_and_run_on_attestation, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, + state_transition_and_sign_block, +) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_should_override_forkchoice_update__false(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # Proposer of next slot + head_root = spec.get_head(store) + + # Next slot + next_slot(spec, state) + slot = state.slot + + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + + should_override = spec.should_override_forkchoice_update(store, head_root) + assert not should_override + + output_store_checks(spec, store, test_steps) + test_steps.append( + { + "checks": { + "should_override_forkchoice_update": { + "validator_is_connected": True, + "result": should_override, + }, + } + } + ) + + yield "steps", test_steps + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_should_override_forkchoice_update__true(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + + # Make an empty block + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + # Fill a slot (parent) + state, store, signed_parent_block = yield from apply_next_slots_with_attestations( + spec, state, store, 1, True, True, test_steps + ) + + # Fill a slot with attestations to its parent + block = build_empty_block_for_next_slot(spec, state) + parent_block_slot = block.slot - 1 + block.body.attestations = get_valid_attestations_at_slot( + state, + spec, + parent_block_slot, + ) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Make the head block late + attesting_cutoff = spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + attesting_cutoff + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert spec.get_current_slot(store) == block.slot + + # Check conditions + head_root = spec.get_head(store) + head_block = store.blocks[head_root] + parent_root = head_block.parent_root + assert parent_root == signed_parent_block.message.hash_tree_root() + parent_block = store.blocks[parent_root] + + # Add attestations to the parent block + temp_state = state.copy() + next_slot(spec, temp_state) + attestations = get_valid_attestations_at_slot( + temp_state, + spec, + slot_to_attest=temp_state.slot - 1, + beacon_block_root=parent_root, + ) + current_slot = spec.get_current_slot(store) + for attestation in attestations: + yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) + + current_slot = spec.get_current_slot(store) + proposal_slot = head_block.slot + 1 + + # The conditions in `get_proposer_head` + assert spec.is_head_late(store, head_root) + assert spec.is_shuffling_stable(proposal_slot) + assert spec.is_ffg_competitive(store, head_root, parent_root) + assert spec.is_finalization_ok(store, proposal_slot) + + parent_state_advanced = store.block_states[parent_root].copy() + spec.process_slots(parent_state_advanced, proposal_slot) + proposer_index = spec.get_beacon_proposer_index(parent_state_advanced) + assert spec.validator_is_connected(proposer_index) + + # Single slot re-org. + parent_slot_ok = parent_block.slot + 1 == head_block.slot + proposing_on_time = spec.is_proposing_on_time(store) + assert proposing_on_time + assert parent_slot_ok and proposal_slot == current_slot and proposing_on_time + + assert spec.is_head_weak(store, head_root) + assert spec.is_parent_strong(store, parent_root) + + should_override = spec.should_override_forkchoice_update(store, head_root) + assert should_override + + output_store_checks(spec, store, test_steps) + test_steps.append( + { + "checks": { + "should_override_forkchoice_update": { + "validator_is_connected": True, + "result": should_override, + }, + } + } + ) + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/merge/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/light_client/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/merge/sanity/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/light_client/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_data_collection.py b/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_data_collection.py new file mode 100644 index 0000000000..aafa9a22a5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_data_collection.py @@ -0,0 +1,49 @@ +from eth2spec.test.context import ( + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, + DENEB, + MINIMAL, +) +from eth2spec.test.helpers.light_client_data_collection import ( + run_lc_data_collection_test_multi_fork, +) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, DENEB]) +@spec_test +@with_config_overrides( + { + "CAPELLA_FORK_EPOCH": 1 * 8, # SyncCommitteePeriod 1 + "DENEB_FORK_EPOCH": 2 * 8, # SyncCommitteePeriod 2 + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=DENEB) +@with_presets([MINIMAL], reason="too slow") +def test_capella_deneb_reorg_aligned(spec, phases, state): + yield from run_lc_data_collection_test_multi_fork(spec, phases, state, CAPELLA, DENEB) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, DENEB]) +@spec_test +@with_config_overrides( + { + "CAPELLA_FORK_EPOCH": 1 * 8 + 4, # SyncCommitteePeriod 1 (+ 4 epochs) + "DENEB_FORK_EPOCH": 3 * 8 + 4, # SyncCommitteePeriod 3 (+ 4 epochs) + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=DENEB) +@with_presets([MINIMAL], reason="too slow") +def test_capella_deneb_reorg_unaligned(spec, phases, state): + yield from run_lc_data_collection_test_multi_fork(spec, phases, state, CAPELLA, DENEB) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_sync.py new file mode 100644 index 0000000000..c6a6c86f19 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/light_client/test_sync.py @@ -0,0 +1,67 @@ +from eth2spec.test.context import ( + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.light_client_sync import ( + run_lc_sync_test_multi_fork, + run_lc_sync_test_single_fork, +) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_config_overrides( + { + "CAPELLA_FORK_EPOCH": 3, # Test setup advances to epoch 2 + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=CAPELLA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_fork(spec, phases, state): + yield from run_lc_sync_test_single_fork(spec, phases, state, CAPELLA) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, DENEB]) +@spec_test +@with_config_overrides( + { + "CAPELLA_FORK_EPOCH": 3, # Test setup advances to epoch 2 + "DENEB_FORK_EPOCH": 4, + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=DENEB) +@with_presets([MINIMAL], reason="too slow") +def test_capella_deneb_fork(spec, phases, state): + yield from run_lc_sync_test_multi_fork(spec, phases, state, CAPELLA, DENEB) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA, DENEB, ELECTRA]) +@spec_test +@with_config_overrides( + { + "CAPELLA_FORK_EPOCH": 3, # Test setup advances to epoch 2 + "DENEB_FORK_EPOCH": 4, + "ELECTRA_FORK_EPOCH": 5, + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_capella_electra_fork(spec, phases, state): + yield from run_lc_sync_test_multi_fork(spec, phases, state, CAPELLA, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/sharding/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/random/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/sharding/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/random/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/random/test_random.py b/tests/core/pyspec/eth2spec/test/bellatrix/random/test_random.py new file mode 100644 index 0000000000..3cd6d78f07 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/random/test_random.py @@ -0,0 +1,1172 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.context import ( + always_bls, + misc_balances_in_default_range_with_many_validators, + only_generator, + single_phase, + spec_test, + with_custom_state, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.constants import BELLATRIX +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([BELLATRIX]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_bellatrix + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_bellatrix", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_bellatrix", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/sharding/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/__init__.py similarity index 100% rename from tests/core/pyspec/eth2spec/test/sharding/unittests/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/sanity/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py new file mode 100644 index 0000000000..8e61c29a10 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sanity/test_blocks.py @@ -0,0 +1,68 @@ +from random import Random + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_phases, +) +from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.constants import ( + BELLATRIX, + EIP7732, +) +from eth2spec.test.helpers.execution_payload import build_randomized_execution_payload +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, +) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_empty_block_transition_no_tx(spec, state): + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + assert len(block.body.execution_payload.transactions) == 0 + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_block_transition_randomized_payload(spec, state): + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + next_slot_state = state.copy() + next_slot(spec, next_slot_state) + block.body.execution_payload = build_randomized_execution_payload( + spec, next_slot_state, rng=Random(34433) + ) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_is_execution_enabled_false(spec, state): + # Set `latest_execution_payload_header` to empty + state.latest_execution_payload_header = spec.ExecutionPayloadHeader() + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + # Set `execution_payload` to empty + block.body.execution_payload = spec.ExecutionPayload() + assert len(block.body.execution_payload.transactions) == 0 + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state diff --git a/tests/generators/ssz_generic/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/__init__.py similarity index 100% rename from tests/generators/ssz_generic/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/sync/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py new file mode 100644 index 0000000000..c83f3d291c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/sync/test_optimistic.py @@ -0,0 +1,131 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, +) +from eth2spec.test.helpers.attestations import ( + state_transition_with_full_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + EIP7732, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) +from eth2spec.test.helpers.fork_choice import ( + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, +) +from eth2spec.test.helpers.optimistic_sync import ( + add_optimistic_block, + get_optimistic_store, + MegaStore, + PayloadStatusV1, + PayloadStatusV1Status, +) +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, +) + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_from_syncing_to_invalid(spec, state): + test_steps = [] + # Initialization + fc_store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + op_store = get_optimistic_store(spec, state, anchor_block) + mega_store = MegaStore(spec, fc_store, op_store) + block_hashes = {} + yield "anchor_state", state + yield "anchor_block", anchor_block + + next_epoch(spec, state) + + current_time = ( + spec.SAFE_SLOTS_TO_IMPORT_OPTIMISTICALLY * 10 + state.slot + ) * spec.config.SECONDS_PER_SLOT + fc_store.genesis_time + on_tick_and_append_step(spec, fc_store, current_time, test_steps) + + # Block 0 + block_0 = build_empty_block_for_next_slot(spec, state) + block_hashes["block_0"] = block_0.body.execution_payload.block_hash + signed_block = state_transition_and_sign_block(spec, state, block_0) + yield from add_optimistic_block( + spec, mega_store, signed_block, test_steps, status=PayloadStatusV1Status.VALID + ) + assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root + + state_0 = state.copy() + + # Create VALID chain `a` + signed_blocks_a = [] + for i in range(3): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = ( + block_hashes[f"chain_a_{i - 1}"] if i != 0 else block_hashes["block_0"] + ) + block.body.execution_payload.extra_data = spec.hash(bytes(f"chain_a_{i}", "UTF-8")) + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + block_hashes[f"chain_a_{i}"] = block.body.execution_payload.block_hash + + signed_block = state_transition_and_sign_block(spec, state, block) + yield from add_optimistic_block( + spec, mega_store, signed_block, test_steps, status=PayloadStatusV1Status.VALID + ) + assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root + signed_blocks_a.append(signed_block.copy()) + + # Create SYNCING chain `b` + signed_blocks_b = [] + state = state_0.copy() + for i in range(3): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = ( + block_hashes[f"chain_b_{i - 1}"] if i != 0 else block_hashes["block_0"] + ) + block.body.execution_payload.extra_data = spec.hash(bytes(f"chain_b_{i}", "UTF-8")) + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + block_hashes[f"chain_b_{i}"] = block.body.execution_payload.block_hash + + signed_block = state_transition_with_full_block(spec, state, True, True, block=block) + signed_blocks_b.append(signed_block.copy()) + yield from add_optimistic_block( + spec, mega_store, signed_block, test_steps, status=PayloadStatusV1Status.SYNCING + ) + assert spec.get_head(mega_store.fc_store) == mega_store.opt_store.head_block_root + + # Now add block 4 to chain `b` with INVALID + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.parent_hash = signed_blocks_b[ + -1 + ].message.body.execution_payload.block_hash + block.body.execution_payload.extra_data = spec.hash(bytes(f"chain_b_{i}", "UTF-8")) + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + block_hashes["chain_b_3"] = block.body.execution_payload.block_hash + + # Ensure that no duplicate block hashes + assert len(block_hashes) == len(set(block_hashes.values())) + + signed_block = state_transition_and_sign_block(spec, state, block) + payload_status = PayloadStatusV1( + status=PayloadStatusV1Status.INVALID, + latest_valid_hash=block_0.body.execution_payload.block_hash, + validation_error="invalid", + ) + yield from add_optimistic_block( + spec, mega_store, signed_block, test_steps, payload_status=payload_status + ) + assert mega_store.opt_store.head_block_root == signed_blocks_a[-1].message.hash_tree_root() + + yield "steps", test_steps diff --git a/tests/generators/ssz_static/__init__.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/__init__.py similarity index 100% rename from tests/generators/ssz_static/__init__.py rename to tests/core/pyspec/eth2spec/test/bellatrix/unittests/__init__.py diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..5b43042336 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_execution_engine_interface.py @@ -0,0 +1,99 @@ +from eth2spec.test.context import ( + BELLATRIX, + CAPELLA, + spec_state_test, + with_bellatrix_and_later, + with_phases, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot +from eth2spec.utils.ssz.ssz_typing import Bytes32 + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_notify_forkchoice_updated(spec, state): + """ + Test NoopExecutionEngine.notify_forkchoice_updated returns None and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test notify_forkchoice_updated + result = engine.notify_forkchoice_updated( + head_block_hash=Bytes32(), + safe_block_hash=Bytes32(), + finalized_block_hash=Bytes32(), + payload_attributes=None, + ) + + # Verify behavior + assert result is None + assert state == pre_state + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_get_payload(spec, state): + """ + Test NoopExecutionEngine.get_payload raises NotImplementedError + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test get_payload raises NotImplementedError + try: + engine.get_payload(payload_id=None) + raise AssertionError("get_payload should raise NotImplementedError") + except NotImplementedError: + pass + + # Verify state wasn't modified + assert state == pre_state + + +@with_bellatrix_and_later +@spec_state_test +def test_noop_execution_engine_verify_and_notify_new_payload(spec, state): + """ + Test NoopExecutionEngine.verify_and_notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + result = engine.verify_and_notify_new_payload(new_payload_request=None) + + assert result is True + assert state == pre_state + + +@with_phases([BELLATRIX, CAPELLA]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_bellatrix_capella(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload(execution_payload=payload) + + assert result is True + + +@with_phases([BELLATRIX, CAPELLA]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_bellatrix_capella(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash(execution_payload=payload) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py new file mode 100644 index 0000000000..826a489cb2 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_is_valid_terminal_pow_block.py @@ -0,0 +1,44 @@ +from eth2spec.test.context import ( + spec_state_test, + with_bellatrix_and_later, +) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_block, +) +from eth2spec.utils.ssz.ssz_typing import uint256 + + +@with_bellatrix_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_success_valid(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + + assert spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_bellatrix_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_fail_before_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) + + +@with_bellatrix_and_later +@spec_state_test +def test_is_valid_terminal_pow_block_fail_just_after_terminal(spec, state): + parent_block = prepare_random_pow_block(spec) + parent_block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = prepare_random_pow_block(spec) + block.parent_hash = parent_block.block_hash + block.total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + + assert not spec.is_valid_terminal_pow_block(block, parent_block) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py new file mode 100644 index 0000000000..f6b3bf4c41 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_transition.py @@ -0,0 +1,60 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_bellatrix_and_later, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + EIP7732, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_state_with_complete_transition, + build_state_with_incomplete_transition, +) + + +@with_bellatrix_and_later +@spec_state_test +def test_fail_merge_complete(spec, state): + state = build_state_with_incomplete_transition(spec, state) + assert not spec.is_merge_transition_complete(state) + + +@with_bellatrix_and_later +@spec_state_test +def test_success_merge_complete(spec, state): + state = build_state_with_complete_transition(spec, state) + assert spec.is_merge_transition_complete(state) + + +# with_complete_transition', 'with_execution_payload', 'is_merge_transition_block', 'is_execution_enabled' +expected_results = [ + (True, True, False, True), + (True, False, False, True), + (False, True, True, True), + (False, False, False, False), +] + + +@with_all_phases_from_except(BELLATRIX, [EIP7732]) +@spec_state_test +def test_is_merge_block_and_is_execution_enabled(spec, state): + for result in expected_results: + ( + with_complete_transition, + with_execution_payload, + is_merge_transition_block, + is_execution_enabled, + ) = result + if with_complete_transition: + state = build_state_with_complete_transition(spec, state) + else: + state = build_state_with_incomplete_transition(spec, state) + + body = spec.BeaconBlockBody() + if with_execution_payload: + body.execution_payload = build_empty_execution_payload(spec, state) + + assert spec.is_merge_transition_block(state, body) == is_merge_transition_block + assert spec.is_execution_enabled(state, body) == is_execution_enabled diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py new file mode 100644 index 0000000000..6cb6f9c9c0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/test_validate_merge_block.py @@ -0,0 +1,257 @@ +from eth2spec.test.context import ( + spec_configured_state_test, + spec_state_test, + with_bellatrix_and_later, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_chain, +) +from eth2spec.utils.ssz.ssz_typing import Bytes32, uint256 + +TERMINAL_BLOCK_HASH_CONFIG_VAR = ( + "0x0000000000000000000000000000000000000000000000000000000000000001" +) +TERMINAL_BLOCK_HASH = Bytes32(TERMINAL_BLOCK_HASH_CONFIG_VAR) + + +def run_validate_merge_block(spec, pow_chain, beacon_block, valid=True): + """ + Run ``validate_merge_block`` + If ``valid == False``, run expecting ``AssertionError`` + """ + + def get_pow_block(hash: spec.Bytes32) -> spec.PowBlock | None: + for block in pow_chain: + if block.block_hash == hash: + return block + return None + + get_pow_block_backup = spec.get_pow_block + + # Guido authorized everyone to do this + spec.get_pow_block = get_pow_block + assertion_error_caught = False + try: + spec.validate_merge_block(beacon_block) + except AssertionError: + assertion_error_caught = True + except Exception as e: + spec.get_pow_block = get_pow_block_backup + raise e + spec.get_pow_block = get_pow_block_backup + + if valid: + assert not assertion_error_caught + else: + assert assertion_error_caught + + +@with_bellatrix_and_later +@spec_state_test +def test_validate_merge_block_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block) + + +@with_bellatrix_and_later +@spec_state_test +def test_validate_merge_block_fail_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_validate_merge_block_fail_parent_block_lookup(spec, state): + pow_chain = prepare_random_pow_chain(spec, 1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_bellatrix_and_later +@spec_state_test +def test_validate_merge_block_fail_after_terminal(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + uint256(1) + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_bellatrix_and_later +@spec_configured_state_test( + { + "TERMINAL_BLOCK_HASH": TERMINAL_BLOCK_HASH_CONFIG_VAR, + "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH": "0", + } +) +def test_validate_merge_block_tbh_override_success(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # should fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(2) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block) + + +@with_bellatrix_and_later +@spec_configured_state_test( + { + "TERMINAL_BLOCK_HASH": TERMINAL_BLOCK_HASH_CONFIG_VAR, + "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH": "0", + } +) +def test_validate_merge_block_fail_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_bellatrix_and_later +@spec_configured_state_test( + { + "TERMINAL_BLOCK_HASH": TERMINAL_BLOCK_HASH_CONFIG_VAR, + "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH": "1", + } +) +def test_validate_merge_block_terminal_block_hash_fail_activation_not_reached(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + pow_chain.head().block_hash = TERMINAL_BLOCK_HASH + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block, valid=False) + + +@with_bellatrix_and_later +@spec_configured_state_test( + { + "TERMINAL_BLOCK_HASH": TERMINAL_BLOCK_HASH_CONFIG_VAR, + "TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH": "1", + } +) +def test_validate_merge_block_fail_activation_not_reached_parent_hash_is_not_tbh(spec, state): + pow_chain = prepare_random_pow_chain(spec, 2) + # shouldn't fail if TTD check is reached + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - uint256(1) + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + block = build_empty_block_for_next_slot(spec, state) + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.parent_block_hash = ( + pow_chain.head().block_hash + ) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + else: + block.body.execution_payload.parent_hash = pow_chain.head().block_hash + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + run_validate_merge_block(spec, pow_chain, block, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py new file mode 100644 index 0000000000..1315be00d5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/bellatrix/unittests/validator/test_validator.py @@ -0,0 +1,167 @@ +from copy import deepcopy + +from eth2spec.test.context import ( + spec_state_test, + with_phases, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, +) +from eth2spec.test.helpers.pow_block import ( + prepare_random_pow_chain, +) + +# For test_get_pow_block_at_terminal_total_difficulty +IS_HEAD_BLOCK = "is_head_block" +IS_HEAD_PARENT_BLOCK = "is_head_parent_block" + +# NOTE: The following parameter names are in the view of the head block (the second block) +# 'block_reached_ttd', 'block_parent_hash_is_empty', 'parent_reached_ttd', 'return_block' +expected_results = [ + (False, False, False, None), + (False, False, True, IS_HEAD_PARENT_BLOCK), + (False, True, False, None), + (False, True, True, IS_HEAD_PARENT_BLOCK), + (True, False, False, IS_HEAD_BLOCK), + (True, False, True, IS_HEAD_PARENT_BLOCK), + (True, True, False, IS_HEAD_BLOCK), + (True, True, True, IS_HEAD_PARENT_BLOCK), +] +# NOTE: since the first block's `parent_hash` is set to `Hash32()` in test, if `parent_reached_ttd is True`, +# it would return the first block (IS_HEAD_PARENT_BLOCK). + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_get_pow_block_at_terminal_total_difficulty(spec, state): + for result in expected_results: + (block_reached_ttd, block_parent_hash_is_empty, parent_reached_ttd, return_block) = result + pow_chain = prepare_random_pow_chain(spec, 2) + pow_chain.head(-1).parent_hash = spec.Hash32() + + if block_reached_ttd: + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + else: + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - 1 + + if parent_reached_ttd: + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + else: + pow_chain.head(-1).total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - 1 + + if block_parent_hash_is_empty: + pow_chain.head().parent_hash = spec.Hash32() + + pow_block = spec.get_pow_block_at_terminal_total_difficulty(pow_chain.to_dict()) + if return_block == IS_HEAD_BLOCK: + assert pow_block == pow_chain.head() + elif return_block == IS_HEAD_PARENT_BLOCK: + assert pow_block == pow_chain.head(-1) + elif return_block is None: + assert pow_block is None + else: + raise Exception("Something is wrong") + + +SAMPLE_PAYLOAD_ID = b"\x12" * 8 +# ('is_merge_complete', 'is_terminal_block_hash_set', 'is_activation_epoch_reached', +# 'terminal_pow_block_is_none', 'result_payload_id') +prepare_execution_payload_expected_results = [ + (False, False, False, False, SAMPLE_PAYLOAD_ID), + (False, False, False, True, None), + (False, False, True, False, SAMPLE_PAYLOAD_ID), + (False, False, True, True, None), + (False, True, False, False, None), + (False, True, False, True, None), + (False, True, True, False, SAMPLE_PAYLOAD_ID), + (False, True, True, True, None), + (True, False, False, False, SAMPLE_PAYLOAD_ID), + (True, False, False, True, SAMPLE_PAYLOAD_ID), + (True, False, True, False, SAMPLE_PAYLOAD_ID), + (True, False, True, True, SAMPLE_PAYLOAD_ID), + (True, True, False, False, SAMPLE_PAYLOAD_ID), + (True, True, False, True, SAMPLE_PAYLOAD_ID), + (True, True, True, False, SAMPLE_PAYLOAD_ID), + (True, True, True, True, SAMPLE_PAYLOAD_ID), +] + + +@with_phases([BELLATRIX]) +@spec_state_test +def test_prepare_execution_payload(spec, state): + for result in prepare_execution_payload_expected_results: + ( + is_merge_complete, + is_terminal_block_hash_set, + is_activation_epoch_reached, + terminal_pow_block_is_none, + result_payload_id, + ) = result + + # 1. Handle `is_merge_complete` + if is_merge_complete: + state.latest_execution_payload_header = spec.ExecutionPayloadHeader( + prev_randao=b"\x12" * 32 + ) + else: + state.latest_execution_payload_header = spec.ExecutionPayloadHeader() + + # 2. `is_terminal_block_hash_set` and `is_activation_epoch_reached` require mocking configs in runtime + config_overrides = {} + _mock_terminal_block_hash = b"\x34" * 32 + if is_terminal_block_hash_set: + config_overrides["TERMINAL_BLOCK_HASH"] = _mock_terminal_block_hash + else: + config_overrides["TERMINAL_BLOCK_HASH"] = spec.Hash32() + + # Default `TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH` is too big and too close to overflow + _mock_terminal_block_hash_activation_epoch = 3 + config_overrides["TERMINAL_BLOCK_HASH_ACTIVATION_EPOCH"] = ( + _mock_terminal_block_hash_activation_epoch + ) + if is_activation_epoch_reached: + state.slot = _mock_terminal_block_hash_activation_epoch * spec.SLOTS_PER_EPOCH + else: + state.slot = (_mock_terminal_block_hash_activation_epoch - 1) * spec.SLOTS_PER_EPOCH + + # Logic from `with_config_overrides` + old_config = spec.config + tmp_config = deepcopy(old_config._asdict()) + tmp_config.update(config_overrides) + config_types = spec.Configuration.__annotations__ + test_config = {k: config_types[k](v) for k, v in tmp_config.items()} + spec.config = spec.Configuration(**test_config) + + # 3. Handle `terminal_pow_block_is_none` + pow_chain = prepare_random_pow_chain(spec, 2) + if terminal_pow_block_is_none: + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY - 1 + else: + if is_terminal_block_hash_set: + pow_chain.head().block_hash = _mock_terminal_block_hash + pow_chain.head().total_difficulty = spec.config.TERMINAL_TOTAL_DIFFICULTY + + # Dummy arguments + finalized_block_hash = b"\x56" * 32 + safe_block_hash = b"\x58" * 32 + suggested_fee_recipient = b"\x78" * 20 + + # Mock execution_engine + class TestEngine(spec.NoopExecutionEngine): + def notify_forkchoice_updated( + self, head_block_hash, safe_block_hash, finalized_block_hash, payload_attributes + ) -> spec.PayloadId | None: + return SAMPLE_PAYLOAD_ID + + payload_id = spec.prepare_execution_payload( + state=state, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + suggested_fee_recipient=suggested_fee_recipient, + execution_engine=TestEngine(), + pow_chain=pow_chain.to_dict(), + ) + assert payload_id == result_payload_id + + # Restore config + spec.config = old_config diff --git a/tests/core/pyspec/eth2spec/test/capella/__init__.py b/tests/core/pyspec/eth2spec/test/capella/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py new file mode 100644 index 0000000000..53cba585a6 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_bls_to_execution_change.py @@ -0,0 +1,288 @@ +from eth2spec.test.context import ( + always_bls, + expect_assertion_error, + spec_state_test, + with_capella_and_later, + with_phases, + with_presets, +) +from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change +from eth2spec.test.helpers.constants import CAPELLA, MAINNET +from eth2spec.test.helpers.keys import pubkeys + + +def run_bls_to_execution_change_processing(spec, state, signed_address_change, valid=True): + """ + Run ``process_bls_to_execution_change``, yielding: + - pre-state ('pre') + - address-change ('address_change') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + # yield pre-state + yield "pre", state + + yield "address_change", signed_address_change + + # If the address_change is invalid, processing is aborted, and there is no post-state. + if not valid: + expect_assertion_error( + lambda: spec.process_bls_to_execution_change(state, signed_address_change) + ) + yield "post", None + return + + # process address change + spec.process_bls_to_execution_change(state, signed_address_change) + + # Make sure the address change has been processed + validator_index = signed_address_change.message.validator_index + validator = state.validators[validator_index] + assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert validator.withdrawal_credentials[1:12] == b"\x00" * 11 + assert ( + validator.withdrawal_credentials[12:] == signed_address_change.message.to_execution_address + ) + + # yield post-state + yield "post", state + + +@with_capella_and_later +@spec_state_test +def test_success(spec, state): + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_not_activated(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch += 4 + validator.activation_epoch = spec.FAR_FUTURE_EPOCH + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert not spec.is_fully_withdrawable_validator( + validator, balance, spec.get_current_epoch(state) + ) + + +@with_capella_and_later +@spec_state_test +def test_success_in_activation_queue(spec, state): + validator_index = 3 + validator = state.validators[validator_index] + validator.activation_eligibility_epoch = spec.get_current_epoch(state) + validator.activation_epoch += 4 + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert not spec.is_fully_withdrawable_validator( + validator, balance, spec.get_current_epoch(state) + ) + + +@with_capella_and_later +@spec_state_test +def test_success_in_exit_queue(spec, state): + validator_index = 3 + spec.initiate_validator_exit(state, validator_index) + + assert spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + assert spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +def test_success_exited(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert not spec.is_fully_withdrawable_validator( + validator, balance, spec.get_current_epoch(state) + ) + + +@with_capella_and_later +@spec_state_test +def test_success_withdrawable(spec, state): + validator_index = 4 + validator = state.validators[validator_index] + validator.exit_epoch = spec.get_current_epoch(state) + validator.withdrawable_epoch = spec.get_current_epoch(state) + + assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) + + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert spec.is_fully_withdrawable_validator(validator, balance, spec.get_current_epoch(state)) + + +@with_capella_and_later +@spec_state_test +def test_invalid_val_index_out_of_range(spec, state): + # Create for one validator beyond the validator list length + signed_address_change = get_signed_address_change( + spec, state, validator_index=len(state.validators) + ) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +def test_invalid_already_0x01(spec, state): + # Create for one validator beyond the validator list length + validator_index = len(state.validators) // 2 + validator = state.validators[validator_index] + validator.withdrawal_credentials = b"\x01" + b"\x00" * 11 + b"\x23" * 20 + signed_address_change = get_signed_address_change(spec, state, validator_index=validator_index) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +def test_invalid_incorrect_from_bls_pubkey(spec, state): + # Create for one validator beyond the validator list length + validator_index = 2 + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + withdrawal_pubkey=pubkeys[0], + ) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +@always_bls +def test_invalid_bad_signature(spec, state): + signed_address_change = get_signed_address_change(spec, state) + # Mutate signature + signed_address_change.signature = spec.BLSSignature(b"\x42" * 96) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +@always_bls +def test_genesis_fork_version(spec, state): + signed_address_change = get_signed_address_change( + spec, state, fork_version=spec.config.GENESIS_FORK_VERSION + ) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) + + +@with_capella_and_later +@spec_state_test +@always_bls +def test_invalid_current_fork_version(spec, state): + signed_address_change = get_signed_address_change( + spec, state, fork_version=state.fork.current_version + ) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +@always_bls +def test_invalid_previous_fork_version(spec, state): + signed_address_change = get_signed_address_change( + spec, state, fork_version=state.fork.previous_version + ) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_capella_and_later +@spec_state_test +@always_bls +def test_invalid_genesis_validators_root(spec, state): + signed_address_change = get_signed_address_change( + spec, state, genesis_validators_root=b"\x99" * 32 + ) + + yield from run_bls_to_execution_change_processing( + spec, state, signed_address_change, valid=False + ) + + +@with_phases([CAPELLA]) +@with_presets([MAINNET], reason="use mainnet fork version") +@spec_state_test +@always_bls +def test_valid_signature_from_staking_deposit_cli(spec, state): + validator_index = 1 + from_bls_pubkey = bytes.fromhex( + "86248e64705987236ec3c41f6a81d96f98e7b85e842a1d71405b216fa75a9917512f3c94c85779a9729c927ea2aa9ed1" + ) # noqa: E501 + to_execution_address = bytes.fromhex("3434343434343434343434343434343434343434") + signature = bytes.fromhex( + "8cf4219884b326a04f6664b680cd9a99ad70b5280745af1147477aa9f8b4a2b2b38b8688c6a74a06f275ad4e14c5c0c70e2ed37a15ece5bf7c0724a376ad4c03c79e14dd9f633a3d54abc1ce4e73bec3524a789ab9a69d4d06686a8a67c9e4dc" + ) # noqa: E501 + + # Use mainnet `genesis_validators_root` + state.genesis_validators_root = bytes.fromhex( + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" + ) + validator = state.validators[validator_index] + validator.withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(from_bls_pubkey)[1:] + + address_change = spec.BLSToExecutionChange( + validator_index=validator_index, + from_bls_pubkey=from_bls_pubkey, + to_execution_address=to_execution_address, + ) + signed_address_change = spec.SignedBLSToExecutionChange( + message=address_change, + signature=signature, + ) + + yield from run_bls_to_execution_change_processing(spec, state, signed_address_change) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py new file mode 100644 index 0000000000..a435697022 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_deposit.py @@ -0,0 +1,62 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + EIP7732, +) +from eth2spec.test.helpers.deposits import ( + prepare_state_and_deposit, + run_deposit_processing, +) +from eth2spec.test.helpers.forks import is_post_electra +from eth2spec.test.helpers.state import next_epoch_via_block +from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_success_top_up_to_withdrawn_validator(spec, state): + validator_index = 0 + + # Fully withdraw validator + set_validator_fully_withdrawable(spec, state, validator_index) + assert state.balances[validator_index] > 0 + next_epoch_via_block(spec, state) + assert state.balances[validator_index] == 0 + assert state.validators[validator_index].effective_balance > 0 + next_epoch_via_block(spec, state) + assert state.validators[validator_index].effective_balance == 0 + + # Make a top-up balance to validator + amount = spec.MAX_EFFECTIVE_BALANCE // 4 + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + yield from run_deposit_processing(spec, state, deposit, validator_index) + + if is_post_electra(spec): + pending_deposits_len = len(state.pending_deposits) + pending_deposit = state.pending_deposits[pending_deposits_len - 1] + assert pending_deposit.pubkey == deposit.data.pubkey + assert pending_deposit.withdrawal_credentials == deposit.data.withdrawal_credentials + assert pending_deposit.amount == deposit.data.amount + assert pending_deposit.signature == deposit.data.signature + assert pending_deposit.slot == spec.GENESIS_SLOT + else: + assert state.balances[validator_index] == amount + assert state.validators[validator_index].effective_balance == 0 + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + current_epoch = spec.get_current_epoch(state) + + if is_post_electra(spec): + has_execution_withdrawal = spec.has_execution_withdrawal_credential(validator) + is_withdrawable = validator.withdrawable_epoch <= current_epoch + has_non_zero_balance = pending_deposit.amount > 0 + # NOTE: directly compute `is_fully_withdrawable_validator` conditions here + # to work around how the epoch processing changed balance updates + assert has_execution_withdrawal and is_withdrawable and has_non_zero_balance + else: + assert spec.is_fully_withdrawable_validator(validator, balance, current_epoch) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_execution_payload.py new file mode 100644 index 0000000000..4b8df432eb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_execution_payload.py @@ -0,0 +1,26 @@ +from eth2spec.test.bellatrix.block_processing.test_process_execution_payload import ( + run_execution_payload_processing, +) +from eth2spec.test.context import ( + spec_state_test, + with_capella_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_state_with_incomplete_transition, + compute_el_block_hash, +) +from eth2spec.test.helpers.state import next_slot + + +@with_capella_and_later +@spec_state_test +def test_invalid_bad_parent_hash_first_payload(spec, state): + state = build_state_with_incomplete_transition(spec, state) + next_slot(spec, state) + + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.parent_hash = b"\x55" * 32 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py new file mode 100644 index 0000000000..27763234e8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/block_processing/test_process_withdrawals.py @@ -0,0 +1,1016 @@ +import random + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_capella_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + EIP7732, + MAINNET, + MINIMAL, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, +) +from eth2spec.test.helpers.random import ( + randomize_state, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, +) +from eth2spec.test.helpers.withdrawals import ( + get_expected_withdrawals, + prepare_expected_withdrawals, + run_withdrawals_processing, + set_eth1_withdrawal_credential_with_balance, + set_validator_fully_withdrawable, + set_validator_partially_withdrawable, +) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_success_zero_expected_withdrawals(spec, state): + assert len(get_expected_withdrawals(spec, state)) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_success_one_full_withdrawal(spec, state): + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_full_withdrawals=1 + ) + assert len(fully_withdrawable_indices) == 1 + assert len(partial_withdrawals_indices) == 0 + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_success_one_partial_withdrawal(spec, state): + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_partial_withdrawals=1 + ) + assert len(fully_withdrawable_indices) == 0 + assert len(partial_withdrawals_indices) == 1 + for index in partial_withdrawals_indices: + assert state.balances[index] > spec.MAX_EFFECTIVE_BALANCE + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_success_mixed_fully_and_partial_withdrawable(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 2 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD - num_full_withdrawals + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=num_full_withdrawals, + num_partial_withdrawals=num_partial_withdrawals, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@with_presets([MAINNET], reason="too few validators with minimal config") +@spec_state_test +def test_success_all_fully_withdrawable_in_one_sweep(spec, state): + assert len(state.validators) <= spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + + withdrawal_count = len(state.validators) + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_full_withdrawals=withdrawal_count + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@with_presets([MINIMAL], reason="too many validators with mainnet config") +@spec_state_test +def test_success_all_fully_withdrawable(spec, state): + assert len(state.validators) > spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + + withdrawal_count = spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_full_withdrawals=withdrawal_count + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@with_presets([MAINNET], reason="too few validators with minimal config") +@spec_state_test +def test_success_all_partially_withdrawable_in_one_sweep(spec, state): + assert len(state.validators) <= spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + + withdrawal_count = len(state.validators) + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_partial_withdrawals=withdrawal_count + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@with_presets([MINIMAL], reason="too many validators with mainnet config") +@spec_state_test +def test_success_all_partially_withdrawable(spec, state): + assert len(state.validators) > spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + + withdrawal_count = spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_partial_withdrawals=withdrawal_count + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +# +# Failure cases in which the number of withdrawals in the execution_payload is incorrect +# + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_non_withdrawable_non_empty_withdrawals(spec, state): + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + withdrawal = spec.Withdrawal( + index=0, + validator_index=0, + address=b"\x30" * 20, + amount=420, + ) + execution_payload.withdrawals.append(withdrawal) + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_one_expected_full_withdrawal_and_none_in_withdrawals(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = [] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_one_expected_partial_withdrawal_and_none_in_withdrawals(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_partial_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = [] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_one_expected_full_withdrawal_and_duplicate_in_withdrawals(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=2) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy()) + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_two_expected_partial_withdrawal_and_duplicate_in_withdrawals(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_partial_withdrawals=2) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals.append(execution_payload.withdrawals[0].copy()) + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_max_per_slot_full_withdrawals_and_one_less_in_withdrawals(spec, state): + prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_max_per_slot_partial_withdrawals_and_one_less_in_withdrawals(spec, state): + prepare_expected_withdrawals( + spec, state, rng=random.Random(42), num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_a_lot_fully_withdrawable_too_few_in_withdrawals(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_a_lot_partially_withdrawable_too_few_in_withdrawals(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_a_lot_mixed_withdrawable_in_queue_too_few_in_withdrawals(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals = execution_payload.withdrawals[:-1] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +# +# Failure cases in which the withdrawals in the execution_payload are incorrect +# + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_incorrect_withdrawal_index(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].index += 1 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_incorrect_address_full(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].address = b"\xff" * 20 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_incorrect_address_partial(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_partial_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].address = b"\xff" * 20 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_incorrect_amount_full(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].amount += 1 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_incorrect_amount_partial(spec, state): + prepare_expected_withdrawals(spec, state, rng=random.Random(42), num_full_withdrawals=1) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.withdrawals[0].amount += 1 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_one_of_many_incorrectly_full(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + num_withdrawals = len(execution_payload.withdrawals) + + # Pick withdrawal in middle of list and mutate + withdrawal = execution_payload.withdrawals[num_withdrawals // 2] + withdrawal.index += 1 + withdrawal.address = b"\x99" * 20 + withdrawal.amount += 4000000 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_one_of_many_incorrectly_partial(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + num_withdrawals = len(execution_payload.withdrawals) + + # Pick withdrawal in middle of list and mutate + withdrawal = execution_payload.withdrawals[num_withdrawals // 2] + withdrawal.index += 1 + withdrawal.address = b"\x99" * 20 + withdrawal.amount += 4000000 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_many_incorrectly_full(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + for i, withdrawal in enumerate(execution_payload.withdrawals): + if i % 3 == 0: + withdrawal.index += 1 + elif i % 3 == 1: + withdrawal.address = i.to_bytes(20, "big") + else: + withdrawal.amount += 1 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_many_incorrectly_partial(spec, state): + prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 4, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + for i, withdrawal in enumerate(execution_payload.withdrawals): + if i % 3 == 0: + withdrawal.index += 1 + elif i % 3 == 1: + withdrawal.address = i.to_bytes(20, "big") + else: + withdrawal.amount += 1 + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + yield from run_withdrawals_processing(spec, state, execution_payload, valid=False) + + +# +# More full withdrawal cases +# + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_withdrawable_epoch_but_0_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 10000000000 + state.balances[0] = 0 + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_withdrawable_epoch_but_0_effective_balance_0_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 0 + state.balances[0] = 0 + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_withdrawable_epoch_but_0_effective_balance_nonzero_balance(spec, state): + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, 0, current_epoch) + + state.validators[0].effective_balance = 0 + state.balances[0] = 100000000 + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_no_withdrawals_but_some_next_epoch(spec, state): + current_epoch = spec.get_current_epoch(state) + + # Make a few validators withdrawable at the *next* epoch + for index in range(3): + set_validator_fully_withdrawable(spec, state, index, current_epoch + 1) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_all_withdrawal(spec, state): + # Make all validators withdrawable + for index in range(len(state.validators)): + set_validator_fully_withdrawable(spec, state, index) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + +def run_random_full_withdrawals_test(spec, state, rng): + randomize_state(spec, state, rng) + for index in range(len(state.validators)): + # 50% withdrawable + if rng.choice([True, False]): + set_validator_fully_withdrawable(spec, state, index) + validator = state.validators[index] + # 12.5% unset credentials + if rng.randint(0, 7) == 0: + validator.withdrawal_credentials = ( + spec.BLS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + ) + # 12.5% not enough balance + if rng.randint(0, 7) == 0: + state.balances[index] = 0 + # 12.5% not close enough epoch + if rng.randint(0, 7) == 0: + validator.withdrawable_epoch += 1 + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_random_full_withdrawals_0(spec, state): + yield from run_random_full_withdrawals_test(spec, state, random.Random(444)) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_random_full_withdrawals_1(spec, state): + yield from run_random_full_withdrawals_test(spec, state, random.Random(420)) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_random_full_withdrawals_2(spec, state): + yield from run_random_full_withdrawals_test(spec, state, random.Random(200)) + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_random_full_withdrawals_3(spec, state): + yield from run_random_full_withdrawals_test(spec, state, random.Random(2000000)) + + +# +# More partial withdrawal cases +# + + +@with_capella_and_later +@spec_state_test +def test_success_no_max_effective_balance(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator's effective balance must be maxed out + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + # Reduce validator's effective balance to make it ineligible for withdrawals + effective_balance=spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT, + # Give the validator an excess balance, so this isn't the reason it fails + balance=spec.MAX_EFFECTIVE_BALANCE + 1, + ) + validator = state.validators[validator_index] + + assert ( + validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + ) + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_no_excess_balance(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator needs an excess balance + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + # Ensure validator has the required effective balance, so this isn't the reason it fails + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + # Remove validator's excess balance to make it ineligible for withdrawals + balance=spec.MAX_EFFECTIVE_BALANCE, + ) + validator = state.validators[validator_index] + + assert validator.effective_balance == spec.MAX_EFFECTIVE_BALANCE + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_excess_balance_but_no_max_effective_balance(spec, state): + validator_index = len(state.validators) // 2 + set_validator_partially_withdrawable(spec, state, validator_index) + validator = state.validators[validator_index] + + # To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance + validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1 + + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_one_partial_withdrawable_not_yet_active(spec, state): + validator_index = min(len(state.validators) // 2, spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - 1) + state.validators[validator_index].activation_epoch += 4 + set_validator_partially_withdrawable(spec, state, validator_index) + + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_one_partial_withdrawable_in_exit_queue(spec, state): + validator_index = min(len(state.validators) // 2, spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - 1) + state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + 1 + set_validator_partially_withdrawable(spec, state, validator_index) + + assert spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + 1 + ) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_one_partial_withdrawable_exited(spec, state): + validator_index = min(len(state.validators) // 2, spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - 1) + state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + set_validator_partially_withdrawable(spec, state, validator_index) + + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_one_partial_withdrawable_active_and_slashed(spec, state): + validator_index = min(len(state.validators) // 2, spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - 1) + state.validators[validator_index].slashed = True + set_validator_partially_withdrawable(spec, state, validator_index) + + assert spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_one_partial_withdrawable_exited_and_slashed(spec, state): + validator_index = min(len(state.validators) // 2, spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP - 1) + state.validators[validator_index].slashed = True + state.validators[validator_index].exit_epoch = spec.get_current_epoch(state) + set_validator_partially_withdrawable(spec, state, validator_index) + + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=1 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_two_partial_withdrawable(spec, state): + set_validator_partially_withdrawable(spec, state, 0) + set_validator_partially_withdrawable(spec, state, 1) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=2 + ) + + +@with_capella_and_later +@spec_state_test +def test_success_max_partial_withdrawable(spec, state): + # Sanity check that this test works for this state + assert len(state.validators) >= spec.MAX_WITHDRAWALS_PER_PAYLOAD + + for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD): + set_validator_partially_withdrawable(spec, state, i) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + +@with_capella_and_later +@with_presets([MINIMAL], reason="not enough validators with mainnet config") +@spec_state_test +def test_success_max_plus_one_withdrawable(spec, state): + # Sanity check that this test works for this state + assert len(state.validators) >= spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1 + + # More than MAX_WITHDRAWALS_PER_PAYLOAD partially withdrawable + for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1): + set_validator_partially_withdrawable(spec, state, i) + + execution_payload = build_empty_execution_payload(spec, state) + + # Should only have MAX_WITHDRAWALS_PER_PAYLOAD withdrawals created + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + +def run_random_partial_withdrawals_test(spec, state, rng): + for _ in range(rng.randint(0, 2)): + next_epoch(spec, state) + randomize_state(spec, state, rng) + + num_validators = len(state.validators) + state.next_withdrawal_validator_index = rng.randint(0, num_validators - 1) + + num_partially_withdrawable = rng.randint(0, num_validators - 1) + partially_withdrawable_indices = rng.sample(range(num_validators), num_partially_withdrawable) + for index in partially_withdrawable_indices: + set_validator_partially_withdrawable( + spec, state, index, excess_balance=rng.randint(1, 1000000000) + ) + + execution_payload = build_empty_execution_payload(spec, state) + + # Note: due to the randomness and other block processing, some of these set as "partially withdrawable" + # may not be partially withdrawable once we get to ``process_withdrawals``, + # thus *not* using the optional third param in this call + yield from run_withdrawals_processing(spec, state, execution_payload) + + +@with_capella_and_later +@spec_state_test +def test_random_0(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(0)) + + +@with_capella_and_later +@spec_state_test +def test_random_partial_withdrawals_1(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(1)) + + +@with_capella_and_later +@spec_state_test +def test_random_partial_withdrawals_2(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(2)) + + +@with_capella_and_later +@spec_state_test +def test_random_partial_withdrawals_3(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(3)) + + +@with_capella_and_later +@spec_state_test +def test_random_partial_withdrawals_4(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(4)) + + +@with_capella_and_later +@spec_state_test +def test_random_partial_withdrawals_5(spec, state): + yield from run_random_partial_withdrawals_test(spec, state, random.Random(5)) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_max_plus_one(spec, state): + """Test legacy validator with balance just above MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + balance=spec.MAX_EFFECTIVE_BALANCE + 1, + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index], + ) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_exact_max(spec, state): + """Test legacy validator whose balance is exactly MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + + +@with_capella_and_later +@spec_state_test +def test_partially_withdrawable_validator_legacy_max_minus_one(spec, state): + """Test legacy validator whose balance is below MAX_EFFECTIVE_BALANCE""" + validator_index = 0 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + # Assume effective balance updates haven't happened yet + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + balance=spec.MAX_EFFECTIVE_BALANCE - 1, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_historical_summaries_update.py b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_historical_summaries_update.py new file mode 100644 index 0000000000..f17d332be1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/epoch_processing/test_process_historical_summaries_update.py @@ -0,0 +1,24 @@ +from eth2spec.test.context import ( + spec_state_test, + with_capella_and_later, +) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with + + +def run_process_historical_summaries_update(spec, state): + yield from run_epoch_processing_with(spec, state, "process_historical_summaries_update") + + +@with_capella_and_later +@spec_state_test +def test_historical_summaries_accumulator(spec, state): + # skip ahead to near the end of the historical batch period (excl block before epoch processing) + state.slot = spec.SLOTS_PER_HISTORICAL_ROOT - 1 + pre_historical_summaries = state.historical_summaries.copy() + + yield from run_process_historical_summaries_update(spec, state) + + assert len(state.historical_summaries) == len(pre_historical_summaries) + 1 + summary = state.historical_summaries[len(state.historical_summaries) - 1] + assert summary.block_summary_root == state.block_roots.hash_tree_root() + assert summary.state_summary_root == state.state_roots.hash_tree_root() diff --git a/tests/core/pyspec/eth2spec/test/capella/fork/__init__.py b/tests/core/pyspec/eth2spec/test/capella/fork/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py new file mode 100644 index 0000000000..85918bf6ed --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_basic.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.capella.fork import ( + CAPELLA_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, + MINIMAL, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[CAPELLA], state) diff --git a/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_random.py b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_random.py new file mode 100644 index 0000000000..b3cdb9ba2b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/fork/test_capella_fork_random.py @@ -0,0 +1,94 @@ +from random import Random + +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.capella.fork import ( + CAPELLA_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.constants import ( + BELLATRIX, + CAPELLA, + MINIMAL, +) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_state +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@spec_test +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[CAPELLA], state) + + +@with_phases(phases=[BELLATRIX], other_phases=[CAPELLA]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(CAPELLA_FORK_TEST_META_TAGS) +def test_capella_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[CAPELLA], state) diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/__init__.py b/tests/core/pyspec/eth2spec/test/capella/light_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/test_data_collection.py b/tests/core/pyspec/eth2spec/test/capella/light_client/test_data_collection.py new file mode 100644 index 0000000000..54ae718808 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_data_collection.py @@ -0,0 +1,49 @@ +from eth2spec.test.context import ( + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.light_client_data_collection import ( + run_lc_data_collection_test_multi_fork, +) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB, ELECTRA]) +@spec_test +@with_config_overrides( + { + "DENEB_FORK_EPOCH": 1 * 8, # SyncCommitteePeriod 1 + "ELECTRA_FORK_EPOCH": 2 * 8, # SyncCommitteePeriod 2 + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_electra_reorg_aligned(spec, phases, state): + yield from run_lc_data_collection_test_multi_fork(spec, phases, state, DENEB, ELECTRA) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB, ELECTRA]) +@spec_test +@with_config_overrides( + { + "DENEB_FORK_EPOCH": 1 * 8 + 4, # SyncCommitteePeriod 1 (+ 4 epochs) + "ELECTRA_FORK_EPOCH": 3 * 8 + 4, # SyncCommitteePeriod 3 (+ 4 epochs) + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_electra_reorg_unaligned(spec, phases, state): + yield from run_lc_data_collection_test_multi_fork(spec, phases, state, DENEB, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py new file mode 100644 index 0000000000..5e45c169a7 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_single_merkle_proof.py @@ -0,0 +1,38 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_test_suite_name, +) +from eth2spec.test.helpers.attestations import ( + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + EIP7732, +) + + +@with_test_suite_name("BeaconBlockBody") +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_execution_merkle_proof(spec, state): + block = state_transition_with_full_block(spec, state, True, False) + + yield "object", block.message.body + gindex = spec.EXECUTION_PAYLOAD_GINDEX + branch = spec.compute_merkle_proof(block.message.body, gindex) + yield ( + "proof", + { + "leaf": "0x" + block.message.body.execution_payload.hash_tree_root().hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in branch], + }, + ) + assert spec.is_valid_merkle_branch( + leaf=block.message.body.execution_payload.hash_tree_root(), + branch=branch, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=block.message.body.hash_tree_root(), + ) diff --git a/tests/core/pyspec/eth2spec/test/capella/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/capella/light_client/test_sync.py new file mode 100644 index 0000000000..5a916c4a01 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/light_client/test_sync.py @@ -0,0 +1,49 @@ +from eth2spec.test.context import ( + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.light_client_sync import ( + run_lc_sync_test_multi_fork, + run_lc_sync_test_single_fork, +) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_config_overrides( + { + "DENEB_FORK_EPOCH": 3, # Test setup advances to epoch 2 + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=DENEB) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_fork(spec, phases, state): + yield from run_lc_sync_test_single_fork(spec, phases, state, DENEB) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB, ELECTRA]) +@spec_test +@with_config_overrides( + { + "DENEB_FORK_EPOCH": 3, # Test setup advances to epoch 2 + "ELECTRA_FORK_EPOCH": 4, + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_deneb_electra_fork(spec, phases, state): + yield from run_lc_sync_test_multi_fork(spec, phases, state, DENEB, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/capella/random/__init__.py b/tests/core/pyspec/eth2spec/test/capella/random/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/random/test_random.py b/tests/core/pyspec/eth2spec/test/capella/random/test_random.py new file mode 100644 index 0000000000..386b26893e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/random/test_random.py @@ -0,0 +1,1172 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.context import ( + always_bls, + misc_balances_in_default_range_with_many_validators, + only_generator, + single_phase, + spec_test, + with_custom_state, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.constants import CAPELLA +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([CAPELLA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_capella + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_capella", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_capella", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/capella/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/capella/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py new file mode 100644 index 0000000000..31639f3aff --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/capella/sanity/test_blocks.py @@ -0,0 +1,552 @@ +import random + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_capella_and_later, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + next_epoch_with_attestations, +) +from eth2spec.test.helpers.block import ( + build_empty_block, + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change +from eth2spec.test.helpers.constants import ( + CAPELLA, + EIP7732, + MINIMAL, +) +from eth2spec.test.helpers.deposits import ( + prepare_state_and_deposit, +) +from eth2spec.test.helpers.forks import is_post_eip7732, is_post_electra +from eth2spec.test.helpers.keys import pubkeys +from eth2spec.test.helpers.state import ( + next_epoch_via_block, + next_slot, + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits +from eth2spec.test.helpers.withdrawals import ( + get_expected_withdrawals, + prepare_expected_withdrawals, + set_eth1_withdrawal_credential_with_balance, + set_validator_fully_withdrawable, + set_validator_partially_withdrawable, +) + +# +# `is_execution_enabled` has been removed from Capella +# + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_invalid_is_execution_enabled_false(spec, state): + # Set `latest_execution_payload_header` to empty + state.latest_execution_payload_header = spec.ExecutionPayloadHeader() + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + # Set `execution_payload` to empty + block.body.execution_payload = spec.ExecutionPayload() + assert len(block.body.execution_payload.transactions) == 0 + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +# +# BLSToExecutionChange +# + + +@with_capella_and_later +@spec_state_test +def test_bls_change(spec, state): + index = 0 + signed_address_change = get_signed_address_change(spec, state, validator_index=index) + pre_credentials = state.validators[index].withdrawal_credentials + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.bls_to_execution_changes.append(signed_address_change) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + post_credentials = state.validators[index].withdrawal_credentials + assert pre_credentials != post_credentials + assert post_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert post_credentials[1:12] == b"\x00" * 11 + assert post_credentials[12:] == signed_address_change.message.to_execution_address + + +@with_capella_and_later +@spec_state_test +def test_deposit_and_bls_change(spec, state): + initial_registry_len = len(state.validators) + initial_balances_len = len(state.balances) + + validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + withdrawal_pubkey=deposit.data.pubkey, # Deposit helper defaults to use pubkey as withdrawal credential + ) + + deposit_credentials = deposit.data.withdrawal_credentials + assert deposit_credentials[:1] == spec.BLS_WITHDRAWAL_PREFIX + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.deposits.append(deposit) + block.body.bls_to_execution_changes.append(signed_address_change) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert len(state.validators) == initial_registry_len + 1 + assert len(state.balances) == initial_balances_len + 1 + validator_credentials = state.validators[validator_index].withdrawal_credentials + assert deposit_credentials != validator_credentials + assert validator_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + assert validator_credentials[1:12] == b"\x00" * 11 + assert validator_credentials[12:] == signed_address_change.message.to_execution_address + + +@with_capella_and_later +@spec_state_test +def test_exit_and_bls_change(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + index = 0 + signed_address_change = get_signed_address_change(spec, state, validator_index=index) + signed_exit = prepare_signed_exits(spec, state, [index])[0] + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.voluntary_exits.append(signed_exit) + block.body.bls_to_execution_changes.append(signed_address_change) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + validator = state.validators[index] + balance = state.balances[index] + current_epoch = spec.get_current_epoch(state) + assert not spec.is_fully_withdrawable_validator(validator, balance, current_epoch) + assert validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH + assert spec.is_fully_withdrawable_validator(validator, balance, validator.withdrawable_epoch) + + +@with_capella_and_later +@spec_state_test +def test_invalid_duplicate_bls_changes_same_block(spec, state): + index = 0 + signed_address_change = get_signed_address_change(spec, state, validator_index=index) + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + # Double BLSToExecutionChange of the same validator + for _ in range(2): + block.body.bls_to_execution_changes.append(signed_address_change) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +@with_capella_and_later +@spec_state_test +def test_invalid_two_bls_changes_of_different_addresses_same_validator_same_block(spec, state): + index = 0 + + signed_address_change_1 = get_signed_address_change( + spec, state, validator_index=index, to_execution_address=b"\x12" * 20 + ) + signed_address_change_2 = get_signed_address_change( + spec, state, validator_index=index, to_execution_address=b"\x34" * 20 + ) + assert signed_address_change_1 != signed_address_change_2 + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + block.body.bls_to_execution_changes.append(signed_address_change_1) + block.body.bls_to_execution_changes.append(signed_address_change_2) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + +# +# Withdrawals +# + + +@with_all_phases_from_except(CAPELLA, [EIP7732]) +@spec_state_test +def test_full_withdrawal_in_epoch_transition(spec, state): + index = 0 + current_epoch = spec.get_current_epoch(state) + set_validator_fully_withdrawable(spec, state, index, current_epoch) + assert len(get_expected_withdrawals(spec, state)) == 1 + + yield "pre", state + + # trigger epoch transition + block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert state.balances[index] == 0 + assert len(get_expected_withdrawals(spec, state)) == 0 + + +@with_capella_and_later +@spec_state_test +def test_partial_withdrawal_in_epoch_transition(spec, state): + index = state.next_withdrawal_index + set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000) + pre_balance = state.balances[index] + + assert len(get_expected_withdrawals(spec, state)) == 1 + + yield "pre", state + + # trigger epoch transition + block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert state.balances[index] < pre_balance + # Potentially less than due to sync committee penalty + assert state.balances[index] <= spec.MAX_EFFECTIVE_BALANCE + assert len(get_expected_withdrawals(spec, state)) == 0 + + +@with_capella_and_later +@spec_state_test +def test_many_partial_withdrawals_in_epoch_transition(spec, state): + assert len(state.validators) > spec.MAX_WITHDRAWALS_PER_PAYLOAD + + for i in range(spec.MAX_WITHDRAWALS_PER_PAYLOAD + 1): + index = (i + state.next_withdrawal_index) % len(state.validators) + set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000000) + + assert len(get_expected_withdrawals(spec, state)) == spec.MAX_WITHDRAWALS_PER_PAYLOAD + + yield "pre", state + + # trigger epoch transition + block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert len(get_expected_withdrawals(spec, state)) == 1 + + +def _perform_valid_withdrawal(spec, state): + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_partial_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2, + num_full_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2, + ) + + next_slot(spec, state) + pre_next_withdrawal_index = state.next_withdrawal_index + + expected_withdrawals = get_expected_withdrawals(spec, state) + + pre_state = state.copy() + + # Block 1 + block = build_empty_block_for_next_slot(spec, state) + signed_block_1 = state_transition_and_sign_block(spec, state, block) + + withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals] + fully_withdrawable_indices = list( + set(fully_withdrawable_indices).difference(set(withdrawn_indices)) + ) + partial_withdrawals_indices = list( + set(partial_withdrawals_indices).difference(set(withdrawn_indices)) + ) + assert ( + state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + withdrawn_indices = [withdrawal.validator_index for withdrawal in expected_withdrawals] + fully_withdrawable_indices = list( + set(fully_withdrawable_indices).difference(set(withdrawn_indices)) + ) + partial_withdrawals_indices = list( + set(partial_withdrawals_indices).difference(set(withdrawn_indices)) + ) + assert ( + state.next_withdrawal_index == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + + return pre_state, signed_block_1, pre_next_withdrawal_index + + +@with_capella_and_later +@spec_state_test +def test_withdrawal_success_two_blocks(spec, state): + pre_state, signed_block_1, pre_next_withdrawal_index = _perform_valid_withdrawal(spec, state) + + yield "pre", pre_state + + # Block 2 + block = build_empty_block_for_next_slot(spec, state) + signed_block_2 = state_transition_and_sign_block(spec, state, block) + + # after EIP-7732 the second block does not perform any withdrawals because + # there was no payload processed + if is_post_eip7732(spec): + assert ( + state.next_withdrawal_index + == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD + ) + else: + assert ( + state.next_withdrawal_index + == pre_next_withdrawal_index + spec.MAX_WITHDRAWALS_PER_PAYLOAD * 2 + ) + + yield "blocks", [signed_block_1, signed_block_2] + yield "post", state + + +@with_capella_and_later +@spec_state_test +def test_invalid_withdrawal_fail_second_block_payload_isnt_compatible(spec, state): + _perform_valid_withdrawal(spec, state) + + # Block 2 + block = build_empty_block_for_next_slot(spec, state) + + # Modify state.next_withdrawal_index to incorrect number + state.next_withdrawal_index += 1 + + # Only need to output the state transition of signed_block_2 + yield "pre", state + + signed_block_2 = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block_2] + yield "post", None + + +# +# Mix top-ups and withdrawals +# + + +@with_capella_and_later +@spec_state_test +def test_top_up_and_partial_withdrawable_validator(spec, state): + next_withdrawal_validator_index = 0 + validator_index = next_withdrawal_validator_index + 1 + + set_eth1_withdrawal_credential_with_balance( + spec, state, validator_index, balance=spec.MAX_EFFECTIVE_BALANCE + ) + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert not spec.is_partially_withdrawable_validator(validator, balance) + + # Make a top-up balance to validator + amount = spec.MAX_EFFECTIVE_BALANCE // 4 + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.deposits.append(deposit) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + if is_post_electra(spec): + assert state.pending_deposits[0].pubkey == deposit.data.pubkey + assert ( + state.pending_deposits[0].withdrawal_credentials == deposit.data.withdrawal_credentials + ) + assert state.pending_deposits[0].amount == deposit.data.amount + assert state.pending_deposits[0].signature == deposit.data.signature + assert state.pending_deposits[0].slot == spec.GENESIS_SLOT + else: + # Since withdrawals happen before deposits, it becomes partially withdrawable after state transition. + validator = state.validators[validator_index] + balance = state.balances[validator_index] + assert spec.is_partially_withdrawable_validator(validator, balance) + + +@with_capella_and_later +@spec_state_test +def test_top_up_to_fully_withdrawn_validator(spec, state): + """ + Similar to `teste_process_deposit::test_success_top_up_to_withdrawn_validator` test. + """ + next_withdrawal_validator_index = 0 + validator_index = next_withdrawal_validator_index + 1 + + # Fully withdraw validator + set_validator_fully_withdrawable(spec, state, validator_index) + assert state.balances[validator_index] > 0 + next_epoch_via_block(spec, state) + assert state.balances[validator_index] == 0 + assert state.validators[validator_index].effective_balance > 0 + next_epoch_via_block(spec, state) + assert state.validators[validator_index].effective_balance == 0 + + # Make a top-up deposit to validator + amount = spec.MAX_EFFECTIVE_BALANCE // 4 + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.deposits.append(deposit) + + signed_block_1 = state_transition_and_sign_block(spec, state, block) + + balance = state.balances[validator_index] + if is_post_electra(spec): + balance += state.pending_deposits[0].amount + + assert spec.is_fully_withdrawable_validator( + state.validators[validator_index], balance, spec.get_current_epoch(state) + ) + + # Apply an empty block + block = build_empty_block_for_next_slot(spec, state) + signed_block_2 = state_transition_and_sign_block(spec, state, block) + + # With mainnet preset, it holds + if len(state.validators) <= spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP: + assert not spec.is_fully_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index], + spec.get_current_epoch(state), + ) + + yield "blocks", [signed_block_1, signed_block_2] + yield "post", state + + +def _insert_validator(spec, state, balance): + effective_balance = ( + balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT + if balance < spec.MAX_EFFECTIVE_BALANCE + else spec.MAX_EFFECTIVE_BALANCE + ) + + validator_index = len(state.validators) + validator = spec.Validator( + pubkey=pubkeys[validator_index], + withdrawal_credentials=spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x56" * 20, + activation_eligibility_epoch=1, + activation_epoch=2, + exit_epoch=spec.FAR_FUTURE_EPOCH, + withdrawable_epoch=spec.FAR_FUTURE_EPOCH, + effective_balance=effective_balance, + ) + state.validators.append(validator) + state.balances.append(balance) + state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(0) + + return validator_index + + +def _run_activate_and_partial_withdrawal(spec, state, initial_balance): + validator_index = _insert_validator(spec, state, balance=initial_balance) + + # To make it eligible activation + transition_to(spec, state, spec.compute_start_slot_at_epoch(2) - 1) + assert not spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + yield "pre", state + + blocks = [] + # To activate + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(signed_block) + + assert spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ) + + if initial_balance > spec.MAX_EFFECTIVE_BALANCE: + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + else: + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + _, new_blocks, state = next_epoch_with_attestations(spec, state, True, True) + blocks += new_blocks + + yield "blocks", blocks + yield "post", state + + +@with_capella_and_later +@with_presets([MINIMAL], reason="too many validators with mainnet config") +@spec_state_test +def test_activate_and_partial_withdrawal_max_effective_balance(spec, state): + yield from _run_activate_and_partial_withdrawal( + spec, state, initial_balance=spec.MAX_EFFECTIVE_BALANCE + ) + + +@with_capella_and_later +@with_presets([MINIMAL], reason="too many validators with mainnet config") +@spec_state_test +def test_activate_and_partial_withdrawal_overdeposit(spec, state): + yield from _run_activate_and_partial_withdrawal( + spec, state, initial_balance=spec.MAX_EFFECTIVE_BALANCE + 10000000 + ) diff --git a/tests/core/pyspec/eth2spec/test/conftest.py b/tests/core/pyspec/eth2spec/test/conftest.py index e6d8352e03..bf5064214b 100644 --- a/tests/core/pyspec/eth2spec/test/conftest.py +++ b/tests/core/pyspec/eth2spec/test/conftest.py @@ -1,4 +1,7 @@ +import pytest + from eth2spec.test import context +from eth2spec.test.helpers.constants import ALL_PHASES, ALLOWED_TEST_RUNNER_FORKS from eth2spec.utils import bls as bls_utils # We import pytest only when it's present, i.e. when we are running tests. @@ -16,27 +19,58 @@ def module_exists(module_name): def fixture(*args, **kwargs): if module_exists("pytest"): - import pytest return pytest.fixture(*args, **kwargs) else: + def ignore(): pass + return ignore def pytest_addoption(parser): parser.addoption( - "--preset", action="store", type=str, default="minimal", - help="preset: make the pyspec use the specified preset" + "--preset", + action="store", + type=str, + default="minimal", + help="preset: make the pyspec use the specified preset", ) parser.addoption( - "--disable-bls", action="store_true", default=False, - help="bls-default: make tests that are not dependent on BLS run without BLS" + "--fork", + action="append", + type=str, + help=( + "fork: make the pyspec only run with the specified phase." + " To run multiple phases, e.g., --fork=phase0 --fork=altair" + ), ) parser.addoption( - "--bls-type", action="store", type=str, default="py_ecc", choices=["py_ecc", "milagro"], - help="bls-type: use 'pyecc' or 'milagro' implementation for BLS" + "--disable-bls", + action="store_true", + default=False, + help="bls-default: make tests that are not dependent on BLS run without BLS", ) + parser.addoption( + "--bls-type", + action="store", + type=str, + default="fastest", + choices=["py_ecc", "milagro", "arkworks", "fastest"], + help=( + "bls-type: use specified BLS implementation;" + "fastest: use milagro for signatures and arkworks for everything else (e.g. KZG)" + ), + ) + + +def _validate_fork_name(forks): + for fork in forks: + if fork not in set(ALLOWED_TEST_RUNNER_FORKS): + raise ValueError( + f'The given --fork argument "{fork}" is not an available fork.' + f" The available forks: {ALLOWED_TEST_RUNNER_FORKS}" + ) @fixture(autouse=True) @@ -44,6 +78,17 @@ def preset(request): context.DEFAULT_TEST_PRESET = request.config.getoption("--preset") +@fixture(autouse=True) +def run_phases(request): + forks = request.config.getoption("--fork", default=None) + if forks: + forks = [fork.lower() for fork in forks] + _validate_fork_name(forks) + context.DEFAULT_PYTEST_FORKS = set(forks) + else: + context.DEFAULT_PYTEST_FORKS = ALL_PHASES + + @fixture(autouse=True) def bls_default(request): disable_bls = request.config.getoption("--disable-bls") @@ -58,5 +103,9 @@ def bls_type(request): bls_utils.use_py_ecc() elif bls_type == "milagro": bls_utils.use_milagro() + elif bls_type == "arkworks": + bls_utils.use_arkworks() + elif bls_type == "fastest": + bls_utils.use_fastest() else: raise Exception(f"unrecognized bls type: {bls_type}") diff --git a/tests/core/pyspec/eth2spec/test/context.py b/tests/core/pyspec/eth2spec/test/context.py index ef92efaded..214f128fc1 100644 --- a/tests/core/pyspec/eth2spec/test/context.py +++ b/tests/core/pyspec/eth2spec/test/context.py @@ -1,93 +1,83 @@ -import pytest +import importlib +from collections.abc import Callable, Sequence from copy import deepcopy -from eth2spec.phase0 import mainnet as spec_phase0_mainnet, minimal as spec_phase0_minimal -from eth2spec.altair import mainnet as spec_altair_mainnet, minimal as spec_altair_minimal -from eth2spec.merge import mainnet as spec_merge_mainnet, minimal as spec_merge_minimal +from dataclasses import dataclass +from random import Random +from typing import Any + +import pytest +from lru import LRU + from eth2spec.utils import bls from .exceptions import SkippedTest from .helpers.constants import ( - SpecForkName, PresetBaseName, - PHASE0, ALTAIR, MERGE, MINIMAL, MAINNET, - ALL_PHASES, FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + ALL_PHASES, + ALLOWED_TEST_RUNNER_FORKS, + ALTAIR, + BELLATRIX, + CAPELLA, + DENEB, + EIP7441, + ELECTRA, + FULU, + LIGHT_CLIENT_TESTING_FORKS, + MINIMAL, + PHASE0, + POST_FORK_OF, ) +from .helpers.forks import is_post_electra, is_post_fork from .helpers.genesis import create_genesis_state -from .utils import vector_test, with_meta_tags, build_transition_test - -from random import Random -from typing import Any, Callable, Sequence, TypedDict, Protocol, Dict - -from lru import LRU +from .helpers.specs import ( + spec_targets, +) +from .helpers.typing import ( + Spec, + SpecForks, +) +from .utils import ( + vector_test, + with_meta_tags, +) # Without pytest CLI arg or pyspec-test-generator 'preset' argument, this will be the config to apply. DEFAULT_TEST_PRESET = MINIMAL +# Without pytest CLI arg or pyspec-test-generator 'run-phase' argument, this will be the config to apply. +DEFAULT_PYTEST_FORKS = ALL_PHASES -# TODO: currently phases are defined as python modules. -# It would be better if they would be more well-defined interfaces for stronger typing. - -class Configuration(Protocol): - PRESET_BASE: str +@dataclass(frozen=True) +class ForkMeta: + pre_fork_name: str + post_fork_name: str + fork_epoch: int -class Spec(Protocol): - fork: str - config: Configuration - -class SpecPhase0(Spec): - ... - - -class SpecAltair(Spec): - ... - - -class SpecMerge(Spec): - ... - - -spec_targets: Dict[PresetBaseName, Dict[SpecForkName, Spec]] = { - MINIMAL: { - PHASE0: spec_phase0_minimal, - ALTAIR: spec_altair_minimal, - MERGE: spec_merge_minimal, - }, - MAINNET: { - PHASE0: spec_phase0_mainnet, - ALTAIR: spec_altair_mainnet, - MERGE: spec_merge_mainnet, - }, -} - - -class SpecForks(TypedDict, total=False): - PHASE0: SpecPhase0 - ALTAIR: SpecAltair - MERGE: SpecMerge - - -def _prepare_state(balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int], - spec: Spec, phases: SpecForks): - phase = phases[spec.fork] - balances = balances_fn(phase) - activation_threshold = threshold_fn(phase) - state = create_genesis_state(spec=phase, validator_balances=balances, - activation_threshold=activation_threshold) +def _prepare_state( + balances_fn: Callable[[Any], Sequence[int]], + threshold_fn: Callable[[Any], int], + spec: Spec, + phases: SpecForks, +): + balances = balances_fn(spec) + activation_threshold = threshold_fn(spec) + state = create_genesis_state( + spec=spec, validator_balances=balances, activation_threshold=activation_threshold + ) return state _custom_state_cache_dict = LRU(size=10) -def with_custom_state(balances_fn: Callable[[Any], Sequence[int]], - threshold_fn: Callable[[Any], int]): +def with_custom_state( + balances_fn: Callable[[Any], Sequence[int]], threshold_fn: Callable[[Any], int] +): def deco(fn): - def entry(*args, spec: Spec, phases: SpecForks, **kw): # make a key for the state, unique to the fork + config (incl preset choice) and balances/activations key = (spec.fork, spec.config.__hash__(), spec.__file__, balances_fn, threshold_fn) - global _custom_state_cache_dict if key not in _custom_state_cache_dict: state = _prepare_state(balances_fn, threshold_fn, spec, phases) _custom_state_cache_dict[key] = state.get_backing() @@ -95,21 +85,26 @@ def entry(*args, spec: Spec, phases: SpecForks, **kw): # Take an entry out of the LRU. # No copy is necessary, as we wrap the immutable backing with a new view. state = spec.BeaconState(backing=_custom_state_cache_dict[key]) - kw['state'] = state + kw["state"] = state return fn(*args, spec=spec, phases=phases, **kw) + return entry + return deco -def default_activation_threshold(spec): +def default_activation_threshold(spec: Spec): """ Helper method to use the default balance activation threshold for state creation for tests. Usage: `@with_custom_state(threshold_fn=default_activation_threshold, ...)` """ - return spec.MAX_EFFECTIVE_BALANCE + if is_post_electra(spec): + return spec.MIN_ACTIVATION_BALANCE + else: + return spec.MAX_EFFECTIVE_BALANCE -def zero_activation_threshold(spec): +def zero_activation_threshold(spec: Spec): """ Helper method to use 0 gwei as the activation threshold for state creation for tests. Usage: `@with_custom_state(threshold_fn=zero_activation_threshold, ...)` @@ -117,7 +112,7 @@ def zero_activation_threshold(spec): return 0 -def default_balances(spec): +def default_balances(spec: Spec): """ Helper method to create a series of default balances. Usage: `@with_custom_state(balances_fn=default_balances, ...)` @@ -126,32 +121,82 @@ def default_balances(spec): return [spec.MAX_EFFECTIVE_BALANCE] * num_validators -def scaled_churn_balances(spec): +def default_balances_electra(spec: Spec): + """ + Helper method to create a series of default balances for Electra. + Usage: `@with_custom_state(balances_fn=default_balances_electra, ...)` + """ + if not is_post_electra(spec): + return default_balances(spec) + + num_validators = spec.SLOTS_PER_EPOCH * 8 + return [spec.MAX_EFFECTIVE_BALANCE_ELECTRA] * num_validators + + +def scaled_churn_balances_min_churn_limit(spec: Spec): """ Helper method to create enough validators to scale the churn limit. (This is *firmly* over the churn limit -- thus the +2 instead of just +1) See the second argument of ``max`` in ``get_validator_churn_limit``. - Usage: `@with_custom_state(balances_fn=scaled_churn_balances, ...)` + Usage: `@with_custom_state(balances_fn=scaled_churn_balances_min_churn_limit, ...)` + """ + num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (spec.config.MIN_PER_EPOCH_CHURN_LIMIT + 2) + return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + + +def scaled_churn_balances_equal_activation_churn_limit(spec: Spec): + """ + Helper method to create enough validators to scale the churn limit. + Usage: `@with_custom_state(balances_fn=scaled_churn_balances_equal_activation_churn_limit, ...)` """ - num_validators = spec.config.CHURN_LIMIT_QUOTIENT * (2 + spec.config.MIN_PER_EPOCH_CHURN_LIMIT) + num_validators = spec.config.CHURN_LIMIT_QUOTIENT * ( + spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + ) return [spec.MAX_EFFECTIVE_BALANCE] * num_validators +def scaled_churn_balances_exceed_activation_churn_limit(spec: Spec): + """ + Helper method to create enough validators to scale the churn limit. + (This is *firmly* over the churn limit -- thus the +2 instead of just +1) + Usage: `@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit, ...)` + """ + num_validators = spec.config.CHURN_LIMIT_QUOTIENT * ( + spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + 2 + ) + return [spec.MAX_EFFECTIVE_BALANCE] * num_validators + + +def scaled_churn_balances_exceed_activation_exit_churn_limit(spec: Spec): + """ + Helper method to create enough validators to scale the churn limit. + (The number of validators is double the amount need for the max activation/exit churn limit) + Usage: `@with_custom_state(balances_fn=scaled_churn_balances_exceed_activation_churn_limit, ...)` + """ + num_validators = ( + 2 + * spec.config.CHURN_LIMIT_QUOTIENT + * spec.config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT + // spec.MIN_ACTIVATION_BALANCE + ) + return [spec.MIN_ACTIVATION_BALANCE] * num_validators + + with_state = with_custom_state(default_balances, default_activation_threshold) -def low_balances(spec): +def low_balances(spec: Spec): """ Helper method to create a series of low balances. Usage: `@with_custom_state(balances_fn=low_balances, ...)` """ num_validators = spec.SLOTS_PER_EPOCH * 8 # Technically the balances cannot be this low starting from genesis, but it is useful for testing - low_balance = 18 * 10 ** 9 + low_balance = 18 * 10**9 return [low_balance] * num_validators -def misc_balances(spec): +def misc_balances(spec: Spec): """ Helper method to create a series of balances that includes some misc. balances. Usage: `@with_custom_state(balances_fn=misc_balances, ...)` @@ -163,7 +208,24 @@ def misc_balances(spec): return balances -def misc_balances_in_default_range_with_many_validators(spec): +def misc_balances_electra(spec: Spec): + """ + Helper method to create a series of balances that includes some misc. balances for Electra. + Usage: `@with_custom_state(balances_fn=misc_balances, ...)` + """ + if not is_post_electra(spec): + return misc_balances(spec) + + num_validators = spec.SLOTS_PER_EPOCH * 8 + balances = [ + spec.MAX_EFFECTIVE_BALANCE_ELECTRA * 2 * i // num_validators for i in range(num_validators) + ] + rng = Random(1234) + rng.shuffle(balances) + return balances + + +def misc_balances_in_default_range_with_many_validators(spec: Spec): """ Helper method to create a series of balances that includes some misc. balances but none that are below the ``EJECTION_BALANCE``. @@ -172,14 +234,15 @@ def misc_balances_in_default_range_with_many_validators(spec): num_validators = spec.SLOTS_PER_EPOCH * 8 * 2 floor = spec.config.EJECTION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT balances = [ - max(spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators, floor) for i in range(num_validators) + max(spec.MAX_EFFECTIVE_BALANCE * 2 * i // num_validators, floor) + for i in range(num_validators) ] rng = Random(1234) rng.shuffle(balances) return balances -def low_single_balance(spec): +def low_single_balance(spec: Spec): """ Helper method to create a single of balance of 1 Gwei. Usage: `@with_custom_state(balances_fn=low_single_balance, ...)` @@ -187,12 +250,14 @@ def low_single_balance(spec): return [1] -def large_validator_set(spec): +def large_validator_set(spec: Spec): """ Helper method to create a large series of default balances. Usage: `@with_custom_state(balances_fn=default_balances, ...)` """ - num_validators = 2 * spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE + num_validators = ( + 2 * spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE + ) return [spec.MAX_EFFECTIVE_BALANCE] * num_validators @@ -202,10 +267,12 @@ def single_phase(fn): most state tests only focus on behavior of a single phase (the "spec"). This decorator is applied as part of spec_state_test(fn). """ + def entry(*args, **kw): - if 'phases' in kw: - kw.pop('phases') + if "phases" in kw: + kw.pop("phases") return fn(*args, **kw) + return entry @@ -231,16 +298,23 @@ def dump_skipping_message(reason: str) -> None: raise SkippedTest(message) +def description(case_description: str): + def entry(fn): + return with_meta_tags({"description": case_description})(fn) + + return entry + + def spec_test(fn): # Bls switch must be wrapped by vector_test, # to fully go through the yielded bls switch data, before setting back the BLS setting. # A test may apply BLS overrides such as @always_bls, # but if it yields data (n.b. @always_bls yields the bls setting), it should be wrapped by this decorator. - # This is why @alway_bls has its own bls switch, since the override is beyond the reach of the outer switch. + # This is why @always_bls has its own bls switch, since the override is beyond the reach of the outer switch. return vector_test()(bls_switch(fn)) -# shorthand for decorating @spectest() @with_state @single_phase +# shorthand for decorating @spec_test @with_state @single_phase def spec_state_test(fn): return spec_test(with_state(single_phase(fn))) @@ -250,9 +324,49 @@ def spec_configured_state_test(conf): def decorator(fn): return spec_test(overrides(with_state(single_phase(fn)))) + return decorator +def _check_current_version(spec, state, version_name): + fork_version_field = version_name.upper() + "_FORK_VERSION" + try: + fork_version = getattr(spec.config, fork_version_field) + except Exception: + return False + else: + return state.fork.current_version == fork_version + + +def config_fork_epoch_overrides(spec, state): + if state.fork.current_version == spec.config.GENESIS_FORK_VERSION: + return {} + + for fork in ALL_PHASES: + if fork != PHASE0 and _check_current_version(spec, state, fork): + overrides = {} + for f in ALL_PHASES: + if f != PHASE0 and is_post_fork(fork, f): + overrides[f.upper() + "_FORK_EPOCH"] = spec.GENESIS_EPOCH + return overrides + + +def with_matching_spec_config(emitted_fork=None): + def decorator(fn): + def wrapper(*args, spec: Spec, **kw): + overrides = config_fork_epoch_overrides(spec, kw["state"]) + deco = with_config_overrides(overrides, emitted_fork) + return deco(fn)(*args, spec=spec, **kw) + + return wrapper + + return decorator + + +def spec_state_test_with_matching_config(fn): + return spec_test(with_state(with_matching_spec_config()(single_phase(fn)))) + + def expect_assertion_error(fn): bad = False try: @@ -264,7 +378,7 @@ def expect_assertion_error(fn): # Index errors are special; the spec is not explicit on bound checking, an IndexError is like a failed assert. pass if bad: - raise AssertionError('expected an assertion error, but got none.') + raise AssertionError("expected an assertion error, but got none.") def never_bls(fn): @@ -273,11 +387,13 @@ def never_bls(fn): This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test, as the yielding needs to complete before setting back the BLS setting. """ + def entry(*args, **kw): # override bls setting - kw['bls_active'] = False + kw["bls_active"] = False return bls_switch(fn)(*args, **kw) - return with_meta_tags({'bls_setting': 2})(entry) + + return with_meta_tags({"bls_setting": 2})(entry) def always_bls(fn): @@ -286,11 +402,13 @@ def always_bls(fn): This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test, as the yielding needs to complete before setting back the BLS setting. """ + def entry(*args, **kw): # override bls setting - kw['bls_active'] = True + kw["bls_active"] = True return bls_switch(fn)(*args, **kw) - return with_meta_tags({'bls_setting': 1})(entry) + + return with_meta_tags({"bls_setting": 1})(entry) def bls_switch(fn): @@ -300,102 +418,195 @@ def bls_switch(fn): This decorator may only be applied to yielding spec test functions, and should be wrapped by vector_test, as the yielding needs to complete before setting back the BLS setting. """ + def entry(*args, **kw): old_state = bls.bls_active - bls.bls_active = kw.pop('bls_active', DEFAULT_BLS_ACTIVE) + bls.bls_active = kw.pop("bls_active", DEFAULT_BLS_ACTIVE) res = fn(*args, **kw) if res is not None: yield from res bls.bls_active = old_state + return entry -def disable_process_reveal_deadlines(fn): +def with_all_phases(fn): """ - Decorator to make a function execute with `process_reveal_deadlines` OFF. - This is for testing long-range epochs transition without considering the reveal-deadline slashing effect. + A decorator for running a test with every phase """ - def entry(*args, spec: Spec, **kw): - if hasattr(spec, 'process_reveal_deadlines'): - old_state = spec.process_reveal_deadlines - spec.process_reveal_deadlines = lambda state: None + return with_phases(ALL_PHASES)(fn) - yield from fn(*args, spec=spec, **kw) - if hasattr(spec, 'process_reveal_deadlines'): - spec.process_reveal_deadlines = old_state +def with_all_phases_from(earliest_phase, all_phases=ALL_PHASES): + """ + A decorator factory for running a tests with every phase starting at `earliest_phase` + excluding the ones listed. + """ - return with_meta_tags({'reveal_deadlines_setting': 1})(entry) + def decorator(fn): + return with_phases([phase for phase in all_phases if is_post_fork(phase, earliest_phase)])( + fn + ) + return decorator -def with_all_phases(fn): + +def with_all_phases_from_except(earliest_phase, except_phases=None): """ - A decorator for running a test with every phase + A decorator factory for running a tests with every phase except the ones listed """ - return with_phases(ALL_PHASES)(fn) + return with_all_phases_from( + earliest_phase, [phase for phase in ALL_PHASES if phase not in except_phases] + ) + + +def with_all_phases_from_to(from_phase, to_phase, other_phases=None, all_phases=ALL_PHASES): + """ + A decorator factory for running a tests with every phase + from a given start phase up to and excluding a given end phase + """ + + def decorator(fn): + return with_phases( + [ + phase + for phase in all_phases + if ( + phase != to_phase + and is_post_fork(to_phase, phase) + and is_post_fork(phase, from_phase) + ) + ], + other_phases=other_phases, + )(fn) + + return decorator + + +def with_all_phases_from_to_except(earliest_phase, latest_phase, except_phases=None): + """ + A decorator factory for running a tests with every phase starting at `earliest_phase` + and ending at `latest_phase` excluding the ones listed. + """ + + def decorator(fn): + return with_phases( + [ + phase + for phase in ALL_PHASES + if phase not in except_phases + and is_post_fork(phase, earliest_phase) + and not is_post_fork(phase, latest_phase) + ] + )(fn) def with_all_phases_except(exclusion_phases): """ A decorator factory for running a tests with every phase except the ones listed """ + def decorator(fn): return with_phases([phase for phase in ALL_PHASES if phase not in exclusion_phases])(fn) + return decorator +def _get_preset_targets(kw): + preset_name = DEFAULT_TEST_PRESET + if "preset" in kw: + preset_name = kw.pop("preset") + return spec_targets[preset_name] + + +def _get_run_phases(phases, kw): + """ + Return the fork names for the base `spec` in test cases + """ + if "phase" in kw: + # Limit phases if one explicitly specified + phase = kw.pop("phase") + if phase not in phases: + dump_skipping_message(f"doesn't support this fork: {phase}") + return None + run_phases = [phase] + else: + # If pytest `--fork` flag is set, filter out the rest of the forks + run_phases = set(phases).intersection(DEFAULT_PYTEST_FORKS) + + return run_phases + + +def _get_available_phases(run_phases, other_phases): + """ + Return the available fork names for multi-phase tests + """ + available_phases = set(run_phases) + if other_phases is not None: + available_phases |= set(other_phases) + return available_phases + + +def _run_test_case_with_phases(fn, phases, other_phases, kw, args, is_fork_transition=False): + run_phases = _get_run_phases(phases, kw) + + if len(run_phases) == 0: + if not is_fork_transition: + dump_skipping_message("none of the recognized phases are executable, skipping test.") + return None + + available_phases = _get_available_phases(run_phases, other_phases) + + targets = _get_preset_targets(kw) + + # Populate all phases for multi-phase tests + phase_dir = {} + for phase in available_phases: + phase_dir[phase] = targets[phase] + + # Return is ignored whenever multiple phases are ran. + # This return is for test generators to emit python generators (yielding test vector outputs) + for phase in run_phases: + ret = fn(spec=targets[phase], phases=phase_dir, *args, **kw) + + return ret + + def with_phases(phases, other_phases=None): """ Decorator factory that returns a decorator that runs a test for the appropriate phases. Additional phases that do not initially run, but are made available through the test, are optional. """ + def decorator(fn): def wrapper(*args, **kw): - run_phases = phases - - # limit phases if one explicitly specified - if 'phase' in kw: - phase = kw.pop('phase') - if phase not in phases: - dump_skipping_message(f"doesn't support this fork: {phase}") - return None - run_phases = [phase] - - if PHASE0 not in run_phases and ALTAIR not in run_phases and MERGE not in run_phases: - dump_skipping_message("none of the recognized phases are executable, skipping test.") - return None - - available_phases = set(run_phases) - if other_phases is not None: - available_phases |= set(other_phases) - - preset_name = DEFAULT_TEST_PRESET - if 'preset' in kw: - preset_name = kw.pop('preset') - targets = spec_targets[preset_name] - - # Populate all phases for multi-phase tests - phase_dir = {} - if PHASE0 in available_phases: - phase_dir[PHASE0] = targets[PHASE0] - if ALTAIR in available_phases: - phase_dir[ALTAIR] = targets[ALTAIR] - if MERGE in available_phases: - phase_dir[MERGE] = targets[MERGE] - - # return is ignored whenever multiple phases are ran. - # This return is for test generators to emit python generators (yielding test vector outputs) - if PHASE0 in run_phases: - ret = fn(spec=targets[PHASE0], phases=phase_dir, *args, **kw) - if ALTAIR in run_phases: - ret = fn(spec=targets[ALTAIR], phases=phase_dir, *args, **kw) - if MERGE in run_phases: - ret = fn(spec=targets[MERGE], phases=phase_dir, *args, **kw) - - # TODO: merge, sharding, custody_game and das are not executable yet. - # Tests that specify these features will not run, and get ignored for these specific phases. + if "fork_metas" in kw: + fork_metas = kw.pop("fork_metas") + if "phase" in kw: + # When running test generator, it sets specific `phase` + phase = kw["phase"] + _phases = [phase] + if phase in POST_FORK_OF: + _other_phases = [POST_FORK_OF[phase]] + else: + _other_phases = None + ret = _run_test_case_with_phases( + fn, _phases, _other_phases, kw, args, is_fork_transition=True + ) + else: + # When running pytest, go through `fork_metas` instead of using `phases` + for fork_meta in fork_metas: + _phases = [fork_meta.pre_fork_name] + _other_phases = [fork_meta.post_fork_name] + ret = _run_test_case_with_phases( + fn, _phases, _other_phases, kw, args, is_fork_transition=True + ) + else: + ret = _run_test_case_with_phases(fn, phases, other_phases, kw, args) return ret + return wrapper + return decorator @@ -412,11 +623,74 @@ def wrapper(*args, spec: Spec, **kw): return None return fn(*args, spec=spec, **kw) + return wrapper + return decorator -def with_config_overrides(config_overrides): +with_light_client = with_phases(LIGHT_CLIENT_TESTING_FORKS) + +with_altair_and_later = with_all_phases_from(ALTAIR) +with_bellatrix_and_later = with_all_phases_from(BELLATRIX) +with_capella_and_later = with_all_phases_from(CAPELLA) +with_deneb_and_later = with_all_phases_from(DENEB) +with_electra_and_later = with_all_phases_from(ELECTRA) +with_fulu_and_later = with_all_phases_from(FULU, all_phases=ALLOWED_TEST_RUNNER_FORKS) +with_eip7441_and_later = with_all_phases_from(EIP7441, all_phases=ALLOWED_TEST_RUNNER_FORKS) + + +class quoted_str(str): + pass + + +def _get_basic_dict(ssz_dict: dict[str, Any]) -> dict[str, Any]: + """ + Get dict of basic types from a dict of SSZ objects. + """ + result = {} + for k, v in ssz_dict.items(): + if isinstance(v, int): + value = int(v) + elif isinstance(v, bytes): + value = bytes(bytearray(v)) + else: + value = quoted_str(v) + result[k] = value + return result + + +def get_copy_of_spec(spec): + fork = spec.fork + preset = spec.config.PRESET_BASE + module_path = f"eth2spec.{fork}.{preset}" + module_spec = importlib.util.find_spec(module_path) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + + # Preserve existing config overrides + module.config = deepcopy(spec.config) + + return module + + +def spec_with_config_overrides(spec, config_overrides): + # apply our overrides to a copy of it, and apply it to the spec + config = spec.config._asdict() + config.update((k, config_overrides[k]) for k in config.keys() & config_overrides.keys()) + config_types = spec.Configuration.__annotations__ + modified_config = {k: config_types[k](v) for k, v in config.items()} + + spec.config = spec.Configuration(**modified_config) + + # To output the changed config in a format compatible with yaml test vectors, + # the dict SSZ objects have to be converted into Python built-in types. + output_config = _get_basic_dict(modified_config) + + return spec, output_config + + +def with_config_overrides(config_overrides, emitted_fork=None, emit=True): """ WARNING: the spec_test decorator must wrap this, to ensure the decorated test actually runs. This decorator forces the test to yield, and pytest doesn't run generator tests, and instead silently passes it. @@ -424,47 +698,40 @@ def with_config_overrides(config_overrides): This is a decorator that applies a dict of config value overrides to the spec during execution. """ + def decorator(fn): def wrapper(*args, spec: Spec, **kw): - # remember the old config - old_config = spec.config - - # apply our overrides to a copy of it, and apply it to the spec - tmp_config = deepcopy(old_config._asdict()) # not a private method, there are multiple - tmp_config.update(config_overrides) - config_types = spec.Configuration.__annotations__ - # Retain types of all config values - test_config = {k: config_types[k](v) for k, v in tmp_config.items()} - - # Output the config for test vectors (TODO: check config YAML encoding) - yield 'config', 'data', test_config - - spec.config = spec.Configuration(**test_config) + # Apply config overrides to spec + spec, output_config = spec_with_config_overrides( + get_copy_of_spec(spec), config_overrides + ) + + # Apply config overrides to additional phases, if present + if "phases" in kw: + phases = {} + for fork in kw["phases"]: + phases[fork], output = spec_with_config_overrides( + get_copy_of_spec(kw["phases"][fork]), config_overrides + ) + if emitted_fork == fork: + output_config = output + kw["phases"] = phases + + # Emit requested spec (with overrides) + if emit: + yield "config", "cfg", output_config # Run the function out = fn(*args, spec=spec, **kw) + # If it's not returning None like a normal test function, # it's generating things, and we need to complete it before setting back the config. if out is not None: yield from out - # Restore the old config and apply it - spec.config = old_config - return wrapper - return decorator - - -def is_post_altair(spec): - return spec.fork not in FORKS_BEFORE_ALTAIR - -def is_post_merge(spec): - return spec.fork not in FORKS_BEFORE_MERGE - - -with_altair_and_later = with_phases([ALTAIR, MERGE]) -with_merge_and_later = with_phases([MERGE]) # TODO: include sharding when spec stabilizes. + return decorator def only_generator(reason): @@ -474,14 +741,41 @@ def _wrapper(*args, **kwargs): dump_skipping_message(reason) return None return inner(*args, **kwargs) + return _wrapper + return _decorator -def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None): +def with_test_suite_name(suite_name: str): + def _decorator(inner): + inner.suite_name = suite_name + return inner + + return _decorator + + +# +# Fork transition state tests +# + + +def set_fork_metas(fork_metas: Sequence[ForkMeta]): + def decorator(fn): + def wrapper(*args, **kwargs): + return fn(*args, fork_metas=fork_metas, **kwargs) + + return wrapper + + return decorator + + +def with_fork_metas(fork_metas: Sequence[ForkMeta]): """ - A decorator to construct a "transition" test from one fork of the eth2 spec - to another. + A decorator to construct a "transition" test from one fork to another. + + Decorator takes a list of `ForkMeta` and each item defines `pre_fork_name`, + `post_fork_name`, and `fork_epoch`. Decorator assumes a transition from the `pre_fork_name` fork to the `post_fork_name` fork. The user can supply a `fork_epoch` at which the @@ -499,15 +793,68 @@ def fork_transition_test(pre_fork_name, post_fork_name, fork_epoch=None): `post_tag`: a function to tag data as belonging to `post_fork_name` fork. Used to discriminate data during consumption of the generated spec tests. """ - def _wrapper(fn): - @with_phases([pre_fork_name], other_phases=[post_fork_name]) - @spec_test - @with_state - def _adapter(*args, **kwargs): - wrapped = build_transition_test(fn, - pre_fork_name, - post_fork_name, - fork_epoch=fork_epoch) - return wrapped(*args, **kwargs) - return _adapter - return _wrapper + run_yield_fork_meta = yield_fork_meta(fork_metas) + run_with_phases = with_phases(ALL_PHASES) + run_set_fork_metas = set_fork_metas(fork_metas) + + def decorator(fn): + return run_set_fork_metas(run_with_phases(spec_test(with_state(run_yield_fork_meta(fn))))) + + return decorator + + +def yield_fork_meta(fork_metas: Sequence[ForkMeta]): + """ + Yield meta fields to `meta.yaml` and pass post spec and meta fields to `fn`. + """ + + def decorator(fn): + def wrapper(*args, **kw): + phases = kw.pop("phases") + spec = kw["spec"] + try: + fork_meta = next(filter(lambda m: m.pre_fork_name == spec.fork, fork_metas)) + except StopIteration: + dump_skipping_message(f"doesn't support this fork: {spec.fork}") + + post_spec = phases[fork_meta.post_fork_name] + + # Reset counter + pre_fork_counter = 0 + + def pre_tag(obj): + nonlocal pre_fork_counter + pre_fork_counter += 1 + return obj + + def post_tag(obj): + return obj + + yield "post_fork", "meta", fork_meta.post_fork_name + + has_fork_epoch = False + if fork_meta.fork_epoch: + kw["fork_epoch"] = fork_meta.fork_epoch + has_fork_epoch = True + yield "fork_epoch", "meta", fork_meta.fork_epoch + + result = fn( + *args, + post_spec=post_spec, + pre_tag=pre_tag, + post_tag=post_tag, + **kw, + ) + if result is not None: + for part in result: + if part[0] == "fork_epoch": + has_fork_epoch = True + yield part + assert has_fork_epoch + + if pre_fork_counter > 0: + yield "fork_block", "meta", pre_fork_counter - 1 + + return wrapper + + return decorator diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_attestation.py deleted file mode 100644 index 4ed3f50885..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_attestation.py +++ /dev/null @@ -1,33 +0,0 @@ -from eth2spec.test.context import ( - with_phases, - spec_state_test, - always_bls, -) -from eth2spec.test.helpers.constants import CUSTODY_GAME -from eth2spec.test.helpers.state import transition_to -from eth2spec.test.helpers.attestations import ( - run_attestation_processing, - get_valid_attestation, -) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_on_time_success(spec, state): - attestation = get_valid_attestation(spec, state, signed=True) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - yield from run_attestation_processing(spec, state, attestation) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_late_success(spec, state): - attestation = get_valid_attestation(spec, state, signed=True) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + 1) - - yield from run_attestation_processing(spec, state, attestation) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_chunk_challenge.py b/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_chunk_challenge.py deleted file mode 100644 index cc12b66f5e..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_chunk_challenge.py +++ /dev/null @@ -1,372 +0,0 @@ -from eth2spec.test.helpers.custody import ( - get_valid_chunk_challenge, - get_valid_custody_chunk_response, - get_sample_shard_transition, -) -from eth2spec.test.helpers.attestations import ( - get_valid_attestation, -) -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, - MINIMAL, -) -from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot -from eth2spec.test.context import ( - expect_assertion_error, - disable_process_reveal_deadlines, - spec_state_test, - with_phases, - with_presets, -) -from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing - - -def run_chunk_challenge_processing(spec, state, custody_chunk_challenge, valid=True): - """ - Run ``process_chunk_challenge``, yielding: - - pre-state ('pre') - - CustodyBitChallenge ('custody_chunk_challenge') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_chunk_challenge', custody_chunk_challenge - - if not valid: - expect_assertion_error(lambda: spec.process_chunk_challenge(state, custody_chunk_challenge)) - yield 'post', None - return - - spec.process_chunk_challenge(state, custody_chunk_challenge) - - assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].responder_index == \ - custody_chunk_challenge.responder_index - assert state.custody_chunk_challenge_records[state.custody_chunk_challenge_index - 1].chunk_index == \ - custody_chunk_challenge.chunk_index - - yield 'post', state - - -def run_custody_chunk_response_processing(spec, state, custody_response, valid=True): - """ - Run ``process_chunk_challenge_response``, yielding: - - pre-state ('pre') - - CustodyResponse ('custody_response') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_response', custody_response - - if not valid: - expect_assertion_error(lambda: spec.process_custody_response(state, custody_response)) - yield 'post', None - return - - spec.process_chunk_challenge_response(state, custody_response) - - assert state.custody_chunk_challenge_records[custody_response.challenge_index] == spec.CustodyChunkChallengeRecord() - - yield 'post', state - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -@disable_process_reveal_deadlines -def test_challenge_appended(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - yield from run_chunk_challenge_processing(spec, state, challenge) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_challenge_empty_element_replaced(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - state.custody_chunk_challenge_records.append(spec.CustodyChunkChallengeRecord()) - - yield from run_chunk_challenge_processing(spec, state, challenge) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_duplicate_challenge(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - yield from run_chunk_challenge_processing(spec, state, challenge, valid=False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_second_challenge(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD) - - challenge0 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=0) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge0) - - challenge1 = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=1) - - yield from run_chunk_challenge_processing(spec, state, challenge1) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_multiple_epochs_custody(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - yield from run_chunk_challenge_processing(spec, state, challenge) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_many_epochs_custody(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - yield from run_chunk_challenge_processing(spec, state, challenge) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_off_chain_attestation(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - yield from run_chunk_challenge_processing(spec, state, challenge) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_custody_response(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - chunk_challenge_index = state.custody_chunk_challenge_index - 1 - - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) - - yield from run_custody_chunk_response_processing(spec, state, custody_response) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_custody_response_chunk_index_2(spec, state): - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition, chunk_index=2) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - chunk_challenge_index = state.custody_chunk_challenge_index - 1 - - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) - - yield from run_custody_chunk_response_processing(spec, state, custody_response) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_custody_response_multiple_epochs(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 3) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - chunk_challenge_index = state.custody_chunk_challenge_index - 1 - - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) - - yield from run_custody_chunk_response_processing(spec, state, custody_response) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_custody_response_many_epochs(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * 20) - - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - chunk_challenge_index = state.custody_chunk_challenge_index - 1 - - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) - - yield from run_custody_chunk_response_processing(spec, state, custody_response) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_key_reveal.py b/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_key_reveal.py deleted file mode 100644 index fa8401228e..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_key_reveal.py +++ /dev/null @@ -1,89 +0,0 @@ -from eth2spec.test.helpers.constants import CUSTODY_GAME -from eth2spec.test.helpers.custody import get_valid_custody_key_reveal -from eth2spec.test.context import ( - with_phases, - spec_state_test, - expect_assertion_error, - always_bls, -) - - -def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=True): - """ - Run ``process_custody_key_reveal``, yielding: - - pre-state ('pre') - - custody_key_reveal ('custody_key_reveal') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_key_reveal', custody_key_reveal - - if not valid: - expect_assertion_error(lambda: spec.process_custody_key_reveal(state, custody_key_reveal)) - yield 'post', None - return - - revealer_index = custody_key_reveal.revealer_index - - pre_next_custody_secret_to_reveal = \ - state.validators[revealer_index].next_custody_secret_to_reveal - - spec.process_custody_key_reveal(state, custody_key_reveal) - - post_next_custody_secret_to_reveal = \ - state.validators[revealer_index].next_custody_secret_to_reveal - - assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1 - - yield 'post', state - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_success(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_reveal_too_early(spec, state): - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_wrong_period(spec, state): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, period=5) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_late_reveal(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150 - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_double_reveal(spec, state): - state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 2 - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_slashing.py b/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_slashing.py deleted file mode 100644 index 4891c7b236..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_custody_slashing.py +++ /dev/null @@ -1,159 +0,0 @@ -from eth2spec.test.helpers.custody import ( - get_valid_custody_slashing, - get_custody_slashable_shard_transition, -) -from eth2spec.test.helpers.attestations import ( - get_valid_attestation, -) -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, - MINIMAL, -) -from eth2spec.test.helpers.keys import privkeys -from eth2spec.utils.ssz.ssz_typing import ByteList -from eth2spec.test.helpers.state import get_balance, transition_to -from eth2spec.test.context import ( - with_phases, - spec_state_test, - expect_assertion_error, - disable_process_reveal_deadlines, - with_presets, -) -from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing - - -def run_custody_slashing_processing(spec, state, custody_slashing, valid=True, correct=True): - """ - Run ``process_bit_challenge``, yielding: - - pre-state ('pre') - - CustodySlashing ('custody_slashing') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'custody_slashing', custody_slashing - - if not valid: - expect_assertion_error(lambda: spec.process_custody_slashing(state, custody_slashing)) - yield 'post', None - return - - if correct: - pre_slashed_balance = get_balance(state, custody_slashing.message.malefactor_index) - else: - pre_slashed_balance = get_balance(state, custody_slashing.message.whistleblower_index) - - spec.process_custody_slashing(state, custody_slashing) - - if correct: - slashed_validator = state.validators[custody_slashing.message.malefactor_index] - assert get_balance(state, custody_slashing.message.malefactor_index) < pre_slashed_balance - else: - slashed_validator = state.validators[custody_slashing.message.whistleblower_index] - assert get_balance(state, custody_slashing.message.whistleblower_index) < pre_slashed_balance - - assert slashed_validator.slashed - assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - yield 'post', state - - -def run_standard_custody_slashing_test(spec, - state, - shard_lateness=None, - shard=None, - validator_index=None, - block_lengths=None, - slashing_message_data=None, - correct=True, - valid=True): - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - if shard_lateness is None: - shard_lateness = spec.SLOTS_PER_EPOCH - transition_to(spec, state, state.slot + shard_lateness) - - if shard is None: - shard = 0 - if validator_index is None: - validator_index = spec.get_beacon_committee(state, state.slot, shard)[0] - - offset_slots = spec.get_offset_slots(state, shard) - if block_lengths is None: - block_lengths = [2**15 // 3] * len(offset_slots) - - custody_secret = spec.get_custody_secret( - state, - validator_index, - privkeys[validator_index], - spec.get_current_epoch(state), - ) - shard_transition, slashable_test_vector = get_custody_slashable_shard_transition( - spec, - state.slot, - block_lengths, - custody_secret, - slashable=correct, - ) - - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - slashing = get_valid_custody_slashing(spec, state, attestation, shard_transition, - custody_secret, slashable_test_vector) - - if slashing_message_data is not None: - slashing.message.data = slashing_message_data - - yield from run_custody_slashing_processing(spec, state, slashing, valid=valid, correct=correct) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_custody_slashing(spec, state): - yield from run_standard_custody_slashing_test(spec, state) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_incorrect_custody_slashing(spec, state): - yield from run_standard_custody_slashing_test(spec, state, correct=False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_multiple_epochs_custody(spec, state): - yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 3) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_many_epochs_custody(spec, state): - yield from run_standard_custody_slashing_test(spec, state, shard_lateness=spec.SLOTS_PER_EPOCH * 5) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@disable_process_reveal_deadlines -@with_presets([MINIMAL], reason="too slow") -def test_invalid_custody_slashing(spec, state): - yield from run_standard_custody_slashing_test( - spec, - state, - slashing_message_data=ByteList[spec.MAX_SHARD_BLOCK_SIZE](), - valid=False, - ) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_early_derived_secret_reveal.py b/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_early_derived_secret_reveal.py deleted file mode 100644 index 904c9af4e6..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/block_processing/test_process_early_derived_secret_reveal.py +++ /dev/null @@ -1,137 +0,0 @@ -from eth2spec.test.helpers.constants import CUSTODY_GAME -from eth2spec.test.helpers.custody import get_valid_early_derived_secret_reveal -from eth2spec.test.helpers.state import next_epoch_via_block, get_balance -from eth2spec.test.context import ( - with_phases, - spec_state_test, - expect_assertion_error, - always_bls, - never_bls, -) - - -def run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, valid=True): - """ - Run ``process_randao_key_reveal``, yielding: - - pre-state ('pre') - - randao_key_reveal ('randao_key_reveal') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - yield 'pre', state - yield 'randao_key_reveal', randao_key_reveal - - if not valid: - expect_assertion_error(lambda: spec.process_early_derived_secret_reveal(state, randao_key_reveal)) - yield 'post', None - return - - pre_slashed_balance = get_balance(state, randao_key_reveal.revealed_index) - - spec.process_early_derived_secret_reveal(state, randao_key_reveal) - - slashed_validator = state.validators[randao_key_reveal.revealed_index] - - if randao_key_reveal.epoch >= spec.get_current_epoch(state) + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING: - assert slashed_validator.slashed - assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH - assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - assert get_balance(state, randao_key_reveal.revealed_index) < pre_slashed_balance - yield 'post', state - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_success(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state) - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@never_bls -def test_reveal_from_current_epoch(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state)) - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@never_bls -def test_reveal_from_past_epoch(spec, state): - next_epoch_via_block(spec, state) - randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state) - 1) - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_reveal_with_custody_padding(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal( - spec, - state, - spec.get_current_epoch(state) + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING, - ) - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@always_bls -def test_reveal_with_custody_padding_minus_one(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal( - spec, - state, - spec.get_current_epoch(state) + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING - 1, - ) - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, True) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@never_bls -def test_double_reveal(spec, state): - epoch = spec.get_current_epoch(state) + spec.RANDAO_PENALTY_EPOCHS - randao_key_reveal1 = get_valid_early_derived_secret_reveal( - spec, - state, - epoch, - ) - _, _, _ = dict(run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal1)) - - randao_key_reveal2 = get_valid_early_derived_secret_reveal( - spec, - state, - epoch, - ) - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal2, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@never_bls -def test_revealer_is_slashed(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal(spec, state, spec.get_current_epoch(state)) - state.validators[randao_key_reveal.revealed_index].slashed = True - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@never_bls -def test_far_future_epoch(spec, state): - randao_key_reveal = get_valid_early_derived_secret_reveal( - spec, - state, - spec.get_current_epoch(state) + spec.EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS, - ) - - yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal, False) diff --git a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_challenge_deadlines.py b/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_challenge_deadlines.py deleted file mode 100644 index 144ea02135..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_challenge_deadlines.py +++ /dev/null @@ -1,64 +0,0 @@ -from eth2spec.test.helpers.custody import ( - get_valid_chunk_challenge, - get_sample_shard_transition, -) -from eth2spec.test.helpers.attestations import ( - get_valid_attestation, -) -from eth2spec.test.helpers.state import transition_to, transition_to_valid_shard_slot -from eth2spec.test.context import ( - spec_state_test, - with_phases, - with_presets, -) -from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, - MINIMAL, -) -from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with - -from eth2spec.test.custody_game.block_processing.test_process_chunk_challenge import ( - run_chunk_challenge_processing, -) - - -def run_process_challenge_deadlines(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -def test_validator_slashed_after_chunk_challenge(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - validator_index = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index - )[0] - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - assert state.validators[validator_index].slashed == 0 - - transition_to(spec, state, state.slot + spec.MAX_CHUNK_CHALLENGE_DELAY * spec.SLOTS_PER_EPOCH) - - state.validators[validator_index].slashed = 0 - - yield from run_process_challenge_deadlines(spec, state) - - assert state.validators[validator_index].slashed == 1 diff --git a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_custody_final_updates.py b/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_custody_final_updates.py deleted file mode 100644 index d8dd3d19e8..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_custody_final_updates.py +++ /dev/null @@ -1,177 +0,0 @@ -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, -) -from eth2spec.test.helpers.custody import ( - get_valid_chunk_challenge, - get_valid_custody_chunk_response, - get_valid_custody_key_reveal, - get_sample_shard_transition -) -from eth2spec.test.helpers.attestations import ( - get_valid_attestation, -) -from eth2spec.test.helpers.state import next_epoch_via_block, transition_to, transition_to_valid_shard_slot -from eth2spec.test.context import ( - with_phases, - spec_state_test, -) -from eth2spec.test.phase0.block_processing.test_process_attestation import run_attestation_processing -from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with - -from eth2spec.test.custody_game.block_processing.test_process_chunk_challenge import ( - run_chunk_challenge_processing, - run_custody_chunk_response_processing, -) -from eth2spec.test.custody_game.block_processing.test_process_custody_key_reveal import ( - run_custody_key_reveal_processing, -) - - -def run_process_custody_final_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_custody_final_updates') - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_validator_withdrawal_delay(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - spec.initiate_validator_exit(state, 0) - assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - yield from run_process_custody_final_updates(spec, state) - - assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_validator_withdrawal_reenable_after_custody_reveal(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - spec.initiate_validator_exit(state, 0) - assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - next_epoch_via_block(spec, state) - - assert state.validators[0].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[0].exit_epoch: - next_epoch_via_block(spec, state) - - while (state.validators[0].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator(0, state.validators[0].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=0) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - yield from run_process_custody_final_updates(spec, state) - - assert state.validators[0].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_validator_withdrawal_suspend_after_chunk_challenge(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - validator_index = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index - )[0] - - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: - next_epoch_via_block(spec, state) - - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - validator_index, - state.validators[validator_index].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - next_epoch_via_block(spec, state) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - yield from run_process_custody_final_updates(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_validator_withdrawal_resume_after_chunk_challenge_response(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + 1) # Make len(offset_slots) == 1 - shard = 0 - offset_slots = spec.get_offset_slots(state, shard) - shard_transition = get_sample_shard_transition(spec, state.slot, [2**15 // 3] * len(offset_slots)) - attestation = get_valid_attestation(spec, state, index=shard, signed=True, - shard_transition=shard_transition) - - transition_to(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) - - _, _, _ = run_attestation_processing(spec, state, attestation) - - validator_index = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index - )[0] - - spec.initiate_validator_exit(state, validator_index) - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH - - next_epoch_via_block(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - while spec.get_current_epoch(state) < state.validators[validator_index].exit_epoch: - next_epoch_via_block(spec, state) - - while (state.validators[validator_index].next_custody_secret_to_reveal - <= spec.get_custody_period_for_validator( - validator_index, - state.validators[validator_index].exit_epoch - 1)): - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=validator_index) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - next_epoch_via_block(spec, state) - - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transition) - - _, _, _ = run_chunk_challenge_processing(spec, state, challenge) - - next_epoch_via_block(spec, state) - - assert state.validators[validator_index].withdrawable_epoch == spec.FAR_FUTURE_EPOCH - - chunk_challenge_index = state.custody_chunk_challenge_index - 1 - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=2**15 // 3) - - _, _, _ = run_custody_chunk_response_processing(spec, state, custody_response) - - yield from run_process_custody_final_updates(spec, state) - - assert state.validators[validator_index].withdrawable_epoch < spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_reveal_deadlines.py b/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_reveal_deadlines.py deleted file mode 100644 index b831ccff73..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/epoch_processing/test_process_reveal_deadlines.py +++ /dev/null @@ -1,59 +0,0 @@ -from eth2spec.test.helpers.custody import ( - get_valid_custody_key_reveal, -) -from eth2spec.test.helpers.state import transition_to -from eth2spec.test.context import ( - with_phases, - with_presets, - spec_state_test, -) -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, - MINIMAL, -) -from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with -from eth2spec.test.custody_game.block_processing.test_process_custody_key_reveal import ( - run_custody_key_reveal_processing, -) - - -def run_process_challenge_deadlines(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_challenge_deadlines') - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -def test_validator_slashed_after_reveal_deadline(spec, state): - assert state.validators[0].slashed == 0 - transition_to(spec, state, spec.get_randao_epoch_for_custody_period(0, 0) * spec.SLOTS_PER_EPOCH) - - # Need to run at least one reveal so that not all validators are slashed (otherwise spec fails to find proposers) - custody_key_reveal = get_valid_custody_key_reveal(spec, state, validator_index=1) - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) - - state.validators[0].slashed = 0 - - yield from run_process_challenge_deadlines(spec, state) - - assert state.validators[0].slashed == 1 - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@with_presets([MINIMAL], reason="too slow") -def test_validator_not_slashed_after_reveal(spec, state): - transition_to(spec, state, spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - - _, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal) - - assert state.validators[0].slashed == 0 - - transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) - - yield from run_process_challenge_deadlines(spec, state) - - assert state.validators[0].slashed == 0 diff --git a/tests/core/pyspec/eth2spec/test/custody_game/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/custody_game/sanity/test_blocks.py deleted file mode 100644 index 77ce3c5add..0000000000 --- a/tests/core/pyspec/eth2spec/test/custody_game/sanity/test_blocks.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import Dict, Sequence - -from eth2spec.test.context import ( - with_phases, - spec_state_test, - with_presets, -) -from eth2spec.test.helpers.attestations import get_valid_attestation -from eth2spec.test.helpers.block import build_empty_block -from eth2spec.test.helpers.constants import ( - CUSTODY_GAME, - MINIMAL, -) -from eth2spec.test.helpers.custody import ( - get_custody_slashable_test_vector, - get_valid_chunk_challenge, - get_valid_custody_chunk_response, - get_valid_custody_key_reveal, - get_valid_custody_slashing, - get_valid_early_derived_secret_reveal, -) -from eth2spec.test.helpers.keys import privkeys -from eth2spec.test.helpers.shard_block import ( - build_shard_block, - get_committee_index_of_shard, - get_sample_shard_block_body, - get_shard_transitions, -) -from eth2spec.test.helpers.state import state_transition_and_sign_block, transition_to_valid_shard_slot, transition_to - - -def run_beacon_block(spec, state, block, valid=True): - yield 'pre', state.copy() - - if not valid: - signed_beacon_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'block', signed_beacon_block - yield 'post', None - return - - signed_beacon_block = state_transition_and_sign_block(spec, state, block) - yield 'block', signed_beacon_block - yield 'post', state - - -# -# Beacon block with custody operations -# - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_with_shard_transition_with_custody_challenge_and_response(spec, state): - transition_to_valid_shard_slot(spec, state) - - # build shard block - shard = 0 - committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) - body = get_sample_shard_block_body(spec) - shard_block = build_shard_block(spec, state, shard, body=body, slot=state.slot, signed=True) - shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} - shard_transitions = get_shard_transitions(spec, state, shard_block_dict) - attestation = get_valid_attestation( - spec, state, index=committee_index, - shard_transition=shard_transitions[shard], signed=True, - ) - - block = build_empty_block(spec, state, slot=state.slot + 1) - block.body.attestations = [attestation] - block.body.shard_transitions = shard_transitions - - # CustodyChunkChallenge operation - challenge = get_valid_chunk_challenge(spec, state, attestation, shard_transitions[shard]) - block.body.chunk_challenges = [challenge] - # CustodyChunkResponse operation - chunk_challenge_index = state.custody_chunk_challenge_index - custody_response = get_valid_custody_chunk_response( - spec, state, challenge, chunk_challenge_index, block_length_or_custody_data=body) - block.body.chunk_challenge_responses = [custody_response] - - yield from run_beacon_block(spec, state, block) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -@with_presets([MINIMAL]) -def test_custody_key_reveal(spec, state): - transition_to_valid_shard_slot(spec, state) - transition_to(spec, state, state.slot + spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH) - - block = build_empty_block(spec, state, slot=state.slot + 1) - custody_key_reveal = get_valid_custody_key_reveal(spec, state) - block.body.custody_key_reveals = [custody_key_reveal] - - yield from run_beacon_block(spec, state, block) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_early_derived_secret_reveal(spec, state): - transition_to_valid_shard_slot(spec, state) - block = build_empty_block(spec, state, slot=state.slot + 1) - early_derived_secret_reveal = get_valid_early_derived_secret_reveal(spec, state) - block.body.early_derived_secret_reveals = [early_derived_secret_reveal] - - yield from run_beacon_block(spec, state, block) - - -@with_phases([CUSTODY_GAME]) -@spec_state_test -def test_custody_slashing(spec, state): - transition_to_valid_shard_slot(spec, state) - - # Build shard block - shard = 0 - committee_index = get_committee_index_of_shard(spec, state, state.slot, shard) - # Create slashable shard block body - validator_index = spec.get_beacon_committee(state, state.slot, committee_index)[0] - custody_secret = spec.get_custody_secret( - state, - validator_index, - privkeys[validator_index], - spec.get_current_epoch(state), - ) - slashable_body = get_custody_slashable_test_vector(spec, custody_secret, length=100, slashable=True) - shard_block = build_shard_block(spec, state, shard, body=slashable_body, slot=state.slot, signed=True) - shard_block_dict: Dict[spec.Shard, Sequence[spec.SignedShardBlock]] = {shard: [shard_block]} - shard_transitions = get_shard_transitions(spec, state, shard_block_dict) - - attestation = get_valid_attestation( - spec, state, index=committee_index, - shard_transition=shard_transitions[shard], signed=True, - ) - block = build_empty_block(spec, state, slot=state.slot + 1) - block.body.attestations = [attestation] - block.body.shard_transitions = shard_transitions - - _, _, _ = run_beacon_block(spec, state, block) - - transition_to(spec, state, state.slot + spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)) - - block = build_empty_block(spec, state, slot=state.slot + 1) - custody_slashing = get_valid_custody_slashing( - spec, state, attestation, shard_transitions[shard], custody_secret, slashable_body - ) - block.body.custody_slashings = [custody_slashing] - - yield from run_beacon_block(spec, state, block) diff --git a/tests/core/pyspec/eth2spec/test/deneb/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py new file mode 100644 index 0000000000..da546a3eb0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_execution_payload.py @@ -0,0 +1,431 @@ +from random import Random + +from eth2spec.test.context import ( + expect_assertion_error, + spec_state_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.blob import ( + get_max_blob_count, + get_sample_blob_tx, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, + get_execution_payload_header, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.keys import privkeys + + +def run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments, valid=True, execution_valid=True +): + """ + Run ``process_execution_payload``, yielding: + - pre-state ('pre') + - execution payload ('execution_payload') + - execution details, to mock EVM execution ('execution.yml', a dict with 'execution_valid' key and boolean value) + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + + # after EIP-7732 the execution payload is no longer in the body + if is_post_eip7732(spec): + envelope = spec.ExecutionPayloadEnvelope( + payload=execution_payload, + payload_withheld=False, + blob_kzg_commitments=blob_kzg_commitments, + ) + kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + blob_kzg_commitments + ) + state.latest_execution_payload_header.blob_kzg_commitments_root = kzg_list.hash_tree_root() + post_state = state.copy() + previous_state_root = state.hash_tree_root() + if post_state.latest_block_header.state_root == spec.Root(): + post_state.latest_block_header.state_root = previous_state_root + envelope.beacon_block_root = post_state.latest_block_header.hash_tree_root() + + post_state.latest_block_hash = execution_payload.block_hash + post_state.latest_full_slot = state.slot + envelope.state_root = post_state.hash_tree_root() + privkey = privkeys[envelope.builder_index] + signature = spec.get_execution_payload_envelope_signature( + state, + envelope, + privkey, + ) + signed_envelope = spec.SignedExecutionPayloadEnvelope( + message=envelope, + signature=signature, + ) + body = spec.BeaconBlockBody() + else: + body = spec.BeaconBlockBody( + blob_kzg_commitments=blob_kzg_commitments, + execution_payload=execution_payload, + ) + + yield "pre", state + yield "execution", {"execution_valid": execution_valid} + yield "body", body + + called_new_block = False + + class TestEngine(spec.NoopExecutionEngine): + def verify_and_notify_new_payload(self, new_payload_request) -> bool: + nonlocal called_new_block + called_new_block = True + assert new_payload_request.execution_payload == execution_payload + return execution_valid + + if not valid: + if is_post_eip7732(spec): + expect_assertion_error( + lambda: spec.process_execution_payload(state, signed_envelope, TestEngine()) + ) + else: + expect_assertion_error( + lambda: spec.process_execution_payload(state, body, TestEngine()) + ) + yield "post", None + return + + if is_post_eip7732(spec): + spec.process_execution_payload(state, signed_envelope, TestEngine()) + else: + spec.process_execution_payload(state, body, TestEngine()) + + # Make sure we called the engine + assert called_new_block + + yield "post", state + + if is_post_eip7732(spec): + assert state.latest_block_hash == execution_payload.block_hash + assert state.latest_full_slot == state.slot + else: + assert state.latest_execution_payload_header == get_execution_payload_header( + spec, state, execution_payload + ) + + +""" +Tests with incorrect blob transactions in the execution payload, but the execution client returns +VALID, and the purpose of these tests is that the beacon client must not reject the block by +attempting to do a validation of its own. +""" + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_blob_tx_type(spec, state): + """ + The transaction type is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + opaque_tx = b"\x04" + opaque_tx[1:] # incorrect tx type + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_transaction_length_1_extra_byte(spec, state): + """ + The transaction length is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + opaque_tx = opaque_tx + b"\x12" # incorrect tx length, longer + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_transaction_length_1_byte_short(spec, state): + """ + The transaction length is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + opaque_tx = opaque_tx[:-1] # incorrect tx length, shorter + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_transaction_length_empty(spec, state): + """ + The transaction length is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + opaque_tx = opaque_tx[0:0] # incorrect tx length, empty + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_transaction_length_32_extra_bytes(spec, state): + """ + The transaction length is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + opaque_tx = opaque_tx + b"\x12" * 32 # incorrect tx length + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_no_transactions_with_commitments(spec, state): + """ + The commitments are provided without blob transactions, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + _, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + + execution_payload.transactions = [] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_commitment(spec, state): + """ + The commitments are wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + blob_kzg_commitments[0] = b"\x12" * 48 # incorrect commitment + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_no_commitments_for_transactions(spec, state): + """ + The blob transactions are provided without commitments, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) + blob_kzg_commitments = [] # incorrect count + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_commitments_order(spec, state): + """ + The commitments are provided in wrong order, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1111)) + blob_kzg_commitments = [blob_kzg_commitments[1], blob_kzg_commitments[0]] # incorrect order + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_transaction_no_blobs_but_with_commitments(spec, state): + """ + The blob transaction is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + # the blob transaction is invalid, because the EL verifies that the tx contains at least one blob + # therefore the EL should reject it, but the CL should not reject the block regardless + opaque_tx, _, _, _ = get_sample_blob_tx(spec, blob_count=0, rng=Random(1111)) + _, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2, rng=Random(1112)) + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + + # the transaction doesn't contain any blob, but commitments are provided + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_incorrect_block_hash(spec, state): + """ + The block hash is wrong, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = b"\x12" * 32 # incorrect block hash + + # CL itself doesn't verify EL block hash + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_zeroed_commitment(spec, state): + """ + The commitment is in correct form but the blob is invalid, but the testing ExecutionEngine returns VALID by default. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( + spec, blob_count=1, is_valid_blob=False + ) + assert all(commitment == b"\x00" * 48 for commitment in blob_kzg_commitments) + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments + ) + + +@with_deneb_and_later +@spec_state_test +def test_invalid_correct_input__execution_invalid(spec, state): + """ + The blob transaction and commitments are correct, but the testing ExecutionEngine returns INVALID. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx(spec) + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments, valid=False, execution_valid=False + ) + + +@with_deneb_and_later +@spec_state_test +def test_invalid_exceed_max_blobs_per_block(spec, state): + """ + The blob transaction and commitments are correct but the number of blobs exceeds the `MAX_BLOBS_PER_BLOCK`. + """ + execution_payload = build_empty_execution_payload(spec, state) + + opaque_tx, _, blob_kzg_commitments, _ = get_sample_blob_tx( + spec, blob_count=get_max_blob_count(spec, state) + 1 + ) + + execution_payload.transactions = [opaque_tx] + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + # Make the first block full in EIP-7732 + if is_post_eip7732(spec): + state.latest_execution_payload_header.block_hash = execution_payload.block_hash + yield from run_execution_payload_processing( + spec, state, execution_payload, blob_kzg_commitments, valid=False + ) diff --git a/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py new file mode 100644 index 0000000000..12daf3b065 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/block_processing/test_process_voluntary_exit.py @@ -0,0 +1,88 @@ +from eth2spec.test.bellatrix.block_processing.test_process_voluntary_exit import ( + run_voluntary_exit_processing_test, +) +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.constants import ( + DENEB, +) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_invalid_voluntary_exit_with_current_fork_version_not_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + """ + assert state.fork.current_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.current_version, + is_before_fork_epoch=False, + valid=False, + ) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_voluntary_exit_with_previous_fork_version_not_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + + Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks + """ + assert state.fork.previous_version != state.fork.current_version + + if spec.fork == DENEB: + assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=False, + ) + else: + assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=False, + valid=False, + ) + + +@with_deneb_and_later +@spec_state_test +@always_bls +def test_voluntary_exit_with_previous_fork_version_is_before_fork_epoch(spec, state): + """ + Since Deneb, the VoluntaryExit domain is fixed to `CAPELLA_FORK_VERSION` + + Note: This test is valid for ``spec.fork == DENEB`` and invalid for subsequent forks + """ + assert state.fork.previous_version != state.fork.current_version + + if spec.fork == DENEB: + assert state.fork.previous_version == spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=True, + ) + else: + assert state.fork.previous_version != spec.config.CAPELLA_FORK_VERSION + yield from run_voluntary_exit_processing_test( + spec, + state, + fork_version=state.fork.previous_version, + is_before_fork_epoch=True, + valid=False, + ) diff --git a/tests/core/pyspec/eth2spec/test/deneb/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/epoch_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/deneb/epoch_processing/test_process_registry_updates.py new file mode 100644 index 0000000000..de81a4353e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/epoch_processing/test_process_registry_updates.py @@ -0,0 +1,116 @@ +from eth2spec.test.context import ( + scaled_churn_balances_equal_activation_churn_limit, + scaled_churn_balances_exceed_activation_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_custom_state, + with_deneb_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.forks import is_post_electra +from eth2spec.test.helpers.keys import pubkeys + + +def run_process_registry_updates(spec, state): + yield from run_epoch_processing_with(spec, state, "process_registry_updates") + + +def run_test_activation_churn_limit(spec, state): + mock_activations = spec.get_validator_activation_churn_limit(state) * 2 + + validator_count_0 = len(state.validators) + + balance = spec.MIN_ACTIVATION_BALANCE if is_post_electra(spec) else spec.MAX_EFFECTIVE_BALANCE + + for i in range(mock_activations): + index = validator_count_0 + i + validator = spec.Validator( + pubkey=pubkeys[index], + withdrawal_credentials=spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + b"\x56" * 20, + activation_eligibility_epoch=0, + activation_epoch=spec.FAR_FUTURE_EPOCH, + exit_epoch=spec.FAR_FUTURE_EPOCH, + withdrawable_epoch=spec.FAR_FUTURE_EPOCH, + effective_balance=balance, + ) + state.validators.append(validator) + state.balances.append(balance) + state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(0) + state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH + + churn_limit_0 = spec.get_validator_activation_churn_limit(state) + + yield from run_process_registry_updates(spec, state) + + # Half should churn in first run of registry update + for i in range(mock_activations): + index = validator_count_0 + i + # NOTE: activations are gated different after EIP-7251 + # all eligible validators have been activated + if index < validator_count_0 + churn_limit_0 or is_post_electra(spec): + # The eligible validators within the activation churn limit should have been activated + assert state.validators[index].activation_epoch < spec.FAR_FUTURE_EPOCH + else: + assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH + + +@with_deneb_and_later +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=scaled_churn_balances_exceed_activation_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) +@single_phase +def test_activation_churn_limit__greater_than_activation_limit(spec, state): + assert ( + spec.get_validator_activation_churn_limit(state) + == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + ) + assert spec.get_validator_churn_limit(state) > spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + yield from run_test_activation_churn_limit(spec, state) + + +@with_deneb_and_later +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=scaled_churn_balances_equal_activation_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) +@single_phase +def test_activation_churn_limit__equal_to_activation_limit(spec, state): + assert ( + spec.get_validator_activation_churn_limit(state) + == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + ) + assert spec.get_validator_churn_limit(state) == spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + yield from run_test_activation_churn_limit(spec, state) + + +@with_deneb_and_later +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_state_test +def test_activation_churn_limit__less_than_activation_limit(spec, state): + assert ( + spec.get_validator_activation_churn_limit(state) + < spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + ) + assert spec.get_validator_churn_limit(state) < spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + yield from run_test_activation_churn_limit(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/fork/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_basic.py b/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_basic.py new file mode 100644 index 0000000000..17f5dbf458 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_basic.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + DENEB, + MINIMAL, +) +from eth2spec.test.helpers.deneb.fork import ( + DENEB_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[DENEB], state) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_random.py b/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_random.py new file mode 100644 index 0000000000..558706806b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/fork/test_deneb_fork_random.py @@ -0,0 +1,94 @@ +from random import Random + +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + CAPELLA, + DENEB, + MINIMAL, +) +from eth2spec.test.helpers.deneb.fork import ( + DENEB_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_state +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@spec_test +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[DENEB], state) + + +@with_phases(phases=[CAPELLA], other_phases=[DENEB]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(DENEB_FORK_TEST_META_TAGS) +def test_deneb_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[DENEB], state) diff --git a/tests/core/pyspec/eth2spec/test/deneb/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..57ce36e878 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/fork_choice/test_on_block.py @@ -0,0 +1,178 @@ +from random import Random + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, +) +from eth2spec.test.helpers.blob import get_block_with_blob +from eth2spec.test.helpers.constants import ( + DENEB, + EIP7732, + FULU, +) +from eth2spec.test.helpers.fork_choice import ( + BlobData, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block_with_data, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) + + +# TODO(jtraglia): Use with_all_phases_from_to_except after EIP7732 is based on Fulu. +# This applies to every other test in this file too. +@with_all_phases_from_except(DENEB, [FULU, EIP7732]) +@spec_state_test +def test_simple_blob_data(spec, state): + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + blob_data = BlobData(blobs, blob_kzg_proofs) + + yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) + + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # On receiving a block of next epoch + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + blob_data = BlobData(blobs, blob_kzg_proofs) + + yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) + + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +@with_all_phases_from_except(DENEB, [FULU, EIP7732]) +@spec_state_test +def test_invalid_incorrect_proof(spec, state): + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block, blobs, _ = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + # Insert incorrect proof + blob_kzg_proofs = [b"\xc0" + b"\x00" * 47] + blob_data = BlobData(blobs, blob_kzg_proofs) + + yield from tick_and_add_block_with_data( + spec, store, signed_block, test_steps, blob_data, valid=False + ) + + assert spec.get_head(store) != signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +@with_all_phases_from_except(DENEB, [FULU, EIP7732]) +@spec_state_test +def test_invalid_data_unavailable(spec, state): + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block, _, _ = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + + # data unavailable + blob_data = BlobData([], []) + + yield from tick_and_add_block_with_data( + spec, store, signed_block, test_steps, blob_data, valid=False + ) + + assert spec.get_head(store) != signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +@with_all_phases_from_except(DENEB, [FULU, EIP7732]) +@spec_state_test +def test_invalid_wrong_proofs_length(spec, state): + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block, blobs, _ = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + + # unavailable proofs + blob_data = BlobData(blobs, []) + + yield from tick_and_add_block_with_data( + spec, store, signed_block, test_steps, blob_data, valid=False + ) + + assert spec.get_head(store) != signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +@with_all_phases_from_except(DENEB, [FULU, EIP7732]) +@spec_state_test +def test_invalid_wrong_blobs_length(spec, state): + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block, _, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng) + signed_block = state_transition_and_sign_block(spec, state, block) + + # unavailable blobs + blob_data = BlobData([], blob_kzg_proofs) + + yield from tick_and_add_block_with_data( + spec, store, signed_block, test_steps, blob_data, valid=False + ) + + assert spec.get_head(store) != signed_block.message.hash_tree_root() + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/deneb/light_client/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/light_client/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/light_client/test_sync.py b/tests/core/pyspec/eth2spec/test/deneb/light_client/test_sync.py new file mode 100644 index 0000000000..163bde7b51 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/light_client/test_sync.py @@ -0,0 +1,31 @@ +from eth2spec.test.context import ( + spec_test, + with_config_overrides, + with_matching_spec_config, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.light_client_sync import ( + run_lc_sync_test_single_fork, +) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_config_overrides( + { + "ELECTRA_FORK_EPOCH": 3, # Test setup advances to epoch 2 + }, + emit=False, +) +@with_state +@with_matching_spec_config(emitted_fork=ELECTRA) +@with_presets([MINIMAL], reason="too slow") +def test_electra_fork(spec, phases, state): + yield from run_lc_sync_test_single_fork(spec, phases, state, ELECTRA) diff --git a/tests/core/pyspec/eth2spec/test/deneb/merkle_proof/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/merkle_proof/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/deneb/merkle_proof/test_single_merkle_proof.py new file mode 100644 index 0000000000..37b4ef779b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/merkle_proof/test_single_merkle_proof.py @@ -0,0 +1,124 @@ +import random + +from eth2spec.debug.random_value import ( + get_random_ssz_object, + RandomizationMode, +) +from eth2spec.test.context import ( + spec_state_test, + with_deneb_and_later, + with_test_suite_name, +) +from eth2spec.test.helpers.blob import ( + get_sample_blob_tx, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + sign_block, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) +from eth2spec.test.helpers.forks import is_post_eip7732 + + +def _run_blob_kzg_commitment_merkle_proof_test(spec, state, rng=None): + opaque_tx, blobs, blob_kzg_commitments, proofs = get_sample_blob_tx(spec, blob_count=1) + if rng is None: + block = build_empty_block_for_next_slot(spec, state) + else: + block = get_random_ssz_object( + rng, + spec.BeaconBlock, + max_bytes_length=2000, + max_list_length=2000, + mode=RandomizationMode, + chaos=True, + ) + if is_post_eip7732(spec): + blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + blob_kzg_commitments + ) + kzg_root = blob_kzg_commitments.hash_tree_root() + block.body.signed_execution_payload_header.message.blob_kzg_commitments_root = kzg_root + else: + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + signed_block = sign_block(spec, state, block, proposer_index=0) + if is_post_eip7732(spec): + blob_sidecars = spec.get_blob_sidecars(signed_block, blobs, blob_kzg_commitments, proofs) + else: + blob_sidecars = spec.get_blob_sidecars(signed_block, blobs, proofs) + blob_index = 0 + blob_sidecar = blob_sidecars[blob_index] + + yield "object", block.body + kzg_commitment_inclusion_proof = blob_sidecar.kzg_commitment_inclusion_proof + + if is_post_eip7732(spec): + inner_gindex = spec.get_generalized_index( + spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK], blob_index + ) + outer_gindex = spec.get_generalized_index( + spec.BeaconBlockBody, + "signed_execution_payload_header", + "message", + "blob_kzg_commitments_root", + ) + gindex = spec.concat_generalized_indices(outer_gindex, inner_gindex) + else: + gindex = spec.get_generalized_index( + spec.BeaconBlockBody, "blob_kzg_commitments", blob_index + ) + + yield ( + "proof", + { + "leaf": "0x" + blob_sidecar.kzg_commitment.hash_tree_root().hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in kzg_commitment_inclusion_proof], + }, + ) + + assert spec.is_valid_merkle_branch( + leaf=blob_sidecar.kzg_commitment.hash_tree_root(), + branch=blob_sidecar.kzg_commitment_inclusion_proof, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=blob_sidecar.signed_block_header.message.body_root, + ) + + +@with_test_suite_name("BeaconBlockBody") +@with_deneb_and_later +@spec_state_test +def test_blob_kzg_commitment_merkle_proof__basic(spec, state): + yield from _run_blob_kzg_commitment_merkle_proof_test(spec, state) + + +@with_test_suite_name("BeaconBlockBody") +@with_deneb_and_later +@spec_state_test +def test_blob_kzg_commitment_merkle_proof__random_block_1(spec, state): + rng = random.Random(1111) + yield from _run_blob_kzg_commitment_merkle_proof_test(spec, state, rng=rng) + + +@with_test_suite_name("BeaconBlockBody") +@with_deneb_and_later +@spec_state_test +def test_blob_kzg_commitment_merkle_proof__random_block_2(spec, state): + rng = random.Random(2222) + yield from _run_blob_kzg_commitment_merkle_proof_test(spec, state, rng=rng) + + +@with_test_suite_name("BeaconBlockBody") +@with_deneb_and_later +@spec_state_test +def test_blob_kzg_commitment_merkle_proof__random_block_3(spec, state): + rng = random.Random(3333) + yield from _run_blob_kzg_commitment_merkle_proof_test(spec, state, rng=rng) diff --git a/tests/core/pyspec/eth2spec/test/deneb/random/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/random/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/random/test_random.py b/tests/core/pyspec/eth2spec/test/deneb/random/test_random.py new file mode 100644 index 0000000000..461b30db20 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/random/test_random.py @@ -0,0 +1,1172 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.context import ( + always_bls, + misc_balances_in_default_range_with_many_validators, + only_generator, + single_phase, + spec_test, + with_custom_state, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.constants import DENEB +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([DENEB]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_deneb + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_deneb", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_deneb", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/deneb/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py new file mode 100644 index 0000000000..9d272a1790 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/sanity/test_blocks.py @@ -0,0 +1,129 @@ +import random + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, +) +from eth2spec.test.helpers.blob import ( + get_max_blob_count, + get_sample_blob_tx, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + DENEB, + EIP7732, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, + get_random_tx, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) + + +def run_block_with_blobs( + spec, + state, + blob_count, + tx_count=1, + blob_gas_used=1, + excess_blob_gas=1, + non_blob_tx_count=0, + rng=random.Random(7777), + valid=True, +): + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + txs = [] + blob_kzg_commitments = [] + for _ in range(tx_count): + opaque_tx, _, commits, _ = get_sample_blob_tx(spec, blob_count=blob_count) + txs.append(opaque_tx) + blob_kzg_commitments += commits + + for _ in range(non_blob_tx_count): + txs.append(get_random_tx(rng)) + + rng.shuffle(txs) + + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = txs + block.body.execution_payload.blob_gas_used = blob_gas_used + block.body.execution_payload.excess_blob_gas = excess_blob_gas + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + if valid: + signed_block = state_transition_and_sign_block(spec, state, block) + else: + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", state if valid else None + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_zero_blob(spec, state): + yield from run_block_with_blobs(spec, state, blob_count=0) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_one_blob(spec, state): + yield from run_block_with_blobs(spec, state, blob_count=1) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_one_blob_two_txs(spec, state): + yield from run_block_with_blobs(spec, state, blob_count=1, tx_count=2) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_one_blob_max_txs(spec, state): + yield from run_block_with_blobs( + spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state) + ) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_invalid_one_blob_max_plus_one_txs(spec, state): + yield from run_block_with_blobs( + spec, state, blob_count=1, tx_count=get_max_blob_count(spec, state) + 1, valid=False + ) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_max_blobs_per_block(spec, state): + yield from run_block_with_blobs(spec, state, blob_count=get_max_blob_count(spec, state)) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_invalid_max_blobs_per_block_two_txs(spec, state): + yield from run_block_with_blobs( + spec, state, blob_count=get_max_blob_count(spec, state), tx_count=2, valid=False + ) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_invalid_exceed_max_blobs_per_block(spec, state): + yield from run_block_with_blobs( + spec, state, blob_count=get_max_blob_count(spec, state) + 1, valid=False + ) + + +@with_all_phases_from_except(DENEB, [EIP7732]) +@spec_state_test +def test_mix_blob_tx_and_non_blob_tx(spec, state): + yield from run_block_with_blobs(spec, state, blob_count=1, tx_count=1, non_blob_tx_count=1) diff --git a/tests/core/pyspec/eth2spec/test/deneb/transition/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/transition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py new file mode 100644 index 0000000000..3904b1877d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/transition/test_operations.py @@ -0,0 +1,129 @@ +from eth2spec.test.context import ( + always_bls, + ForkMeta, + with_fork_metas, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + AFTER_DENEB_PRE_POST_FORKS, + DENEB, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + OperationType, + run_transition_with_operation, + transition_until_fork, +) +from eth2spec.test.helpers.state import ( + next_epoch_via_block, + state_transition_and_sign_block, + transition_to, +) + +# +# BLSToExecutionChange +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_DENEB_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_btec_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a BLS_TO_EXECUTION_CHANGE right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.BLS_TO_EXECUTION_CHANGE, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_DENEB_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_btec_right_before_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a BLS_TO_EXECUTION_CHANGE right *before* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.BLS_TO_EXECUTION_CHANGE, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH - 1, + ) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_DENEB_PRE_POST_FORKS + ] +) +def test_transition_attestation_from_previous_fork_with_new_range( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + [EIP-7045] test + """ + # Transition to the epoch prior to the fork epoch + next_epoch_via_block(spec, state) + + # Generate an attestation for slot 0 of this epoch + if spec.fork == DENEB: + # NOTE: attestation format changes from Deneb to Electra + # so the attestation must be made with the `post_spec` + target_spec = post_spec + target_state = post_spec.upgrade_to_electra(state.copy()) + target_state.fork = state.fork + else: + target_spec = spec + target_state = state + attestation = get_valid_attestation(target_spec, target_state, signed=True) + + yield "pre", state + + # Transition to the fork epoch with a block + transition_until_fork(spec, state, fork_epoch) + state, fork_block = do_fork(state, spec, post_spec, fork_epoch) + current_epoch = spec.get_current_epoch(state) + assert current_epoch == fork_epoch + # Transition to second to last slot in `fork_epoch` + penultimate_slot = post_spec.compute_start_slot_at_epoch(current_epoch + 1) - 2 + transition_to(post_spec, state, penultimate_slot) + + # Ensure the new state is in the increased EIP-7045 slot inclusion range + assert penultimate_slot - attestation.data.slot > post_spec.SLOTS_PER_EPOCH + + block = build_empty_block_for_next_slot(post_spec, state) + block.body.attestations.append(attestation) + signed_block = state_transition_and_sign_block(post_spec, state, block) + + yield "blocks", [post_tag(fork_block), post_tag(signed_block)] + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/deneb/transition/test_transition.py b/tests/core/pyspec/eth2spec/test/deneb/transition/test_transition.py new file mode 100644 index 0000000000..5c1b42288f --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/transition/test_transition.py @@ -0,0 +1,86 @@ +from eth2spec.test.context import ( + ForkMeta, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + AFTER_DENEB_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_to_next_epoch_and_append_blocks, + transition_until_fork, +) +from eth2spec.test.helpers.keys import pubkeys + + +def mock_activated_validators(spec, state, mock_activations): + validator_count = len(state.validators) + for i in range(mock_activations): + index = validator_count + i + validator = spec.Validator( + pubkey=pubkeys[index], + withdrawal_credentials=spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + b"\x56" * 20, + activation_eligibility_epoch=0, + activation_epoch=spec.FAR_FUTURE_EPOCH, + exit_epoch=spec.FAR_FUTURE_EPOCH, + withdrawable_epoch=spec.FAR_FUTURE_EPOCH, + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + ) + state.validators.append(validator) + state.balances.append(spec.MAX_EFFECTIVE_BALANCE) + state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) + state.inactivity_scores.append(0) + state.validators[index].activation_epoch = spec.get_current_epoch(state) + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_DENEB_PRE_POST_FORKS + ] +) +@with_presets([MINIMAL], reason="churn limit update needs enough validators") +def test_higher_churn_limit_to_lower(state, fork_epoch, spec, post_spec, pre_tag, post_tag): + """ + Test if churn limit goes from high to low due to EIP-7514. + """ + # Create high churn limit + mock_activations = ( + post_spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT * spec.config.CHURN_LIMIT_QUOTIENT + ) + mock_activated_validators(spec, state, mock_activations) + + transition_until_fork(spec, state, fork_epoch) + + churn_limit_0 = spec.get_validator_churn_limit(state) + assert churn_limit_0 > post_spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + + # check pre state + assert spec.get_current_epoch(state) < fork_epoch + + yield "pre", state + + # irregular state transition to handle fork + blocks = [] + state, block = do_fork(state, spec, post_spec, fork_epoch) + blocks.append(post_tag(block)) + + # check post state + assert spec.get_current_epoch(state) == fork_epoch + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + yield "blocks", blocks + yield "post", state + + churn_limit_1 = post_spec.get_validator_activation_churn_limit(state) + assert churn_limit_1 == post_spec.config.MAX_PER_EPOCH_ACTIVATION_CHURN_LIMIT + assert churn_limit_1 < churn_limit_0 diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py new file mode 100644 index 0000000000..aa72570715 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -0,0 +1,320 @@ +import random + +from eth2spec.test.context import ( + always_bls, + expect_assertion_error, + single_phase, + spec_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.blob import ( + eval_poly_in_coeff_form, + get_poly_in_both_forms, + get_sample_blob, +) +from eth2spec.utils import bls +from eth2spec.utils.bls import BLS_MODULUS + +G1 = bls.G1_to_bytes48(bls.G1()) +P1_NOT_IN_G1 = bytes.fromhex( + "8123456789abcdef0123456789abcdef0123456789abcdef" + + "0123456789abcdef0123456789abcdef0123456789abcdef" +) +P1_NOT_ON_CURVE = bytes.fromhex( + "8123456789abcdef0123456789abcdef0123456789abcdef" + + "0123456789abcdef0123456789abcdef0123456789abcde0" +) + + +def bls_add_one(x): + """ + Adds "one" (actually bls.G1()) to a compressed group element. + Useful to compute definitely incorrect proofs. + """ + return bls.G1_to_bytes48(bls.add(bls.bytes48_to_G1(x), bls.G1())) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_kzg_proof(spec): + """ + Test the wrapper functions (taking bytes arguments) for computing and verifying KZG proofs. + """ + x = spec.bls_field_to_bytes(spec.BLSFieldElement(3)) + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + proof, y = spec.compute_kzg_proof(blob, x) + + assert spec.verify_kzg_proof(commitment, x, y, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_kzg_proof_incorrect_proof(spec): + """ + Test the wrapper function `verify_kzg_proof` fails on an incorrect proof. + """ + x = spec.bls_field_to_bytes(spec.BLSFieldElement(3465)) + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + proof, y = spec.compute_kzg_proof(blob, x) + proof = bls_add_one(proof) + + assert not spec.verify_kzg_proof(commitment, x, y, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_kzg_proof_impl(spec): + """ + Test the implementation functions (taking field element arguments) for computing and verifying KZG proofs. + """ + x = spec.BLSFieldElement(BLS_MODULUS - 1) + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + polynomial = spec.blob_to_polynomial(blob) + proof, y = spec.compute_kzg_proof_impl(polynomial, x) + + assert spec.verify_kzg_proof_impl(commitment, x, y, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_kzg_proof_impl_incorrect_proof(spec): + """ + Test the implementation function `verify_kzg_proof` fails on an incorrect proof. + """ + x = spec.BLSFieldElement(324561) + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + polynomial = spec.blob_to_polynomial(blob) + proof, y = spec.compute_kzg_proof_impl(polynomial, x) + proof = bls_add_one(proof) + + assert not spec.verify_kzg_proof_impl(commitment, x, y, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_barycentric_outside_domain(spec): + """ + Test barycentric formula correctness by using it to evaluate a polynomial at a bunch of points outside its domain + (the roots of unity). + + Then make sure that we would get the same result if we evaluated it from coefficient form without using the + barycentric formula + """ + rng = random.Random(5566) + poly_coeff, poly_eval = get_poly_in_both_forms(spec) + roots_of_unity_brp = spec.bit_reversal_permutation( + spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + ) + + assert len(poly_coeff) == len(poly_eval) == len(roots_of_unity_brp) + n_samples = 12 + + for _ in range(n_samples): + # Get a random evaluation point and make sure it's not a root of unity + z = spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + while z in roots_of_unity_brp: + z = spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + + # Get p(z) by evaluating poly in coefficient form + p_z_coeff = eval_poly_in_coeff_form(spec, poly_coeff, z) + + # Get p(z) by evaluating poly in evaluation form + p_z_eval = spec.evaluate_polynomial_in_evaluation_form(poly_eval, z) + + # Both evaluations should agree + assert p_z_coeff == p_z_eval + + +@with_deneb_and_later +@spec_test +@single_phase +def test_barycentric_within_domain(spec): + """ + Test barycentric formula correctness by using it to evaluate a polynomial at various points inside its domain + (the roots of unity). + + Then make sure that we would get the same result if we evaluated it from coefficient form without using the + barycentric formula + """ + rng = random.Random(5566) + poly_coeff, poly_eval = get_poly_in_both_forms(spec) + roots_of_unity_brp = spec.bit_reversal_permutation( + spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + ) + + assert len(poly_coeff) == len(poly_eval) == len(roots_of_unity_brp) + n = len(poly_coeff) + + # Iterate over some roots of unity + for i in range(12): + i = rng.randint(0, n - 1) + # Grab a root of unity and use it as the evaluation point + z = roots_of_unity_brp[i] + + # Get p(z) by evaluating poly in coefficient form + p_z_coeff = eval_poly_in_coeff_form(spec, poly_coeff, z) + + # Get p(z) by evaluating poly in evaluation form + p_z_eval = spec.evaluate_polynomial_in_evaluation_form(poly_eval, z) + + # The two evaluations should be agree and p(z) should also be the i-th "coefficient" of the polynomial in + # evaluation form + assert p_z_coeff == p_z_eval == poly_eval[i] + + +@with_deneb_and_later +@spec_test +@single_phase +def test_compute_kzg_proof_within_domain(spec): + """ + Create and verify KZG proof that p(z) == y + where z is in the domain of our KZG scheme (i.e. a relevant root of unity). + """ + rng = random.Random(5566) + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + polynomial = spec.blob_to_polynomial(blob) + + roots_of_unity_brp = spec.bit_reversal_permutation( + spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + ) + + # Let's test some roots of unity + for _ in range(6): + z = rng.choice(roots_of_unity_brp) + proof, y = spec.compute_kzg_proof_impl(polynomial, z) + assert spec.verify_kzg_proof_impl(commitment, z, y, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_blob_kzg_proof(spec): + """ + Test the functions to compute and verify a blob KZG proof + """ + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) + + assert spec.verify_blob_kzg_proof(blob, commitment, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_verify_blob_kzg_proof_incorrect_proof(spec): + """ + Check that `verify_blob_kzg_proof` fails on an incorrect proof + """ + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + proof = spec.compute_blob_kzg_proof(blob, commitment) + proof = bls_add_one(proof) + + assert not spec.verify_blob_kzg_proof(blob, commitment, proof) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_validate_kzg_g1_generator(spec): + """ + Verify that `validate_kzg_g1` allows the generator G1 + """ + + spec.validate_kzg_g1(bls.G1_to_bytes48(bls.G1())) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_validate_kzg_g1_neutral_element(spec): + """ + Verify that `validate_kzg_g1` allows the neutral element in G1 + """ + + spec.validate_kzg_g1(bls.G1_to_bytes48(bls.Z1())) + + +@with_deneb_and_later +@spec_test +@single_phase +@always_bls +def test_validate_kzg_g1_not_in_g1(spec): + """ + Verify that `validate_kzg_g1` fails on point not in G1 + """ + + expect_assertion_error(lambda: spec.validate_kzg_g1(P1_NOT_IN_G1)) + + +@with_deneb_and_later +@spec_test +@single_phase +@always_bls +def test_validate_kzg_g1_not_on_curve(spec): + """ + Verify that `validate_kzg_g1` fails on point not in G1 + """ + + expect_assertion_error(lambda: spec.validate_kzg_g1(P1_NOT_ON_CURVE)) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_bytes_to_bls_field_zero(spec): + """ + Verify that `bytes_to_bls_field` handles zero + """ + + spec.bytes_to_bls_field(b"\0" * 32) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_bytes_to_bls_field_modulus_minus_one(spec): + """ + Verify that `bytes_to_bls_field` handles modulus minus one + """ + + spec.bytes_to_bls_field( + (BLS_MODULUS - 1).to_bytes(spec.BYTES_PER_FIELD_ELEMENT, spec.KZG_ENDIANNESS) + ) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_bytes_to_bls_field_modulus(spec): + """ + Verify that `bytes_to_bls_field` fails on BLS modulus + """ + + expect_assertion_error( + lambda: spec.bytes_to_bls_field( + BLS_MODULUS.to_bytes(spec.BYTES_PER_FIELD_ELEMENT, spec.KZG_ENDIANNESS) + ) + ) + + +@with_deneb_and_later +@spec_test +@single_phase +def test_bytes_to_bls_field_max(spec): + """ + Verify that `bytes_to_bls_field` fails on 2**256 - 1 + """ + + expect_assertion_error(lambda: spec.bytes_to_bls_field(b"\xff" * 32)) diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py new file mode 100644 index 0000000000..a63e8ed6e3 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_config_invariants.py @@ -0,0 +1,42 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.forks import is_post_eip7732 + + +@with_deneb_and_later +@spec_test +@single_phase +def test_length(spec): + assert spec.config.MAX_BLOBS_PER_BLOCK < spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + + +@with_deneb_and_later +@spec_test +@single_phase +def test_networking(spec): + assert spec.config.MAX_BLOBS_PER_BLOCK < spec.MAX_BLOB_COMMITMENTS_PER_BLOCK + assert ( + spec.config.MAX_REQUEST_BLOB_SIDECARS + == spec.config.MAX_REQUEST_BLOCKS_DENEB * spec.config.MAX_BLOBS_PER_BLOCK + ) + # Start with the same size, but `BLOB_SIDECAR_SUBNET_COUNT` could potentially increase later. + assert spec.config.BLOB_SIDECAR_SUBNET_COUNT == spec.config.MAX_BLOBS_PER_BLOCK + for i in range(spec.MAX_BLOB_COMMITMENTS_PER_BLOCK): + if is_post_eip7732(spec): + inner_gindex = spec.get_generalized_index( + spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK], i + ) + outer_gindex = spec.get_generalized_index( + spec.BeaconBlockBody, + "signed_execution_payload_header", + "message", + "blob_kzg_commitments_root", + ) + gindex = spec.concat_generalized_indices(outer_gindex, inner_gindex) + assert spec.floorlog2(gindex) == spec.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH_EIP7732 + else: + gindex = spec.get_generalized_index(spec.BeaconBlockBody, "blob_kzg_commitments", i) + assert spec.floorlog2(gindex) == spec.KZG_COMMITMENT_INCLUSION_PROOF_DEPTH diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..c877ee488c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/test_execution_engine_interface.py @@ -0,0 +1,63 @@ +from eth2spec.test.context import ( + DENEB, + spec_state_test, + with_deneb_and_later, + with_phases, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot + + +@with_deneb_and_later +@spec_state_test +def test_noop_execution_engine_is_valid_versioned_hashes(spec, state): + """ + Test NoopExecutionEngine.is_valid_versioned_hashes returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + pre_state = state.copy() + + # Test is_valid_versioned_hashes + result = engine.is_valid_versioned_hashes(new_payload_request=None) + + # Verify behavior + assert result is True + assert state == pre_state + + +@with_phases([DENEB]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_deneb(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + ) + + assert result is True + + +@with_phases([DENEB]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_deneb(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + ) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/__init__.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py b/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py new file mode 100644 index 0000000000..7a0ff4d794 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/deneb/unittests/validator/test_validator.py @@ -0,0 +1,84 @@ +import random + +from eth2spec.test.context import ( + spec_state_test, + with_deneb_and_later, +) +from eth2spec.test.helpers.blob import ( + get_sample_blob_tx, +) +from eth2spec.test.helpers.block import build_empty_block_for_next_slot, sign_block +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) +from eth2spec.test.helpers.forks import is_post_eip7732 + + +def _get_sample_sidecars(spec, state, rng): + block = build_empty_block_for_next_slot(spec, state) + + # 2 txs, each has 2 blobs + blob_count = 2 + opaque_tx_1, blobs_1, blob_kzg_commitments_1, proofs_1 = get_sample_blob_tx( + spec, blob_count=blob_count, rng=rng + ) + opaque_tx_2, blobs_2, blob_kzg_commitments_2, proofs_2 = get_sample_blob_tx( + spec, blob_count=blob_count, rng=rng + ) + assert opaque_tx_1 != opaque_tx_2 + + if is_post_eip7732(spec): + blob_kzg_commitments = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]( + blob_kzg_commitments_1 + blob_kzg_commitments_2 + ) + kzg_root = blob_kzg_commitments.hash_tree_root() + block.body.signed_execution_payload_header.message.blob_kzg_commitments_root = kzg_root + else: + block.body.blob_kzg_commitments = blob_kzg_commitments_1 + blob_kzg_commitments_2 + block.body.execution_payload.transactions = [opaque_tx_1, opaque_tx_2] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + + blobs = blobs_1 + blobs_2 + proofs = proofs_1 + proofs_2 + signed_block = sign_block(spec, state, block, proposer_index=0) + if is_post_eip7732(spec): + return spec.get_blob_sidecars(signed_block, blobs, blob_kzg_commitments, proofs) + return spec.get_blob_sidecars(signed_block, blobs, proofs) + + +@with_deneb_and_later +@spec_state_test +def test_blob_sidecar_inclusion_proof_correct(spec, state): + rng = random.Random(1234) + blob_sidecars = _get_sample_sidecars(spec, state, rng) + + for blob_sidecar in blob_sidecars: + assert spec.verify_blob_sidecar_inclusion_proof(blob_sidecar) + + +@with_deneb_and_later +@spec_state_test +def test_blob_sidecar_inclusion_proof_incorrect_wrong_body(spec, state): + rng = random.Random(1234) + blob_sidecars = _get_sample_sidecars(spec, state, rng) + + for blob_sidecar in blob_sidecars: + block = blob_sidecar.signed_block_header.message + block.body_root = spec.hash(block.body_root) # mutate body root to break proof + assert not spec.verify_blob_sidecar_inclusion_proof(blob_sidecar) + + +@with_deneb_and_later +@spec_state_test +def test_blob_sidecar_inclusion_proof_incorrect_wrong_proof(spec, state): + rng = random.Random(1234) + blob_sidecars = _get_sample_sidecars(spec, state, rng) + + for blob_sidecar in blob_sidecars: + # wrong proof + blob_sidecar.kzg_commitment_inclusion_proof = spec.compute_merkle_proof( + spec.BeaconBlockBody(), 0 + ) + assert not spec.verify_blob_sidecar_inclusion_proof(blob_sidecar) diff --git a/tests/core/pyspec/eth2spec/test/eip7441/__init__.py b/tests/core/pyspec/eth2spec/test/eip7441/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7441/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_opening_proof.py b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_opening_proof.py new file mode 100644 index 0000000000..ea8f240358 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_opening_proof.py @@ -0,0 +1,67 @@ +from eth2spec.test.context import expect_assertion_error, spec_state_test, with_eip7441_and_later +from eth2spec.test.helpers.eip7441 import ( + compute_whisk_k_commitment, + compute_whisk_tracker, + set_opening_proof, +) + + +def empty_block(spec): + return spec.BeaconBlock() + + +def run_process_whisk_opening_proof(spec, state, block, valid=True): + yield "pre", state + yield "block", block + + if not valid: + expect_assertion_error(lambda: spec.process_whisk_opening_proof(state, block)) + yield "post", None + return + + spec.process_whisk_opening_proof(state, block) + + yield "post", state + + +PROPOSER_INDEX = 0 +K_OK = 2 +K_WRONG = 3 +R_OK = 2 +R_WRONG = 3 + + +@with_eip7441_and_later +@spec_state_test +def test_valid_proof(spec, state): + block = empty_block(spec) + set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK) + run_process_whisk_opening_proof(spec, state, block) + + +@with_eip7441_and_later +@spec_state_test +def test_wrong_commitment(spec, state): + block = empty_block(spec) + set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK) + state.whisk_k_commitments[PROPOSER_INDEX] = compute_whisk_k_commitment(K_WRONG) + run_process_whisk_opening_proof(spec, state, block, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_wrong_tracker_r(spec, state): + block = empty_block(spec) + set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK) + wrong_tracker = compute_whisk_tracker(K_OK, R_WRONG) + state.whisk_proposer_trackers[state.slot % spec.PROPOSER_TRACKERS_COUNT] = wrong_tracker + run_process_whisk_opening_proof(spec, state, block, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_wrong_proof(spec, state): + block = empty_block(spec) + set_opening_proof(spec, state, block, PROPOSER_INDEX, K_OK, R_OK) + block.body.whisk_opening_proof = spec.WhiskTrackerProof() + run_process_whisk_opening_proof(spec, state, block, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_registration.py b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_registration.py new file mode 100644 index 0000000000..49ffb2c40e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_eip7441_registration.py @@ -0,0 +1,109 @@ +from eth2spec.test.context import expect_assertion_error, spec_state_test, with_eip7441_and_later +from eth2spec.test.helpers.eip7441 import ( + compute_whisk_k_commitment, + register_tracker, + set_as_first_proposal, + set_registration, +) + + +def empty_block_body(spec): + return spec.BeaconBlockBody() + + +def set_as_first_proposal_and_proposer(spec, state, proposer_index): + state.latest_block_header.proposer_index = proposer_index + set_as_first_proposal(spec, state, proposer_index) + + +def run_process_whisk_registration(spec, state, body, valid=True): + yield "pre", state + yield "body", body + + if not valid: + expect_assertion_error(lambda: spec.process_whisk_registration(state, body)) + yield "post", None + return + + spec.process_whisk_registration(state, body) + + yield "post", state + + +IDENTITY_R = 1 +OTHER_R = 100_000_2 # Large enough values to not collide with initial k values +OTHER_K = 100_000_2 +PROPOSER_INDEX = 0 +OTHER_INDEX = 1 + +# First proposal + + +@with_eip7441_and_later +@spec_state_test +def test_first_proposal_ok(spec, state): + body = empty_block_body(spec) + set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX) + set_registration(body, OTHER_K, OTHER_R) + yield from run_process_whisk_registration(spec, state, body) + + +@with_eip7441_and_later +@spec_state_test +def test_first_proposal_identity_tracker(spec, state): + body = empty_block_body(spec) + set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX) + set_registration(body, OTHER_K, IDENTITY_R) + yield from run_process_whisk_registration(spec, state, body, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_first_proposal_non_unique_k_other(spec, state): + body = empty_block_body(spec) + set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX) + state.whisk_k_commitments[OTHER_INDEX] = compute_whisk_k_commitment(OTHER_K) + set_registration(body, OTHER_K, OTHER_R) + yield from run_process_whisk_registration(spec, state, body, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_first_proposal_non_unique_k_self(spec, state): + body = empty_block_body(spec) + set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX) + state.whisk_k_commitments[PROPOSER_INDEX] = compute_whisk_k_commitment(OTHER_K) + set_registration(body, OTHER_K, OTHER_R) + yield from run_process_whisk_registration(spec, state, body, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_first_proposal_invalid_proof(spec, state): + body = empty_block_body(spec) + set_as_first_proposal_and_proposer(spec, state, PROPOSER_INDEX) + set_registration(body, OTHER_K, OTHER_R) + body.whisk_tracker.k_r_G = spec.BLSG1Point() + yield from run_process_whisk_registration(spec, state, body, valid=False) + + +# Second proposal + + +@with_eip7441_and_later +@spec_state_test +def test_second_proposal_ok(spec, state): + body = empty_block_body(spec) + # An empty body has the correct values for a second proposal + # Set tracker to != G1 generator for second proposal condition + register_tracker(state, PROPOSER_INDEX, OTHER_K, OTHER_R) + yield from run_process_whisk_registration(spec, state, body) + + +@with_eip7441_and_later +@spec_state_test +def test_second_proposal_not_zero(spec, state): + body = empty_block_body(spec) + set_registration(body, OTHER_K, OTHER_R) + register_tracker(state, PROPOSER_INDEX, OTHER_K, OTHER_R) + yield from run_process_whisk_registration(spec, state, body, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_shuffled_trackers.py b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_shuffled_trackers.py new file mode 100644 index 0000000000..24cbed2cdf --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/block_processing/test_process_shuffled_trackers.py @@ -0,0 +1,137 @@ +from curdleproofs import GenerateWhiskShuffleProof + +from eth2spec.test.context import expect_assertion_error, spec_state_test, with_eip7441_and_later +from eth2spec.test.helpers.eip7441 import compute_whisk_tracker +from eth2spec.test.helpers.keys import whisk_ks_initial + + +def set_correct_shuffle_proofs(spec, state, body): + pre_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body) + + post_trackers, shuffle_proof = GenerateWhiskShuffleProof( + spec.CURDLEPROOFS_CRS, pre_shuffle_trackers + ) + body.whisk_post_shuffle_trackers = post_trackers + body.whisk_shuffle_proof = shuffle_proof + + +def get_and_populate_pre_shuffle_trackers(spec, state, body): + shuffle_indices = spec.get_shuffle_indices(body.randao_reveal) + pre_shuffle_trackers = [] + for i in shuffle_indices: + # Set r to some value > 1 ( = 2+i) + tracker = compute_whisk_tracker(whisk_ks_initial(i), 2 + i) + state.whisk_candidate_trackers[i] = tracker + pre_shuffle_trackers.append(tracker) + return pre_shuffle_trackers + + +def get_pre_shuffle_trackers(spec, state, body): + return [state.whisk_candidate_trackers[i] for i in spec.get_shuffle_indices(body.randao_reveal)] + + +def set_state_epoch(spec, state, epoch): + state.slot = epoch * spec.SLOTS_PER_EPOCH + + +def set_state_epoch_selection_gap(spec, state): + set_state_epoch(spec, state, spec.config.EPOCHS_PER_SHUFFLING_PHASE - 1) + + +def empty_block_body(spec): + return spec.BeaconBlockBody() + + +def run_process_shuffled_trackers(spec, state, body, valid=True): + yield "pre", state + yield "body", body + + if not valid: + expect_assertion_error(lambda: spec.process_shuffled_trackers(state, body)) + yield "post", None + return + + spec.process_shuffled_trackers(state, body) + + yield "post", state + + +@with_eip7441_and_later +@spec_state_test +def test_shuffle_trackers(spec, state): + body = empty_block_body(spec) + set_correct_shuffle_proofs(spec, state, body) + yield from run_process_shuffled_trackers(spec, state, body) + + +@with_eip7441_and_later +@spec_state_test +def test_no_shuffle_minus_selection_gap(spec, state): + body = empty_block_body(spec) + set_state_epoch_selection_gap(spec, state) + yield from run_process_shuffled_trackers(spec, state, body) + + +@with_eip7441_and_later +@spec_state_test +def test_no_shuffle_minus_one_and_selection_gap(spec, state): + body = empty_block_body(spec) + set_state_epoch( + spec, state, spec.config.EPOCHS_PER_SHUFFLING_PHASE - spec.config.PROPOSER_SELECTION_GAP - 1 + ) + yield from run_process_shuffled_trackers(spec, state, body) + + +@with_eip7441_and_later +@spec_state_test +def test_shuffle_during_selection_gap(spec, state): + body = empty_block_body(spec) + set_correct_shuffle_proofs(spec, state, body) + set_state_epoch_selection_gap(spec, state) + yield from run_process_shuffled_trackers(spec, state, body, valid=False) + + +# Invalid cases on shuffle +# - wrong proof +# - wrong post shuffle + + +@with_eip7441_and_later +@spec_state_test +def test_invalid_shuffle_bad_proof(spec, state): + body = empty_block_body(spec) + set_correct_shuffle_proofs(spec, state, body) + body.whisk_shuffle_proof = spec.WhiskShuffleProof() + yield from run_process_shuffled_trackers(spec, state, body, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_invalid_shuffle_bad_trackers_zero(spec, state): + body = empty_block_body(spec) + set_correct_shuffle_proofs(spec, state, body) + body.whisk_post_shuffle_trackers[0] = spec.WhiskTracker() + yield from run_process_shuffled_trackers(spec, state, body, valid=False) + + +# Invalid cases on gap +# - not empty shuffle trackers +# - not empty proof + + +@with_eip7441_and_later +@spec_state_test +def test_invalid_gap_non_zero_proof(spec, state): + body = empty_block_body(spec) + body.whisk_shuffle_proof = spec.WhiskShuffleProof("0xff") + set_state_epoch_selection_gap(spec, state) + yield from run_process_shuffled_trackers(spec, state, body, valid=False) + + +@with_eip7441_and_later +@spec_state_test +def test_invalid_gap_non_zero_trackers(spec, state): + body = empty_block_body(spec) + body.whisk_post_shuffle_trackers = get_and_populate_pre_shuffle_trackers(spec, state, body) + set_state_epoch_selection_gap(spec, state) + yield from run_process_shuffled_trackers(spec, state, body, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/eip7441/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/eip7441/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/__init__.py b/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/__init__.py new file mode 100644 index 0000000000..b4abbc3c1b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/__init__.py @@ -0,0 +1 @@ +from .test_eip7441 import * # noqa: F401 F403 diff --git a/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/test_eip7441.py b/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/test_eip7441.py new file mode 100644 index 0000000000..c856feefbd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/sanity/blocks/test_eip7441.py @@ -0,0 +1,54 @@ +from curdleproofs import WhiskTracker + +from eth2spec.test.context import spec_state_test, with_eip7441_and_later +from eth2spec.test.helpers.block import build_empty_block +from eth2spec.test.helpers.eip7441 import compute_whisk_tracker_and_commitment +from eth2spec.test.helpers.keys import whisk_ks_initial +from eth2spec.test.helpers.state import state_transition_and_sign_block + +known_whisk_trackers = {} + + +def assign_proposer_at_slot(state, slot: int): + state + + +def initialize_whisk_full(spec, state): + # TODO: De-duplicate code from whisk/fork.md + for index in range(len(state.validators)): + whisk_k_commitment, whisk_tracker = spec.get_initial_commitments(whisk_ks_initial(index)) + state.whisk_k_commitments[index] = whisk_k_commitment + state.whisk_trackers[index] = whisk_tracker + + # Do a candidate selection followed by a proposer selection so that we have proposers for the upcoming day + # Use an old epoch when selecting candidates so that we don't get the same seed as in the next candidate selection + spec.select_whisk_candidate_trackers(state, spec.Epoch(0)) + spec.select_whisk_proposer_trackers(state, spec.Epoch(0)) + + +# Fill candidate trackers with the same tracker so shuffling does not break +def fill_candidate_trackers(spec, state, tracker: WhiskTracker): + for i in range(spec.CANDIDATE_TRACKERS_COUNT): + state.whisk_candidate_trackers[i] = tracker + + +@with_eip7441_and_later +@spec_state_test +def test_eip7441__process_block_single_initial(spec, state): + assert state.slot == 0 + proposer_slot_1 = 0 + tracker_slot_1, k_commitment = compute_whisk_tracker_and_commitment( + whisk_ks_initial(proposer_slot_1), 1 + ) + state.whisk_k_commitments[proposer_slot_1] = k_commitment + state.whisk_proposer_trackers[1] = tracker_slot_1 + fill_candidate_trackers(spec, state, tracker_slot_1) + + # Produce and process a whisk block + yield "pre", state + + block = build_empty_block(spec, state, 1, proposer_slot_1) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/eip7441/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/eip7441/unittests/test_config_invariants.py new file mode 100644 index 0000000000..9f615ba2e0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/eip7441/unittests/test_config_invariants.py @@ -0,0 +1,16 @@ +from eth2spec.test.context import single_phase, spec_test, with_eip7441_and_later + + +# Note: remove once whisk is rebased on top of deneb +def is_power_of_two(value: int) -> bool: + """ + Check if ``value`` is a power of two integer. + """ + return (value > 0) and (value & (value - 1) == 0) + + +@with_eip7441_and_later +@spec_test +@single_phase +def test_curdleproof(spec): + assert is_power_of_two(spec.CURDLEPROOFS_N_BLINDERS + spec.VALIDATORS_PER_SHUFFLE) diff --git a/tests/core/pyspec/eth2spec/test/electra/__init__.py b/tests/core/pyspec/eth2spec/test/electra/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/__init__.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_attestation.py new file mode 100644 index 0000000000..2d3aa4bcd0 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_attestation.py @@ -0,0 +1,180 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_electra_and_later, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + build_attestation_data, + get_empty_eip7549_aggregation_bits, + get_valid_attestation, + get_valid_attestation_at_slot, + run_attestation_processing, + sign_attestation, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.state import ( + next_slots, +) + + +@with_electra_and_later +@spec_state_test +def test_invalid_attestation_data_index_not_zero(spec, state): + """ + EIP-7549 test + """ + committee_index = 1 + attestation = get_valid_attestation(spec, state, index=committee_index) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + # flip the attestations index to make it non-zero and invalid + assert committee_index == spec.get_committee_indices(attestation.committee_bits)[0] + attestation.data.index = committee_index + + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_invalid_committee_index(spec, state): + """ + EIP-7549 test + """ + committee_index = 0 + attestation = get_valid_attestation(spec, state, index=committee_index, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + # flip the bits of the attestation to make it invalid + assert attestation.committee_bits[committee_index] == 1 + attestation.committee_bits[committee_index] = 0 + attestation.committee_bits[committee_index + 1] = 1 + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_electra_and_later +@spec_state_test +def test_invalid_too_many_committee_bits(spec, state): + """ + EIP-7549 test + """ + committee_index = 0 + attestation = get_valid_attestation(spec, state, index=committee_index, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation.committee_bits[committee_index + 1] = 1 + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_electra_and_later +@spec_state_test +def test_invalid_nonset_committee_bits(spec, state): + """ + EIP-7549 test + """ + committee_index = 0 + attestation = get_valid_attestation(spec, state, index=committee_index, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation.committee_bits[committee_index] = 0 + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need multiple committees per slot") +def test_invalid_nonset_multiple_committee_bits(spec, state): + """ + EIP-7549 test + """ + attestation_data = build_attestation_data(spec, state, slot=state.slot, index=0) + attestation = spec.Attestation(data=attestation_data) + + # a single attestation with all committees of a slot, but with unset aggregation_bits + committees_per_slot = spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) + for index in range(committees_per_slot): + attestation.committee_bits[index] = True + + attestation.aggregation_bits = get_empty_eip7549_aggregation_bits( + spec, state, attestation.committee_bits, attestation.data.slot + ) + + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need multiple committees per slot") +@always_bls +def test_multiple_committees(spec, state): + """ + EIP-7549 test + """ + # a single attestation with all committees of a slot + attestation = get_valid_attestation_at_slot(state, spec, state.slot) + + # check that all committees are presented in a single attestation + attesting_indices = set() + committees_per_slot = spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) + for index in range(committees_per_slot): + attesting_indices.update(spec.get_beacon_committee(state, state.slot, index)) + assert spec.get_attesting_indices(state, attestation) == attesting_indices + + # advance a slot + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need multiple committees per slot") +@always_bls +def test_one_committee_with_gap(spec, state): + """ + EIP-7549 test + """ + attestation = get_valid_attestation(spec, state, index=1, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need multiple committees per slot") +def test_invalid_nonset_bits_for_one_committee(spec, state): + """ + EIP-7549 test + """ + # Attestation with full committee participating + committee_0 = spec.get_beacon_committee(state, state.slot, 0) + attestation_1 = get_valid_attestation(spec, state, index=1, signed=True) + + # Create an on chain aggregate + aggregate = spec.Attestation(data=attestation_1.data, signature=attestation_1.signature) + aggregate.committee_bits[0] = True + aggregate.committee_bits[1] = True + aggregate.aggregation_bits = get_empty_eip7549_aggregation_bits( + spec, state, aggregate.committee_bits, aggregate.data.slot + ) + committee_offset = len(committee_0) + for i in range(len(attestation_1.aggregation_bits)): + aggregate.aggregation_bits[committee_offset + i] = attestation_1.aggregation_bits[i] + + # Check that only one committee is presented + assert spec.get_attesting_indices(state, aggregate) == spec.get_attesting_indices( + state, attestation_1 + ) + + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + yield from run_attestation_processing(spec, state, aggregate, valid=False) 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 new file mode 100644 index 0000000000..3afa3cb210 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_consolidation_request.py @@ -0,0 +1,1424 @@ +from eth2spec.test.context import ( + default_activation_threshold, + scaled_churn_balances_exceed_activation_exit_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_custom_state, + with_electra_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential, + set_compounding_withdrawal_credential_with_balance, + set_eth1_withdrawal_credential_with_balance, +) + +# *********************** +# * CONSOLIDATION TESTS * +# *********************** + + +@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_in_current_consolidation_epoch(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 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) + 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 + + 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(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 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) + 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_in_new_consolidation_epoch(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 + # Set consolidation balance to consume to some arbitrary nonzero value below the churn limit + state.consolidation_balance_to_consume = spec.EFFECTIVE_BALANCE_INCREMENT + 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 compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # Check consolidation churn is decremented correctly + # consolidation_balance_to_consume is replenished to the churn limit since we move to a new consolidation epoch + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + assert ( + state.consolidation_balance_to_consume + == consolidation_churn_limit - spec.MIN_ACTIVATION_BALANCE + ) + # Check exit epochs + 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_preexisting_churn(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 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) + state.earliest_consolidation_epoch = expected_exit_epoch + # Set some nonzero preexisting churn lower than churn limit and sufficient to process the consolidation + preexisting_churn = 2 * spec.MIN_ACTIVATION_BALANCE + state.consolidation_balance_to_consume = preexisting_churn + + yield from run_consolidation_processing(spec, state, consolidation) + + # Check consolidation churn is decremented correctly + assert state.consolidation_balance_to_consume == preexisting_churn - 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_insufficient_preexisting_churn(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 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(current_epoch) + # Set preexisting churn lower than required to process the consolidation + preexisting_churn = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.consolidation_balance_to_consume = preexisting_churn + + yield from run_consolidation_processing(spec, state, consolidation) + + # It takes one more epoch to process the consolidation due to insufficient churn + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 + # Check consolidation churn is decremented correctly + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = spec.MIN_ACTIVATION_BALANCE % preexisting_churn + assert state.consolidation_balance_to_consume == consolidation_churn_limit - remainder + # 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_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 compounding credentials + source_address = b"\x22" * 20 + set_compounding_withdrawal_credential(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 compounding credentials + set_compounding_withdrawal_credential(spec, state, target_index) + + # Set the consolidation balance to consume equal to churn limit + consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + state.consolidation_balance_to_consume = consolidation_churn_limit + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # 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_consolidation_churn_limit_balance(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 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) + state.validators[source_index].effective_balance = consolidation_churn_limit + # Churn limit increases due to higher total balance + updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + + yield from run_consolidation_processing(spec, state, consolidation) + + # validator's effective balance fits into the churn, exit as soon as possible + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == updated_consolidation_churn_limit - consolidation_churn_limit + ) + # 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_source_has_less_than_max_effective_balance(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) + + # Lower the source validator's effective balance + source_effective_balance = spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.validators[source_index].effective_balance = source_effective_balance + + # 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 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) + 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 + + yield from run_consolidation_processing(spec, state, consolidation) + + # Check consolidation churn is decremented correctly + assert ( + state.consolidation_balance_to_consume + == consolidation_churn_limit - source_effective_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_target_has_less_than_min_activation_effective_balance(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) + + # Lower the target validator's effective balance + # This shouldn't prevent the consolidation from happening + target_effective_balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.validators[target_index].effective_balance = target_effective_balance + + # 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 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) + 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 + + yield from run_consolidation_processing(spec, state, consolidation) + + # 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_consolidation_balance_larger_than_churn_limit(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 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) + state.validators[source_index].effective_balance = 2 * consolidation_churn_limit + + # Consolidation churn limit increases due to higher total balance + updated_consolidation_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = state.validators[source_index].effective_balance % updated_consolidation_churn_limit + expected_balance = updated_consolidation_churn_limit - remainder + + yield from run_consolidation_processing(spec, state, consolidation) + + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 1 + # Check consolidation churn is decremented correctly + assert state.consolidation_balance_to_consume == expected_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_consolidation_balance_through_two_churn_epochs(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 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) + state.validators[source_index].effective_balance = 3 * consolidation_churn_limit + + new_churn_limit = spec.get_consolidation_churn_limit(state) + remainder = state.validators[source_index].effective_balance % new_churn_limit + expected_balance = new_churn_limit - remainder + + yield from run_consolidation_processing(spec, state, consolidation) + + # when exiting a multiple of the churn limit greater than 1, an extra exit epoch is added + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + 2 + assert state.validators[0].exit_epoch == expected_exit_epoch + # since the earliest exit epoch moves to a new one, consolidation balance is back to full + assert state.consolidation_balance_to_consume == expected_balance + + +@with_electra_and_later +@spec_state_test +def test_basic_switch_to_compounding(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # 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 from source to source + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=True) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_with_excess(spec, state): + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index, address=source_address) + # Add excess balance + state.balances[source_index] = state.balances[source_index] + spec.EFFECTIVE_BALANCE_INCREMENT + # Make consolidation from source to source + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=True) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_with_pending_consolidations_at_limit(spec, state): + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0, target_index=1) + ] * spec.PENDING_CONSOLIDATIONS_LIMIT + + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # Set source to eth1 credentials + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index, address=source_address) + # Add excess balance + state.balances[source_index] = state.balances[source_index] + spec.EFFECTIVE_BALANCE_INCREMENT + # Make consolidation from source to source + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=True) + + +# Tests that should fail + + +@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_same_source_target(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] + # Set source and target to be the same + target_index = source_index + source_address = b"\x22" * 20 + # Make source/target a compounding validator (0x02) so this request isn't a + # valid switch to compounding request. To be a valid switch to compounding + # request, the source validator must be an eth1 validator (0x01). + set_compounding_withdrawal_credential_with_balance( + spec, state, target_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, + ) + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_exceed_pending_consolidations_limit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0, target_index=1) + ] * spec.PENDING_CONSOLIDATIONS_LIMIT + + # 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_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Check the the return condition + assert len(state.pending_consolidations) == spec.PENDING_CONSOLIDATIONS_LIMIT + + yield from run_consolidation_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +@single_phase +def test_incorrect_not_enough_consolidation_churn_available(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for consolidation + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + state.pending_consolidations = [spec.PendingConsolidation(source_index=0, target_index=1)] + + # 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_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 + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_exited_source(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_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # exit source + spec.initiate_validator_exit(state, source_index) + + # Check the the return condition + assert state.validators[source_index].exit_epoch != spec.FAR_FUTURE_EPOCH + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_exited_target(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_compounding_withdrawal_credential_with_balance(spec, state, target_index) + # exit target + spec.initiate_validator_exit(state, 1) + + # Check the the return condition + assert state.validators[target_index].exit_epoch != spec.FAR_FUTURE_EPOCH + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_inactive_source(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_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 + + # Check the the return condition + assert not spec.is_active_validator(state.validators[source_index], current_epoch) + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_inactive_target(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_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 + + # Check the the return condition + assert not spec.is_active_validator(state.validators[target_index], current_epoch) + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_no_source_execution_withdrawal_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 source does not have + # an execution withdrawal credential + 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 + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + 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]) + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +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 + # an execution withdrawal credential + 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, + ) + + # Check the the return condition + assert not spec.has_execution_withdrawal_credential(state.validators[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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_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 + + 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_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Ensure that the source validator has BLS-type withdrawal credentials + assert state.validators[source_index].withdrawal_credentials[:1] == spec.BLS_WITHDRAWAL_PREFIX + + # An attacker could create a new validator with BLS withdrawal credentials where the last twenty + # bytes of the BLS pubkey are hardcoded to an address that they control. To be clear, the source + # address field in consolidation requests cannot be set to an arbitrary value. + source_address = state.validators[source_index].withdrawal_credentials[-20:] + + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + + 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( + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_address(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) + # Make consolidation with different source address + consolidation = spec.ConsolidationRequest( + source_address=b"\x33" * 20, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + 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 + ) + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_pubkey_is_target_pubkey(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) + # Make consolidation with different source pubkey + consolidation = spec.ConsolidationRequest( + source_address=source_address, + # Use the target's pubkey instead + source_pubkey=state.validators[target_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + set_compounding_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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_unknown_source_pubkey(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) + # Make consolidation with different source pubkey + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=b"\x00" * 48, + target_pubkey=state.validators[target_index].pubkey, + ) + 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 + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_unknown_target_pubkey(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) + # Make consolidation with different target pubkey + consolidation = spec.ConsolidationRequest( + source_address=b"\x33" * 20, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=b"\x00" * 48, + ) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Check the return condition + assert not state.validators[target_index].pubkey == consolidation.target_pubkey + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_has_pending_withdrawal(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 + excess_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 4 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + source_index, + address=source_address, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE + excess_balance, + ) + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Create pending withdrawal + pending_withdrawal = spec.PendingPartialWithdrawal( + validator_index=0, amount=excess_balance, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals.append(pending_withdrawal) + + # Check the return condition + assert spec.get_pending_balance_to_withdraw(state, source_index) > 0 + + 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( + balances_fn=scaled_churn_balances_exceed_activation_exit_churn_limit, + threshold_fn=default_activation_threshold, +) +@spec_test +@single_phase +def test_incorrect_source_not_active_long_enough(spec, state): + # 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 + excess_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 4 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + source_index, + address=source_address, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE + excess_balance, + ) + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + 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 + ) + + yield from run_consolidation_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_exited_source(spec, state): + # Set up an otherwise correct request + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + 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[source_index].pubkey, + ) + + # Initiate exit for source + spec.initiate_validator_exit(state, source_index) + + # Check the return condition + assert state.validators[source_index].exit_epoch != spec.FAR_FUTURE_EPOCH + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_inactive_source(spec, state): + # Set up an otherwise correct request + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + 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[source_index].pubkey, + ) + + # Set source validator as not yet activated + state.validators[source_index].activation_epoch = spec.FAR_FUTURE_EPOCH + + # Check the the return condition + assert not spec.is_active_validator(state.validators[source_index], current_epoch) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_source_bls_withdrawal_credential(spec, state): + # Set up a correct request, but source does have + # a bls withdrawal credential + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + consolidation = spec.ConsolidationRequest( + source_address=state.validators[source_index].withdrawal_credentials[12:], + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + # Check the the return condition + assert not spec.has_eth1_withdrawal_credential(state.validators[source_index]) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_source_compounding_withdrawal_credential(spec, state): + # Set up a correct request, but source does have + # a compounding withdrawal credential and excess balance + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + source_address = b"\x22" * 20 + set_compounding_withdrawal_credential(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[source_index].pubkey, + ) + state.balances[source_index] = spec.MIN_ACTIVATION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + + # Check the the return condition + assert not spec.has_eth1_withdrawal_credential(state.validators[source_index]) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_not_authorized(spec, state): + # Set up an otherwise correct request + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index, address=source_address) + # Make request with different source address + consolidation = spec.ConsolidationRequest( + source_address=b"\x33" * 20, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + + # Check the the return condition + assert ( + not state.validators[source_index].withdrawal_credentials[12:] + == consolidation.source_address + ) + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +@with_electra_and_later +@spec_state_test +def test_switch_to_compounding_unknown_source_pubkey(spec, state): + # Set up an otherwise correct request + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + source_address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index, address=source_address) + # Make consolidation with different source pubkey + consolidation = spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=b"\x00" * 48, + target_pubkey=b"\x00" * 48, + ) + + # Check the the return condition + assert not state.validators[source_index].pubkey == consolidation.source_pubkey + + yield from run_switch_to_compounding_processing(spec, state, consolidation, success=False) + + +def run_consolidation_processing(spec, state, consolidation, success=True): + """ + Run ``process_consolidation``, yielding: + - pre-state ('pre') + - consolidation_request ('consolidation_request') + - post-state ('post'). + If ``success == False``, ``process_consolidation_request`` would return without any state change. + """ + if success: + validator_pubkeys = [v.pubkey for v in state.validators] + source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey)) + target_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.target_pubkey)) + source_validator = state.validators[source_index] + target_validator = state.validators[target_index] + pre_exit_epoch_source = source_validator.exit_epoch + pre_exit_epoch_target = target_validator.exit_epoch + pre_pending_consolidations = state.pending_consolidations.copy() + pre_source_balance = state.balances[source_index] + pre_target_balance = state.balances[target_index] + else: + pre_state = state.copy() + + yield "pre", state + yield "consolidation_request", consolidation + + spec.process_consolidation_request(state, consolidation) + + yield "post", state + + if success: + # Check source has execution credentials + assert spec.has_execution_withdrawal_credential(source_validator) + # Check target has compounding credentials + assert spec.has_compounding_withdrawal_credential(state.validators[target_index]) + # Check source address in the consolidation fits the withdrawal credentials + assert source_validator.withdrawal_credentials[12:] == consolidation.source_address + # Check source and target are not the same + assert source_index != target_index + # Check source and target were not exiting + assert pre_exit_epoch_source == spec.FAR_FUTURE_EPOCH + assert pre_exit_epoch_target == spec.FAR_FUTURE_EPOCH + # Check source is now exiting + assert state.validators[source_index].exit_epoch < spec.FAR_FUTURE_EPOCH + # Check that the exit epoch matches earliest_consolidation_epoch + assert state.validators[source_index].exit_epoch == state.earliest_consolidation_epoch + # Check that the withdrawable_epoch is set correctly + assert state.validators[source_index].withdrawable_epoch == ( + state.validators[source_index].exit_epoch + + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + # Check that the correct consolidation has been appended + expected_new_pending_consolidation = spec.PendingConsolidation( + source_index=source_index, + target_index=target_index, + ) + assert state.pending_consolidations == pre_pending_consolidations + [ + expected_new_pending_consolidation + ] + # 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 + + +def run_switch_to_compounding_processing(spec, state, consolidation, success=True): + """ + Run ``process_consolidation``, yielding: + - pre-state ('pre') + - consolidation_request ('consolidation_request') + - post-state ('post'). + If ``success == False``, ``process_consolidation_request`` would return without any state change. + """ + if success: + validator_pubkeys = [v.pubkey for v in state.validators] + source_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.source_pubkey)) + target_index = spec.ValidatorIndex(validator_pubkeys.index(consolidation.target_pubkey)) + source_validator = state.validators[source_index] + pre_pending_consolidations = state.pending_consolidations.copy() + pre_withdrawal_credentials = source_validator.withdrawal_credentials + pre_balance = state.balances[source_index] + else: + pre_state = state.copy() + + yield "pre", state + yield "consolidation_request", consolidation + + spec.process_consolidation_request(state, consolidation) + + yield "post", state + + if success: + # Check that source and target are same + assert source_index == target_index + # Check that the credentials before the switch are of ETH1 type + assert pre_withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + # Check source address in the consolidation fits the withdrawal credentials + assert ( + state.validators[source_index].withdrawal_credentials[12:] + == consolidation.source_address + ) + # Check that the source has switched to compounding + post_withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + pre_withdrawal_credentials[1:] + ) + assert state.validators[source_index].withdrawal_credentials == post_withdrawal_credentials + # Check excess balance is queued + assert state.balances[source_index] == spec.MIN_ACTIVATION_BALANCE + if pre_balance > spec.MIN_ACTIVATION_BALANCE: + assert len(state.pending_deposits) == 1 + pending_deposit = state.pending_deposits[0] + assert pending_deposit.pubkey == source_validator.pubkey + assert pending_deposit.withdrawal_credentials == post_withdrawal_credentials + assert pending_deposit.amount == (pre_balance - spec.MIN_ACTIVATION_BALANCE) + assert pending_deposit.signature == spec.G2_POINT_AT_INFINITY + assert pending_deposit.slot == spec.GENESIS_SLOT + # Check no consolidation has been initiated + assert state.validators[source_index].exit_epoch == spec.FAR_FUTURE_EPOCH + assert state.pending_consolidations == pre_pending_consolidations + else: + assert pre_state == state diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py new file mode 100644 index 0000000000..480b6dafe5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_deposit_request.py @@ -0,0 +1,184 @@ +from eth2spec.test.context import always_bls, spec_state_test, with_electra_and_later +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, + run_deposit_request_processing, +) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_extra_gwei(spec, state): + """The deposit amount must be at least 1 ETH and must be a multiple of gwei.""" + validator_index = len(state.validators) + # An amount with some gwei (the +1 at the end) + amount = spec.EFFECTIVE_BALANCE_INCREMENT + spec.Gwei(1) + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + # Ensure the deposit amount is not a multiple of ETH + assert state.pending_deposits[0].amount % spec.EFFECTIVE_BALANCE_INCREMENT == spec.Gwei(1) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_max_effective_balance_compounding(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + deposit_request = prepare_deposit_request( + spec, validator_index, amount, signed=True, withdrawal_credentials=withdrawal_credentials + ) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_greater_than_max_effective_balance_compounding(spec, state): + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + deposit_request = prepare_deposit_request( + spec, + validator_index, + # An amount greater than the max effective balance for electra + spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT, + signed=True, + withdrawal_credentials=withdrawal_credentials, + ) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_top_up_min_activation(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_top_up_still_less_than_min_activation(spec, state): + validator_index = 0 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + balance = 20 * spec.EFFECTIVE_BALANCE_INCREMENT + assert balance < spec.MIN_ACTIVATION_BALANCE + state.balances[validator_index] = balance + state.validators[validator_index].effective_balance = balance + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_top_up_max_effective_balance_compounding(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials + + deposit_request = prepare_deposit_request( + spec, validator_index, amount, signed=True, withdrawal_credentials=withdrawal_credentials + ) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_process_deposit_request_invalid_sig(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_process_deposit_request_top_up_invalid_sig(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + deposit_request = prepare_deposit_request(spec, validator_index, amount) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_set_start_index(spec, state): + assert state.deposit_requests_start_index == spec.UNSET_DEPOSIT_REQUESTS_START_INDEX + + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + assert state.deposit_requests_start_index == deposit_request.index + + +@with_electra_and_later +@spec_state_test +def test_process_deposit_request_set_start_index_only_once(spec, state): + initial_start_index = 1 + + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request(spec, validator_index, amount, signed=True) + + assert initial_start_index != deposit_request.index + state.deposit_requests_start_index = initial_start_index + + yield from run_deposit_request_processing(spec, state, deposit_request, validator_index) + + assert state.deposit_requests_start_index == initial_start_index 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 new file mode 100644 index 0000000000..cde2ebaf27 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_voluntary_exit.py @@ -0,0 +1,397 @@ +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import MAINNET +from eth2spec.test.helpers.keys import pubkey_to_privkey +from eth2spec.test.helpers.voluntary_exits import ( + run_voluntary_exit_processing, + sign_voluntary_exit, +) + +# ******************** +# * EXIT QUEUE TESTS * +# ******************** + + +@with_electra_and_later +@spec_state_test +def test_min_balance_exit(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + # This state has 64 validators each with 32 ETH + current_epoch = spec.get_current_epoch(state) + expected_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + churn_limit = spec.get_activation_exit_churn_limit(state) + # Set the balance to consume equal to churn limit + state.exit_balance_to_consume = churn_limit + + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[0] + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Check exit queue churn is set correctly + assert state.exit_balance_to_consume == churn_limit - spec.MIN_ACTIVATION_BALANCE + # Check exit epoch and withdrawable epoch + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +def test_min_balance_exits_up_to_churn(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + # This state has 64 validators each with 32 ETH + single_validator_balance = spec.MIN_ACTIVATION_BALANCE + churn_limit = spec.get_activation_exit_churn_limit(state) + # Set the balance to consume equal to churn limit + state.exit_balance_to_consume = churn_limit + num_to_exit = churn_limit // single_validator_balance + + # Exit all but 1 validators, all fit in the churn limit + for i in range(num_to_exit - 1): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] + spec.initiate_validator_exit(state, validator_index) + + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[ + num_to_exit + ] + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=spec.get_current_epoch(state), validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Last validator also fits in the churn limit + expected_exit_epoch = spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + # Check exit epoch and withdrawable epoch + assert state.validators[num_to_exit].exit_epoch == expected_exit_epoch + assert state.validators[num_to_exit].withdrawable_epoch == expected_withdrawable_epoch + # Check exit queue churn is set + assert state.exit_balance_to_consume == churn_limit - single_validator_balance * num_to_exit + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +def test_min_balance_exits_above_churn(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + # This state has 64 validators each with 32 ETH + single_validator_balance = spec.MIN_ACTIVATION_BALANCE + expected_exit_epoch = spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + churn_limit = spec.get_activation_exit_churn_limit(state) + # Set the balance to consume equal to churn limit + state.exit_balance_to_consume = churn_limit + num_to_exit = churn_limit // single_validator_balance + + # Exit all but 1 validators, all fit in the churn limit + for i in range(num_to_exit): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] + spec.initiate_validator_exit(state, validator_index) + + # Exit one more validator, not fitting in the churn limit + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[ + num_to_exit + ] + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=spec.get_current_epoch(state), validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Check exit epoch and withdrawable epoch. Last validator exits one epoch later + assert state.validators[num_to_exit].exit_epoch == expected_exit_epoch + 1 + assert state.validators[num_to_exit].withdrawable_epoch == expected_withdrawable_epoch + 1 + # Check exit balance to consume is set correctly + remainder = (num_to_exit + 1) * single_validator_balance % churn_limit + assert state.exit_balance_to_consume == churn_limit - remainder + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + 1 + + +@with_electra_and_later +@spec_state_test +@with_presets( + [MAINNET], + "With CHURN_LIMIT_QUOTIENT=32, can't change validator balance without changing churn_limit", +) +def test_max_balance_exit(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + churn_limit = spec.get_activation_exit_churn_limit(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + # Set validator effective balance to 2048 ETH + to_exit = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.validators[validator_index].effective_balance = to_exit + + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Check exit epoch and withdrawable epoch + earliest_exit_epoch = spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + additional_epochs = (to_exit - 1) // churn_limit + expected_exit_epoch = earliest_exit_epoch + additional_epochs + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check exit_balance_to_consume + assert state.exit_balance_to_consume == (additional_epochs + 1) * churn_limit - to_exit + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +@with_presets( + [MAINNET], + "With CHURN_LIMIT_QUOTIENT=32, can't change validator balance without changing churn_limit", +) +def test_exit_with_balance_equal_to_churn_limit(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + churn_limit = spec.get_activation_exit_churn_limit(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + # Set 0th validator effective balance to churn_limit + state.validators[validator_index].effective_balance = churn_limit + + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Validator consumes churn limit fully in the current epoch + expected_exit_epoch = spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check exit_balance_to_consume + assert state.exit_balance_to_consume == 0 + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +@with_presets( + [MAINNET], + "With CHURN_LIMIT_QUOTIENT=32, can't change validator balance without changing churn_limit", +) +def test_exit_with_balance_multiple_of_churn_limit(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + churn_limit = spec.get_activation_exit_churn_limit(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + # Set validator effective balance to a multiple of churn_limit + epochs_to_consume = 3 + state.validators[validator_index].effective_balance = epochs_to_consume * churn_limit + + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Validator consumes churn limit fully in epochs_to_consume epochs + expected_exit_epoch = ( + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + epochs_to_consume - 1 + ) + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check exit_balance_to_consume + assert state.exit_balance_to_consume == 0 + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +@with_presets( + [MAINNET], + "With CHURN_LIMIT_QUOTIENT=32, can't change validator balance without changing churn_limit", +) +def test_exit_existing_churn_and_churn_limit_balance(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + churn_limit = spec.get_activation_exit_churn_limit(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # set exit epoch to the first available one and set exit balance to consume to full churn limit + earliest_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + state.earliest_exit_epoch = earliest_exit_epoch + state.exit_balance_to_consume = churn_limit + # consume some churn in exit epoch + existing_churn = spec.EFFECTIVE_BALANCE_INCREMENT + state.exit_balance_to_consume -= existing_churn + # Set validator effective balance to the churn limit + state.validators[validator_index].effective_balance = churn_limit + + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + expected_exit_epoch = earliest_exit_epoch + 1 + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + # Check exit epoch + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check balance consumed in exit epoch is the remainder 1 ETH + assert state.exit_balance_to_consume == churn_limit - existing_churn + # check earliest exit epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +@with_presets( + [MAINNET], + "With CHURN_LIMIT_QUOTIENT=32, can't change validator balance without changing churn_limit", +) +def test_exit_existing_churn_and_balance_multiple_of_churn_limit(spec, state): + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + churn_limit = spec.get_activation_exit_churn_limit(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + + # set exit epoch to the first available one and set exit balance to consume to full churn limit + earliest_exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + state.earliest_exit_epoch = earliest_exit_epoch + state.exit_balance_to_consume = churn_limit + # consume some churn in exit epoch + existing_churn = spec.EFFECTIVE_BALANCE_INCREMENT + state.exit_balance_to_consume -= existing_churn + + # Set validator effective balance to a multiple of churn_limit + epochs_to_consume = 3 + state.validators[validator_index].effective_balance = epochs_to_consume * churn_limit + + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + signed_voluntary_exit = sign_voluntary_exit( + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + # Validator fully consumes epochs_to_consume and gets into the next one + expected_exit_epoch = earliest_exit_epoch + epochs_to_consume + expected_withdrawable_epoch = ( + expected_exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + assert state.validators[validator_index].exit_epoch == expected_exit_epoch + assert state.validators[validator_index].withdrawable_epoch == expected_withdrawable_epoch + # Check exit_balance_to_consume + assert state.exit_balance_to_consume == churn_limit - existing_churn + # Check earliest_exit_epoch + assert state.earliest_exit_epoch == expected_exit_epoch + + +@with_electra_and_later +@spec_state_test +def test_voluntary_exit_with_pending_deposit(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator = state.validators[validator_index] + privkey = pubkey_to_privkey[validator.pubkey] + + voluntary_exit = spec.VoluntaryExit( + epoch=current_epoch, + validator_index=validator_index, + ) + signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey) + + # A pending deposit will not prevent an exit + state.pending_deposits = [ + spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + ) + ] + + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) + + +@with_electra_and_later +@spec_state_test +def test_invalid_validator_has_pending_withdrawal(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] + + voluntary_exit = spec.VoluntaryExit( + epoch=current_epoch, + validator_index=validator_index, + ) + signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey) + + state.pending_partial_withdrawals.append( + spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=1, + withdrawable_epoch=spec.compute_activation_exit_epoch(current_epoch), + ) + ) + + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) 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 new file mode 100644 index 0000000000..c2b17db9ad --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawal_request.py @@ -0,0 +1,897 @@ +import random + +from eth2spec.test.context import ( + expect_assertion_error, + spec_state_test, + with_electra_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.state import ( + get_validator_index_by_pubkey, +) +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential, + set_eth1_withdrawal_credential_with_balance, +) + +# +# 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 + + +@with_electra_and_later +@spec_state_test +def test_basic_withdrawal_request(spec, state): + rng = random.Random(1337) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request) + + +@with_electra_and_later +@spec_state_test +def test_basic_withdrawal_request_with_first_validator(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = spec.get_active_validator_indices(state, current_epoch)[0] + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request) + + +@with_electra_and_later +@spec_state_test +def test_basic_withdrawal_request_with_compounding_credentials(spec, state): + rng = random.Random(1338) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need full partial withdrawal queue") +def test_basic_withdrawal_request_with_full_partial_withdrawal_queue(spec, state): + rng = random.Random(1339) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + # Fill the partial withdrawal queue to the max (with a different validator index) + partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=1, amount=1, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals = [ + partial_withdrawal + ] * spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT + + # Exit should still be processed + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + +# Tests that should fail + + +@with_electra_and_later +@spec_state_test +def test_incorrect_source_address(spec, state): + rng = random.Random(1340) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(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, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_incorrect_withdrawal_credential_prefix(spec, state): + rng = random.Random(1341) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + # Set incorrect prefix + state.validators[validator_index].withdrawal_credentials = ( + spec.BLS_WITHDRAWAL_PREFIX + state.validators[validator_index].withdrawal_credentials[1:] + ) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_on_withdrawal_request_initiated_exit_validator(spec, state): + rng = random.Random(1342) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + # Initiate exit earlier + spec.initiate_validator_exit(state, validator_index) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_activation_epoch_less_than_shard_committee_period(spec, state): + rng = random.Random(1343) + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + assert spec.get_current_epoch(state) < ( + state.validators[validator_index].activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_unknown_pubkey(spec, state): + rng = random.Random(1344) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + address = b"\x22" * 20 + pubkey = spec.BLSPubkey(b"\x23" * 48) + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +# Partial withdrawals tests + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_basic_partial_withdrawal_request(spec, state): + rng = random.Random(1344) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Set excess balance exactly to the requested amount + state.balances[validator_index] += amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_basic_partial_withdrawal_request_higher_excess_balance(spec, state): + rng = random.Random(1345) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Set excess balance higher than requested amount + state.balances[validator_index] += 2 * amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_basic_partial_withdrawal_request_lower_than_excess_balance(spec, state): + rng = random.Random(1346) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + excess_balance = spec.EFFECTIVE_BALANCE_INCREMENT + amount = 2 * excess_balance + # Set excess balance higher than requested amount + state.balances[validator_index] += excess_balance + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_partial_withdrawal_request_with_pending_withdrawals(spec, state): + rng = random.Random(1347) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + # Add pending withdrawals + partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, amount=amount, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals = [partial_withdrawal] * 2 + + # Set balance so that the validator still has excess balance even with the pending withdrawals + state.balances[validator_index] += 3 * amount + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_partial_withdrawal_request_with_pending_withdrawals_and_high_amount(spec, state): + rng = random.Random(1348) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.UINT64_MAX + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + # Add many pending withdrawals + partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, + withdrawable_epoch=current_epoch, + ) + state.pending_partial_withdrawals = [partial_withdrawal] * ( + spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT - 1 + ) + + # Set balance so that the validator still has excess balance even with the pending withdrawals + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_partial_withdrawal_request_with_high_balance(spec, state): + rng = random.Random(1349) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.balances[validator_index] = 3 * spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + churn_limit = spec.get_activation_exit_churn_limit(state) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + exit_epoch = spec.compute_activation_exit_epoch(current_epoch) + amount // churn_limit + assert state.earliest_exit_epoch == exit_epoch + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_partial_withdrawal_request_with_high_amount(spec, state): + rng = random.Random(1350) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + # Set high amount requested to withdraw + amount = spec.UINT64_MAX + # Give the validator some excess balance to withdraw + state.balances[validator_index] += 1 + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL]) +def test_partial_withdrawal_request_with_low_amount(spec, state): + rng = random.Random(1351) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = 1 + # Give the validator some excess balance to withdraw + state.balances[validator_index] += amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + ) + + # Check that the assigned exit epoch is correct + assert state.earliest_exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + + +# No-op partial withdrawal tests + + +@with_electra_and_later +@spec_state_test +@with_presets([MINIMAL], "need full partial withdrawal queue") +def test_partial_withdrawal_queue_full(spec, state): + rng = random.Random(1352) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Ensure that the validator has sufficient excess balance + state.balances[validator_index] += 2 * amount + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + # Fill the partial withdrawal queue to the max + partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=1, amount=1, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals = [ + partial_withdrawal + ] * spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_no_compounding_credentials(spec, state): + rng = random.Random(1353) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Ensure that the validator has sufficient excess balance + state.balances[validator_index] += 2 * amount + + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + success=False, + ) + + +@with_electra_and_later +@spec_state_test +def test_no_excess_balance(spec, state): + rng = random.Random(1354) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_consume_all_excess_balance(spec, state): + rng = random.Random(1355) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Add excess balance + state.balances[validator_index] += 10 * amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + # Add pending withdrawals totalling an amount equal to the excess balance + partial_withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, amount=amount, withdrawable_epoch=current_epoch + ) + state.pending_partial_withdrawals = [partial_withdrawal] * 10 + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_insufficient_effective_balance(spec, state): + rng = random.Random(1356) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + # Make effective balance insufficient + state.validators[validator_index].effective_balance -= spec.EFFECTIVE_BALANCE_INCREMENT + # Make sure validator has enough balance to withdraw + state.balances[validator_index] += spec.EFFECTIVE_BALANCE_INCREMENT + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + success=False, + ) + + +@with_electra_and_later +@spec_state_test +def test_partial_withdrawal_incorrect_source_address(spec, state): + rng = random.Random(1357) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(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 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] += 2 * amount + + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=incorrect_address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_partial_withdrawal_incorrect_withdrawal_credential_prefix(spec, state): + rng = random.Random(1358) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] += 2 * amount + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + # Set incorrect prefix + state.validators[validator_index].withdrawal_credentials = ( + spec.BLS_WITHDRAWAL_PREFIX + state.validators[validator_index].withdrawal_credentials[1:] + ) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_partial_withdrawal_on_exit_initiated_validator(spec, state): + rng = random.Random(1359) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] += 2 * amount + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + # Initiate exit earlier + spec.initiate_validator_exit(state, validator_index) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_partial_withdrawal_activation_epoch_less_than_shard_committee_period(spec, state): + rng = random.Random(1360) + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] += 2 * amount + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + assert spec.get_current_epoch(state) < ( + state.validators[validator_index].activation_epoch + spec.config.SHARD_COMMITTEE_PERIOD + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_insufficient_balance(spec, state): + rng = random.Random(1361) + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + # Validator will not be able to partial withdrawal because MIN_ACTIVATION_BALANCE + amount > balance + set_compounding_withdrawal_credential(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=amount, + ) + + yield from run_withdrawal_request_processing( + spec, + state, + withdrawal_request, + success=False, + ) + + +@with_electra_and_later +@spec_state_test +def test_full_exit_request_has_partial_withdrawal(spec, state): + rng = random.Random(1361) + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + # Validator can only be exited if there's no pending partial withdrawals in state + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.pending_partial_withdrawals.append( + spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=1, + withdrawable_epoch=spec.compute_activation_exit_epoch(current_epoch), + ) + ) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) + + +@with_electra_and_later +@spec_state_test +def test_incorrect_inactive_validator(spec, state): + rng = random.Random(1361) + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + current_epoch = spec.get_current_epoch(state) + validator_index = rng.choice(spec.get_active_validator_indices(state, current_epoch)) + validator_pubkey = state.validators[validator_index].pubkey + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.FULL_EXIT_REQUEST_AMOUNT, + ) + + # set validator as not yet activated + state.validators[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH + assert not spec.is_active_validator(state.validators[validator_index], current_epoch) + + yield from run_withdrawal_request_processing(spec, state, withdrawal_request, success=False) 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 new file mode 100644 index 0000000000..5d6f3190dd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py @@ -0,0 +1,826 @@ +import random + +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import ( + next_slot, +) +from eth2spec.test.helpers.withdrawals import ( + prepare_expected_withdrawals, + prepare_pending_withdrawal, + run_withdrawals_processing, + set_compounding_withdrawal_credential_with_balance, +) + + +@with_electra_and_later +@spec_state_test +def test_success_mixed_fully_and_partial_withdrawable_compounding(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 2 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD - num_full_withdrawals + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals_comp=num_full_withdrawals, + num_partial_withdrawals_comp=num_partial_withdrawals, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + ) + + +@with_electra_and_later +@spec_state_test +def test_success_no_max_effective_balance_compounding(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator's effective balance must be maxed out + effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - spec.EFFECTIVE_BALANCE_INCREMENT + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, effective_balance + ) + + validator = state.validators[validator_index] + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_electra_and_later +@spec_state_test +def test_success_no_excess_balance_compounding(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator needs an excess balance + effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, effective_balance + ) + + validator = state.validators[validator_index] + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_electra_and_later +@spec_state_test +def test_success_excess_balance_but_no_max_effective_balance_compounding(spec, state): + validator_index = len(state.validators) // 2 + # To be partially withdrawable, the validator needs both a maxed out effective balance and an excess balance + effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - spec.EFFECTIVE_BALANCE_INCREMENT + balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, effective_balance, balance + ) + + validator = state.validators[validator_index] + assert not spec.is_partially_withdrawable_validator(validator, state.balances[validator_index]) + + execution_payload = build_empty_execution_payload(spec, state) + + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_one_skipped_one_effective(spec, state): + validator_index_0 = 3 + validator_index_1 = 5 + + 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[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] + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=1, + pending_withdrawal_requests=[pending_withdrawal_1], + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_next_epoch(spec, state): + validator_index = len(state.validators) // 2 + next_epoch = spec.get_current_epoch(state) + 1 + + pending_withdrawal = prepare_pending_withdrawal( + spec, state, validator_index, withdrawable_epoch=next_epoch + ) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + assert state.pending_partial_withdrawals == [pending_withdrawal] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_at_max(spec, state): + pending_withdrawal_requests = [] + # Create spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 partial withdrawals + for i in range(0, spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1): + pending_withdrawal = prepare_pending_withdrawal(spec, state, i) + pending_withdrawal_requests.append(pending_withdrawal) + + assert ( + len(state.pending_partial_withdrawals) + == spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + ) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + pending_withdrawal_requests=pending_withdrawal_requests[ + : spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + ], + ) + + withdrawals_exceeding_max = pending_withdrawal_requests[ + spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP : + ] + assert state.pending_partial_withdrawals == withdrawals_exceeding_max + + +@with_electra_and_later +@spec_state_test +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.validator_index) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_full_pending_withdrawals_but_first_skipped_exiting_validator(spec, state): + # Fill the pending withdrawals, plus one which will be skipped + for index in range(spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1): + # Note, this adds the pending withdrawal to the state + prepare_pending_withdrawal(spec, state, index) + + # Ensure that there's one more than the limit, the first will be skipped + assert ( + len(state.pending_partial_withdrawals) + == spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + ) + + # For the first pending withdrawal, set the validator as exiting + spec.initiate_validator_exit(state, 0) + + yield from run_withdrawals_processing( + spec, + state, + build_empty_execution_payload(spec, state), + # We expect the first pending withdrawal to be skipped + num_expected_withdrawals=spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + ) + + # Ensure all pending withdrawals were processed and the first one was skipped + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +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.validator_index].effective_balance = ( + spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + ) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, execution_payload, num_expected_withdrawals=0 + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_full_pending_withdrawals_but_first_skipped_low_effective_balance(spec, state): + # Fill the pending withdrawals, plus one which will be skipped + for index in range(spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1): + # Note, this adds the pending withdrawal to the state + prepare_pending_withdrawal(spec, state, index) + + # Ensure that there's one more than the limit, the first will be skipped + assert ( + len(state.pending_partial_withdrawals) + == spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + ) + + # For the first pending withdrawal, set the validator to insufficient effective balance + state.validators[0].effective_balance = ( + spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + ) + + yield from run_withdrawals_processing( + spec, + state, + build_empty_execution_payload(spec, state), + # We expect the first pending withdrawal to be skipped + num_expected_withdrawals=spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + ) + + # Ensure all pending withdrawals were processed and the first one was skipped + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +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.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 + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_full_pending_withdrawals_but_first_skipped_no_excess_balance(spec, state): + # Fill the pending withdrawals, plus one which will be skipped + for index in range(spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1): + # Note, this adds the pending withdrawal to the state + prepare_pending_withdrawal(spec, state, index) + + # Ensure that there's one more than the limit, the first will be skipped + assert ( + len(state.pending_partial_withdrawals) + == spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + ) + + # For the first pending withdrawal, set the validator to have no excess balance + state.balances[0] = spec.MIN_ACTIVATION_BALANCE + + yield from run_withdrawals_processing( + spec, + state, + build_empty_execution_payload(spec, state), + # We expect the first pending withdrawal to be skipped + num_expected_withdrawals=spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + ) + + # Ensure all pending withdrawals were processed and the first one was skipped + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_ineffective_sweep_on_top(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal = prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + ) + + # Check that validator is partially withdrawable before pending withdrawal is processed + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + # And is not partially withdrawable thereafter + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal.amount, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=1, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + pending_withdrawal_requests=[pending_withdrawal], + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_ineffective_sweep_on_top_2(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT // 2, + ) + + pending_withdrawal_1 = prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, + ) + + # Set excess balance in a way that validator + # becomes not partially withdrawable only after the second pending withdrawal is processed + state.balances[validator_index] = ( + spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount - pending_withdrawal_1.amount, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=2, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + pending_withdrawal_requests=[pending_withdrawal_0, pending_withdrawal_1], + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_effective_sweep_on_top(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT // 2, + ) + + pending_withdrawal_1 = prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, + ) + + # Set excess balance to requested amount times three, + # so the validator is partially withdrawable after pending withdrawal is processed + state.balances[validator_index] = ( + spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT * 2 + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount - pending_withdrawal_1.amount, + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=3, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index], + pending_withdrawal_requests=[pending_withdrawal_0, pending_withdrawal_1], + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_sweep_different_validator(spec, state): + # Ensure validator will be processed by the sweep + validator_index_0 = ( + min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 - 1 + ) + validator_index_1 = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + # Initiate pending withdrawal for the first validator + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, + state, + validator_index_0, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT, + ) + + # Make the second validator partially withdrawable by the sweep + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index_1, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + balance=(spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT), + ) + + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index_1], state.balances[validator_index_1] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=2, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index_1], + pending_withdrawal_requests=[pending_withdrawal_0], + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_mixed_with_sweep_and_fully_withdrawable(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_full_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_pending_withdrawal_requests = spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP // 2 + + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=num_full_withdrawals, + num_partial_withdrawals=num_partial_withdrawals, + num_full_withdrawals_comp=num_full_withdrawals_comp, + num_partial_withdrawals_comp=num_partial_withdrawals_comp, + ) + + pending_withdrawal_requests = [] + for index in range(0, len(state.validators)): + if len(pending_withdrawal_requests) >= num_pending_withdrawal_requests: + break + if index in (fully_withdrawable_indices + partial_withdrawals_indices): + continue + + pending_withdrawal = prepare_pending_withdrawal(spec, state, index) + pending_withdrawal_requests.append(pending_withdrawal) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + pending_withdrawal_requests=pending_withdrawal_requests, + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_at_max_mixed_with_sweep_and_fully_withdrawable(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_full_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_pending_withdrawal_requests = spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, + state, + rng=random.Random(42), + num_full_withdrawals=num_full_withdrawals, + num_partial_withdrawals=num_partial_withdrawals, + num_full_withdrawals_comp=num_full_withdrawals_comp, + num_partial_withdrawals_comp=num_partial_withdrawals_comp, + ) + + pending_withdrawal_requests = [] + for index in range(0, len(state.validators)): + if len(pending_withdrawal_requests) >= num_pending_withdrawal_requests: + break + if index in (fully_withdrawable_indices + partial_withdrawals_indices): + continue + + pending_withdrawal = prepare_pending_withdrawal(spec, state, index) + pending_withdrawal_requests.append(pending_withdrawal) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + pending_withdrawal_requests=pending_withdrawal_requests[ + : spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + ], + ) + + withdrawals_exceeding_max = pending_withdrawal_requests[ + spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP : + ] + assert state.pending_partial_withdrawals == withdrawals_exceeding_max + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_max_plus_one(spec, state): + """Test compounding validator with balance just above MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA + 1 + ) + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_exact_max(spec, state): + """Test compounding validator with balance exactly equal to MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance(spec, state, validator_index) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_max_minus_one(spec, state): + """Test compounding validator whose balance is just below MAX_EFFECTIVE_BALANCE_ELECTRA""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA - spec.EFFECTIVE_BALANCE_INCREMENT, + balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA - 1, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_min_plus_one(spec, state): + """Test compounding validator just above MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE + 1, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_exact_min(spec, state): + """Test compounding validator with balance exactly equal to MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_partially_withdrawable_validator_compounding_min_minus_one(spec, state): + """Test compounding validator below MIN_ACTIVATION_BALANCE""" + validator_index = 0 + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT, + balance=spec.MIN_ACTIVATION_BALANCE - 1, + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], state.balances[validator_index] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, + state, + execution_payload, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + ) + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_two_partial_withdrawals_same_validator_1(spec, state): + validator_index = 0 + + # Initialize a compounding validator + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.Gwei(32_000_000_000), + balance=spec.Gwei(33_000_000_000), + ) + + # Add two pending withdrawals of 1 ETH each to the queue + withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=spec.Gwei(1_000_000_000), + withdrawable_epoch=spec.get_current_epoch(state), + ) + state.pending_partial_withdrawals = [withdrawal, withdrawal] + + # Ensure our two pending withdrawals are there + assert len(state.pending_partial_withdrawals) == 2 + + yield from run_withdrawals_processing( + spec, + state, + build_empty_execution_payload(spec, state), + num_expected_withdrawals=1, + ) + + # Ensure our two pending withdrawals were processed + assert state.pending_partial_withdrawals == [] + # Ensure the validator's balance is the minimum + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_two_partial_withdrawals_same_validator_2(spec, state): + validator_index = 0 + + # Initialize a compounding validator + set_compounding_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.Gwei(2015_000_000_000), + balance=spec.Gwei(2015_000_000_000), + ) + + # Add two pending withdrawals of 1008 ETH each to the queue + withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=spec.Gwei(1008_000_000_000), + withdrawable_epoch=spec.get_current_epoch(state), + ) + state.pending_partial_withdrawals = [withdrawal, withdrawal] + + # Ensure our two pending withdrawals are there + assert len(state.pending_partial_withdrawals) == 2 + + yield from run_withdrawals_processing( + spec, + state, + build_empty_execution_payload(spec, state), + num_expected_withdrawals=2, + ) + + # Ensure our two pending withdrawals were processed + assert state.pending_partial_withdrawals == [] + # Ensure the validator's balance is the minimum + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py new file mode 100644 index 0000000000..de7612a767 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/__init__.py @@ -0,0 +1,3 @@ +# This is a trick to allow tests be split into multiple files and use the same test format. +from .test_apply_pending_deposit import * # noqa: F401 F403 +from .test_process_pending_deposits import * # noqa: F401 F403 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 new file mode 100644 index 0000000000..de65e44140 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_apply_pending_deposit.py @@ -0,0 +1,518 @@ +from eth2spec.test.context import ( + always_bls, + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.deposits import ( + prepare_pending_deposit, + run_pending_deposit_applying, +) +from eth2spec.test.helpers.state import next_epoch_via_block +from eth2spec.test.helpers.withdrawals import set_validator_fully_withdrawable + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_under_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement + amount = spec.MIN_ACTIVATION_BALANCE - 1 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # effective balance will be exactly the same as balance. + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_over_min_activation(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # just 1 over the limit, effective balance should be set MIN_ACTIVATION_BALANCE during processing + amount = spec.MIN_ACTIVATION_BALANCE + 1 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_over_min_activation_next_increment(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + # set deposit amount to the next effective balance increment over the limit + # the validator's effective balance should be set to pre-electra MAX_EFFECTIVE_BALANCE + amount = spec.MAX_EFFECTIVE_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + # check validator's effective balance + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_eth1_withdrawal_credentials(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_under_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + # effective balance will be 1 EFFECTIVE_BALANCE_INCREMENT smaller because of this small decrement + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA - 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + # effective balance will be exactly the same as balance + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_over_max(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + # just 1 over the limit, effective balance should be set MAX_EFFECTIVE_BALANCE_ELECTRA during processing + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_compounding_withdrawal_credentials_over_max_next_increment( + spec, state +): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + # set deposit amount to the next effective balance increment over the limit + # the validator's effective balance should be set to MAX_EFFECTIVE_BALANCE_ELECTRA + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + # check validator's effective balance + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_non_versioned_withdrawal_credentials(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + b"\xff" + b"\x02" * 31 # Non specified withdrawal credentials version # Garabage bytes + ) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_non_versioned_withdrawal_credentials_over_min_activation( + spec, state +): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + withdrawal_credentials = ( + b"\xff" + b"\x02" * 31 # Non specified withdrawal credentials version # Garabage bytes + ) + # just 1 over the limit, effective balance should be set MIN_ACTIVATION_BALANCE during processing + amount = spec.MIN_ACTIVATION_BALANCE + 1 + pending_deposit = prepare_pending_deposit( + spec, + validator_index, + amount, + withdrawal_credentials=withdrawal_credentials, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_correct_sig_but_forked_state(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + # deposits will always be valid, regardless of the current fork + state.fork.current_version = spec.Version("0x1234abcd") + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_incorrect_sig_new_deposit(spec, state): + # fresh deposit = next validator index = validator appended to registry + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + pending_deposit = prepare_pending_deposit(spec, validator_index, amount) + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__min_activation_balance(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE + amount + assert state.validators[validator_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__min_activation_balance_compounding(spec, state): + validator_index = 0 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MIN_ACTIVATION_BALANCE + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials + state.validators[validator_index].effective_balance = spec.MIN_ACTIVATION_BALANCE + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MIN_ACTIVATION_BALANCE + amount + assert state.validators[validator_index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__max_effective_balance_compounding(spec, state): + validator_index = 0 + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address + ) + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.validators[validator_index].withdrawal_credentials = withdrawal_credentials + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + amount + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE_ELECTRA + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_top_up__less_effective_balance(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + initial_balance = spec.MIN_ACTIVATION_BALANCE - 1000 + initial_effective_balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] = initial_balance + state.validators[validator_index].effective_balance = initial_effective_balance + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == initial_balance + amount + # unchanged effective balance + assert state.validators[validator_index].effective_balance == initial_effective_balance + + +@with_electra_and_later +@spec_state_test +@always_bls +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=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) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_incorrect_withdrawal_credentials_top_up(spec, state): + validator_index = 0 + amount = spec.MIN_ACTIVATION_BALANCE // 4 + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(b"junk")[1:] + pending_deposit = prepare_pending_deposit( + spec, validator_index, amount, signed=True, withdrawal_credentials=withdrawal_credentials + ) + + # inconsistent withdrawal credentials, in top-ups, are allowed! + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_key_validate_invalid_subgroup(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + + # All-zero pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception + pubkey = b"\x00" * 48 + + pending_deposit = prepare_pending_deposit( + spec, validator_index, amount, pubkey=pubkey, signed=True + ) + + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_key_validate_invalid_decompression(spec, state): + validator_index = len(state.validators) + amount = spec.MIN_ACTIVATION_BALANCE + + # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case + # This pubkey would not pass `bls.KeyValidate`, but `apply_pending_deposit` would not throw exception + pubkey_hex = "c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + pubkey = bytes.fromhex(pubkey_hex) + + pending_deposit = prepare_pending_deposit( + spec, validator_index, amount, pubkey=pubkey, signed=True + ) + + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_ineffective_deposit_with_bad_fork_version(spec, state): + validator_index = len(state.validators) + fork_version = spec.Version("0xAaBbCcDd") + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True, + ) + + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_with_previous_fork_version(spec, state): + # Since deposits are valid across forks, the domain is always set with `GENESIS_FORK_VERSION` + # It's an ineffective deposit because it fails at BLS sig verification + # NOTE: it was effective in Altair + assert state.fork.previous_version != state.fork.current_version + + validator_index = len(state.validators) + fork_version = state.fork.previous_version + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True, + ) + + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_ineffective_deposit_with_current_fork_version(spec, state): + validator_index = len(state.validators) + fork_version = state.fork.current_version + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True, + ) + + yield from run_pending_deposit_applying( + spec, state, pending_deposit, validator_index, effective=False + ) + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_apply_pending_deposit_effective_deposit_with_genesis_fork_version(spec, state): + assert spec.config.GENESIS_FORK_VERSION not in ( + state.fork.previous_version, + state.fork.current_version, + ) + + validator_index = len(state.validators) + fork_version = spec.config.GENESIS_FORK_VERSION + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + fork_version=fork_version, + signed=True, + ) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + +@with_electra_and_later +@spec_state_test +def test_apply_pending_deposit_success_top_up_to_withdrawn_validator(spec, state): + validator_index = 0 + + # Fully withdraw validator + set_validator_fully_withdrawable(spec, state, validator_index) + assert state.balances[validator_index] > 0 + next_epoch_via_block(spec, state) + assert state.balances[validator_index] == 0 + assert state.validators[validator_index].effective_balance > 0 + next_epoch_via_block(spec, state) + assert state.validators[validator_index].effective_balance == 0 + + # Make a top-up balance to validator + amount = spec.MIN_ACTIVATION_BALANCE // 4 + pending_deposit = prepare_pending_deposit(spec, validator_index, amount, signed=True) + + yield from run_pending_deposit_applying(spec, state, pending_deposit, validator_index) + + assert state.balances[validator_index] == amount + assert state.validators[validator_index].effective_balance == 0 + + validator = state.validators[validator_index] + balance = state.balances[validator_index] + current_epoch = spec.get_current_epoch(state) + + assert spec.is_fully_withdrawable_validator(validator, balance, current_epoch) diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py new file mode 100644 index 0000000000..57f713fd31 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/pending_deposits/test_process_pending_deposits.py @@ -0,0 +1,543 @@ +from eth2spec.test.context import ( + always_bls, + default_activation_threshold, + scaled_churn_balances_exceed_activation_exit_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_custom_state, + with_electra_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.deposits import prepare_pending_deposit +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.state import ( + advance_finality_to, + next_epoch_with_full_participation, + set_full_participation, +) + + +def run_process_pending_deposits(spec, state): + yield from run_epoch_processing_with(spec, state, "process_pending_deposits") + + +def _ensure_enough_churn_to_process_deposits(spec, state): + state.deposit_balance_to_consume = sum(d.amount for d in state.pending_deposits) + + +def _prepare_eth1_bridge_deprecation(spec, state, eth1_bridge_flags): + new_pending_deposits = [] + validator_index_base = len(state.validators) + deposit_request_slot = spec.Slot(1) + for index, eth1_bridge in enumerate(eth1_bridge_flags): + validator_index = validator_index_base + index + slot = spec.GENESIS_SLOT if eth1_bridge else deposit_request_slot + pending_deposit = prepare_pending_deposit( + spec, + validator_index=validator_index, + amount=spec.MIN_ACTIVATION_BALANCE, + signed=True, + slot=slot, + ) + new_pending_deposits.append(pending_deposit) + + # Eth1 bridge deposits instantly yield new validator records + if eth1_bridge: + spec.add_validator_to_registry( + state, pending_deposit.pubkey, pending_deposit.withdrawal_credentials, spec.Gwei(0) + ) + state.eth1_deposit_index += 1 + + # Advance state to make pending deposits finalized + advance_finality_to(spec, state, spec.compute_epoch_at_slot(deposit_request_slot) + 1) + + # Add pending deposits + for pending_deposit in new_pending_deposits: + state.pending_deposits.append(pending_deposit) + + # Ensure there is enough churn to process them all + _ensure_enough_churn_to_process_deposits(spec, state) + + return state, new_pending_deposits + + +def _check_pending_deposits_induced_new_validators( + spec, state, pre_validator_count, applied_pending_deposits +): + assert pre_validator_count + len(applied_pending_deposits) == len(state.validators) + + eth1_bridge_deposits = [d for d in applied_pending_deposits if d.slot == spec.GENESIS_SLOT] + deposit_requests = [d for d in applied_pending_deposits if d.slot > spec.GENESIS_SLOT] + + # Eth1 bridge deposits should induce new validators in the first place + for index, deposit in enumerate(eth1_bridge_deposits): + validator_index = pre_validator_count + index + validator = state.validators[validator_index] + assert state.balances[validator_index] == deposit.amount + assert validator.pubkey == deposit.pubkey + # Effective balance is updated after pending deposits by process_effective_balance_updates + assert validator.effective_balance == spec.Gwei(0) + assert validator.withdrawal_credentials == deposit.withdrawal_credentials + + # then deposit requests go + for index, deposit in enumerate(deposit_requests): + validator_index = pre_validator_count + len(eth1_bridge_deposits) + index + validator = state.validators[validator_index] + assert state.balances[validator_index] == deposit.amount + assert validator.pubkey == deposit.pubkey + assert validator.withdrawal_credentials == deposit.withdrawal_credentials + # Validators induced from deposit requests get instant update of the EB + assert validator.effective_balance == deposit.amount + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_pending(spec, state): + # There are pending Eth1 bridge deposits + # state.eth1_deposit_index < state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 3 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [True, True, False]) + assert state.eth1_deposit_index < state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # Eth1 bridge deposits were applied + _check_pending_deposits_induced_new_validators( + spec, state, pre_validator_count, new_pending_deposits[:2] + ) + # deposit request was postponed and not processed + assert state.pending_deposits == new_pending_deposits[2:] + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_not_applied(spec, state): + # There are pending Eth1 bridge deposits + # state.eth1_deposit_index < state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 3 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [False, True, True]) + assert state.eth1_deposit_index < state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # no pending deposit was processed, however Eth1 bridge deposits induced new validators + assert pre_validator_count + 2 == len(state.validators) + assert state.pending_deposits == new_pending_deposits + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_eth1_bridge_transition_complete(spec, state): + # There is no pending Eth1 bridge deposits + # state.eth1_deposit_index == state.deposit_requests_start_index + pre_validator_count = len(state.validators) + state.eth1_data.deposit_count = len(state.validators) + 2 + state.deposit_requests_start_index = state.eth1_data.deposit_count + + state, new_pending_deposits = _prepare_eth1_bridge_deprecation(spec, state, [True, False, True]) + assert state.eth1_deposit_index == state.deposit_requests_start_index + + yield from run_process_pending_deposits(spec, state) + + # all deposits were applied + assert state.pending_deposits == [] + _check_pending_deposits_induced_new_validators( + spec, state, pre_validator_count, new_pending_deposits + ) + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_not_finalized(spec, state): + # complete eth1 bridge transition + state.deposit_requests_start_index = 0 + # advance state three epochs into the future + for _ in range(0, 3): + next_epoch_with_full_participation(spec, state) + # create pending deposits + pre_validator_count = len(state.validators) + for index in range(0, 2): + state.pending_deposits.append( + prepare_pending_deposit( + spec, + validator_index=pre_validator_count + index, + amount=spec.MIN_ACTIVATION_BALANCE, + signed=True, + slot=state.slot + index, + ) + ) + new_pending_deposits = state.pending_deposits.copy() + + # finalize a slot before the slot of the first deposit + advance_finality_to(spec, state, spec.get_current_epoch(state) - 1) + + # process pending deposits + # the slot of the first deposit will be finalized before the call to process_pending_deposits + set_full_participation(spec, state) + _ensure_enough_churn_to_process_deposits(spec, state) + + yield from run_process_pending_deposits(spec, state) + + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + # second deposit was not processed as it hasn't been finalized + assert state.pending_deposits == new_pending_deposits[1:] + _check_pending_deposits_induced_new_validators( + spec, state, pre_validator_count, new_pending_deposits[:1] + ) + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_limit_is_reached(spec, state): + # set pending deposits to the maximum + amount = spec.EFFECTIVE_BALANCE_INCREMENT * 1 + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH + 2): + wc = state.validators[i].withdrawal_credentials + pd = prepare_pending_deposit(spec, i, amount, withdrawal_credentials=wc, signed=True) + state.pending_deposits.append(pd) + new_pending_deposits = state.pending_deposits.copy() + + # process pending deposits + pre_balances = state.balances.copy() + _ensure_enough_churn_to_process_deposits(spec, state) + + yield from run_process_pending_deposits(spec, state) + + # deposit_balance_to_consume was reset to 0 + assert state.deposit_balance_to_consume == 0 + # no deposits above limit were processed + assert state.pending_deposits == new_pending_deposits[spec.MAX_PENDING_DEPOSITS_PER_EPOCH :] + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH): + assert state.balances[i] == pre_balances[i] + amount + for i in range(spec.MAX_PENDING_DEPOSITS_PER_EPOCH, spec.MAX_PENDING_DEPOSITS_PER_EPOCH + 2): + assert state.balances[i] == pre_balances[i] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_balance_equal_churn(spec, state): + index = 0 + amount = spec.get_activation_exit_churn_limit(state) + state.pending_deposits.append(prepare_pending_deposit(spec, index, amount)) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + assert state.balances[index] == pre_balance + amount + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_balance_above_churn(spec, state): + index = 0 + amount = spec.get_activation_exit_churn_limit(state) + 1 + state.pending_deposits.append(prepare_pending_deposit(spec, index, amount)) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + # deposit was above churn, balance hasn't changed + assert state.balances[index] == pre_balance + # deposit balance to consume is the full churn limit + wantedBalanceToConsume = spec.get_activation_exit_churn_limit(state) + assert state.deposit_balance_to_consume == wantedBalanceToConsume + # deposit is still in the queue + assert state.pending_deposits == [prepare_pending_deposit(spec, index, amount)] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_preexisting_churn(spec, state): + index = 0 + amount = spec.EFFECTIVE_BALANCE_INCREMENT + 1 + state.deposit_balance_to_consume = 2 * amount + state.pending_deposits.append(prepare_pending_deposit(spec, index, amount)) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + # balance was deposited correctly + assert state.balances[index] == pre_balance + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + # queue emptied + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_deposits_below_churn(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index=0, amount=amount)) + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index=1, amount=amount)) + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + for i in [0, 1]: + assert state.balances[i] == pre_balances[i] + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_deposits_above_churn(spec, state): + # set third deposit to be over the churn + amount = (spec.get_activation_exit_churn_limit(state) // 3) + 1 + for i in [0, 1, 2]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + # First two deposits are processed, third is not because above churn + for i in [0, 1]: + assert state.balances[i] == pre_balances[i] + amount + assert state.balances[2] == pre_balances[2] + # Only first two subtract from the deposit balance to consume + assert ( + state.deposit_balance_to_consume == spec.get_activation_exit_churn_limit(state) - 2 * amount + ) + # third deposit is still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=2, amount=amount) + ] + + +@with_electra_and_later +@spec_state_test +@always_bls +def test_process_pending_deposits_multiple_for_new_validator(spec, state): + """ + - There are three pending deposits in the state, all pointing to the same public key. + - The public key does not exist in the beacon state. + - The first pending deposit has an invalid signature and should be ignored. + - The second pending deposit has a valid signature and the validator should be created. + - The third pending deposit has a valid signature and should be applied. + """ + # A new validator, pubkey doesn't exist in the state + validator_index = len(state.validators) + amount = spec.EFFECTIVE_BALANCE_INCREMENT + + # Add pending deposits to the state + # Provide different amounts so we can tell which were applied + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index, amount * 1, signed=False) + ) + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index, amount * 2, signed=True) + ) + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index, amount * 4, signed=True) + ) + + yield from run_process_pending_deposits(spec, state) + + # The second and third deposits were applied + assert state.balances[validator_index] == amount * 6 + # No more pending deposits + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_skipped_deposit_exiting_validator(spec, state): + index = 0 + amount = spec.MIN_ACTIVATION_BALANCE + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=index, amount=amount) + ) + pre_pending_deposits = state.pending_deposits.copy() + pre_balance = state.balances[index] + # Initiate the validator's exit + spec.initiate_validator_exit(state, index) + + yield from run_process_pending_deposits(spec, state) + + # Deposit is skipped because validator is exiting + assert state.balances[index] == pre_balance + # All deposits either processed or postponed + assert state.deposit_balance_to_consume == 0 + # The deposit is still in the queue + assert state.pending_deposits == pre_pending_deposits + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_skipped_deposits_exiting_validators(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + for i in [0, 1, 2]: + # Append pending deposit for validator i + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + + # Initiate the exit of validator i + spec.initiate_validator_exit(state, i) + pre_pending_deposits = state.pending_deposits.copy() + pre_balances = state.balances.copy() + + yield from run_process_pending_deposits(spec, state) + + # All deposits are postponed, no balance changes + assert state.balances == pre_balances + # All deposits are postponed, no leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + # All deposits still in the queue, in the same order + assert state.pending_deposits == pre_pending_deposits + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_multiple_pending_one_skipped(spec, state): + amount = spec.EFFECTIVE_BALANCE_INCREMENT + for i in [0, 1, 2]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + # Initiate the second validator's exit + spec.initiate_validator_exit(state, 1) + + yield from run_process_pending_deposits(spec, state) + + # First and last deposit are processed, second is not because of exiting + for i in [0, 2]: + assert state.balances[i] == pre_balances[i] + amount + assert state.balances[1] == pre_balances[1] + # All deposits either processed or postponed + assert state.deposit_balance_to_consume == 0 + # second deposit is still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=1, amount=amount) + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_mixture_of_skipped_and_above_churn(spec, state): + amount1 = spec.EFFECTIVE_BALANCE_INCREMENT + amount2 = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + # First two validators have small deposit, third validators a large one + for i in [0, 1]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount1) + ) + state.pending_deposits.append(prepare_pending_deposit(spec, validator_index=2, amount=amount2)) + pre_balances = state.balances.copy() + # Initiate the second validator's exit + spec.initiate_validator_exit(state, 1) + + yield from run_process_pending_deposits(spec, state) + + # First deposit is processed + assert state.balances[0] == pre_balances[0] + amount1 + # Second deposit is postponed, third is above churn + for i in [1, 2]: + assert state.balances[i] == pre_balances[i] + # First deposit consumes some deposit balance + # Deposit is not processed + wanted_balance = spec.get_activation_exit_churn_limit(state) - amount1 + assert state.deposit_balance_to_consume == wanted_balance + # second and third deposit still in the queue + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=2, amount=amount2), + prepare_pending_deposit(spec, validator_index=1, amount=amount1), + ] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_withdrawable_validator(spec, state): + index = 0 + amount = spec.MIN_ACTIVATION_BALANCE + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=index, amount=amount) + ) + pre_balance = state.balances[index] + # Initiate the validator's exit + spec.initiate_validator_exit(state, index) + # Set epoch to withdrawable epoch + 1 to allow processing of the deposit + withdrawable_epoch = state.validators[index].withdrawable_epoch + state.slot = spec.SLOTS_PER_EPOCH * (withdrawable_epoch + 1) + + yield from run_process_pending_deposits(spec, state) + + # Deposit is correctly processed + assert state.balances[index] == pre_balance + amount + # No leftover deposit balance to consume + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] + + +@with_electra_and_later +@spec_state_test +def test_process_pending_deposits_withdrawable_validator_not_churned(spec, state): + amount = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + for i in [0, 1]: + state.pending_deposits.append( + prepare_pending_deposit(spec, validator_index=i, amount=amount) + ) + pre_balances = state.balances.copy() + # Initiate the first validator's exit + spec.initiate_validator_exit(state, 0) + # Set epoch to withdrawable epoch + 1 to allow processing of the deposit + withdraw_epoch = state.validators[0].withdrawable_epoch + state.slot = spec.SLOTS_PER_EPOCH * (withdraw_epoch + 1) + # Don't use run_epoch_processing_with to avoid penalties being applied + yield "pre", state + spec.process_pending_deposits(state) + yield "post", state + # First deposit is processed though above churn limit + assert state.balances[0] == pre_balances[0] + amount + # Second deposit is not processed because above churn + assert state.balances[1] == pre_balances[1] + # Second deposit is not processed + # First deposit does not consume any. + wanted_limit = spec.get_activation_exit_churn_limit(state) + assert state.deposit_balance_to_consume == wanted_limit + assert state.pending_deposits == [ + prepare_pending_deposit(spec, validator_index=1, amount=amount) + ] + + +@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_process_pending_deposits_scaled_churn(spec, state): + index = 0 + amount = spec.get_activation_exit_churn_limit(state) + state.pending_deposits.append(prepare_pending_deposit(spec, index, amount)) + pre_balance = state.balances[index] + + yield from run_process_pending_deposits(spec, state) + + assert state.balances[index] == pre_balance + amount + assert state.deposit_balance_to_consume == 0 + assert state.pending_deposits == [] diff --git a/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_effective_balance_updates.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_effective_balance_updates.py new file mode 100644 index 0000000000..1fef76fb09 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_effective_balance_updates.py @@ -0,0 +1,10 @@ +from eth2spec.test.context import spec_state_test, with_electra_and_later +from eth2spec.test.phase0.epoch_processing.test_process_effective_balance_updates import ( + run_test_effective_balance_hysteresis, +) + + +@with_electra_and_later +@spec_state_test +def test_effective_balance_hysteresis_with_compounding_credentials(spec, state): + yield from run_test_effective_balance_hysteresis(spec, state, with_compounding_credentials=True) 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 new file mode 100644 index 0000000000..4ab49662bf --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_pending_consolidations.py @@ -0,0 +1,550 @@ +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.epoch_processing import ( + run_epoch_processing_to, + run_epoch_processing_with, +) +from eth2spec.test.helpers.state import ( + next_epoch_with_full_participation, +) +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential_with_balance, + set_eth1_withdrawal_credential_with_balance, +) + +# *********************** +# * CONSOLIDATION TESTS * +# *********************** + + +@with_electra_and_later +@spec_state_test +def test_basic_pending_consolidation(spec, state): + 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] + # append pending consolidation + 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 the target withdrawal credential to eth1 + eth1_withdrawal_credential = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + state.validators[target_index].withdrawal_credentials = eth1_withdrawal_credential + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + assert state.balances[target_index] == 2 * spec.MIN_ACTIVATION_BALANCE + assert state.balances[source_index] == 0 + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_consolidation_not_yet_withdrawable_validator(spec, state): + 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] + # append pending consolidation + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source_index, target_index=target_index) + ) + # Set the target to eth1 withdrawal credentials + eth1_withdrawal_credential = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + state.validators[target_index].withdrawal_credentials = eth1_withdrawal_credential + # Initiate exit of source validator + spec.initiate_validator_exit(state, source_index) + + pre_pending_consolidations = state.pending_consolidations.copy() + pre_balances = state.balances.copy() + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation is not processed + # Balances are unchanged + assert state.balances[source_index] == pre_balances[0] + assert state.balances[target_index] == pre_balances[1] + # Pending consolidation is still in the queue + assert state.pending_consolidations == pre_pending_consolidations + + +@with_electra_and_later +@spec_state_test +def test_skip_consolidation_when_source_slashed(spec, state): + current_epoch = spec.get_current_epoch(state) + source0_index = spec.get_active_validator_indices(state, current_epoch)[0] + target0_index = spec.get_active_validator_indices(state, current_epoch)[1] + source1_index = spec.get_active_validator_indices(state, current_epoch)[2] + target1_index = spec.get_active_validator_indices(state, current_epoch)[3] + # append pending consolidation + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source0_index, target_index=target0_index) + ) + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source1_index, target_index=target1_index) + ) + + eth1_withdrawal_credential = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + state.validators[target0_index].withdrawal_credentials = eth1_withdrawal_credential + state.validators[target1_index].withdrawal_credentials = eth1_withdrawal_credential + + # Set withdrawable epoch of sources to current epoch to allow processing + state.validators[source0_index].withdrawable_epoch = spec.get_current_epoch(state) + state.validators[source1_index].withdrawable_epoch = spec.get_current_epoch(state) + + # set first source as slashed + state.validators[source0_index].slashed = True + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # first pending consolidation should not be processed + assert state.balances[target0_index] == spec.MIN_ACTIVATION_BALANCE + assert state.balances[source0_index] == spec.MIN_ACTIVATION_BALANCE + # second pending consolidation should be processed: first one is skipped and doesn't block the queue + assert state.balances[target1_index] == 2 * spec.MIN_ACTIVATION_BALANCE + assert state.balances[source1_index] == 0 + + +@with_electra_and_later +@spec_state_test +def test_all_consolidation_cases_together(spec, state): + current_epoch = spec.get_current_epoch(state) + source_index = [spec.get_active_validator_indices(state, current_epoch)[i] for i in range(4)] + target_index = [ + spec.get_active_validator_indices(state, current_epoch)[4 + i] for i in range(4) + ] + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=source_index[i], target_index=target_index[i]) + for i in range(4) + ] + # Set withdrawable epoch to current epoch for first and last source validators + for i in [0, 2]: + state.validators[source_index[i]].withdrawable_epoch = current_epoch + # Set second source validator as slashed + state.validators[source_index[1]].slashed = True + # Set targets withdrawal credentials to eth1 + eth1_withdrawal_credential = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + for i in range(4): + state.validators[target_index[i]].withdrawal_credentials = eth1_withdrawal_credential + # Initiate exit of third source validator + spec.initiate_validator_exit(state, 2) + + pre_balances = state.balances.copy() + pre_pending_consolidations = state.pending_consolidations.copy() + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # First consolidation is successfully processed + assert state.balances[target_index[0]] == 2 * spec.MIN_ACTIVATION_BALANCE + assert state.balances[source_index[0]] == 0 + # All other consolidations are not processed + for i in [1, 2, 3]: + assert state.balances[source_index[i]] == pre_balances[source_index[i]] + assert state.balances[target_index[i]] == pre_balances[target_index[i]] + # First consolidation is processed, second is skipped, last two are left in the queue + state.pending_consolidations = pre_pending_consolidations[2:] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_future_epoch(spec, state): + 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] + # initiate source exit + spec.initiate_validator_exit(state, source_index) + # set withdrawable_epoch to exit_epoch + 1 + state.validators[source_index].withdrawable_epoch = state.validators[ + source_index + ].exit_epoch + spec.Epoch(1) + # append pending consolidation + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source_index, target_index=target_index) + ) + # Set the target withdrawal credential to eth1 + eth1_withdrawal_credential = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + state.validators[target_index].withdrawal_credentials = eth1_withdrawal_credential + + # Advance to withdrawable_epoch - 1 with full participation + target_epoch = state.validators[source_index].withdrawable_epoch - spec.Epoch(1) + while spec.get_current_epoch(state) < target_epoch: + next_epoch_with_full_participation(spec, state) + + # Obtain state before the call to process_pending_consolidations + state_before_consolidation = state.copy() + run_epoch_processing_to(spec, state_before_consolidation, "process_pending_consolidations") + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + expected_source_balance = ( + state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE + ) + expected_target_balance = ( + state_before_consolidation.balances[target_index] + spec.MIN_ACTIVATION_BALANCE + ) + assert state.balances[source_index] == expected_source_balance + assert state.balances[target_index] == expected_target_balance + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_compounding_creds(spec, state): + 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] + # initiate source exit + spec.initiate_validator_exit(state, source_index) + # set withdrawable_epoch to exit_epoch + 1 + state.validators[source_index].withdrawable_epoch = state.validators[ + source_index + ].exit_epoch + spec.Epoch(1) + # append pending consolidation + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source_index, target_index=target_index) + ) + # Set the source and the target withdrawal credential to compounding + state.validators[source_index].withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + ) + state.validators[target_index].withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x12" * 20 + ) + + # Advance to withdrawable_epoch - 1 with full participation + target_epoch = state.validators[source_index].withdrawable_epoch - spec.Epoch(1) + while spec.get_current_epoch(state) < target_epoch: + next_epoch_with_full_participation(spec, state) + + # Obtain state before the call to process_pending_consolidations + state_before_consolidation = state.copy() + run_epoch_processing_to(spec, state_before_consolidation, "process_pending_consolidations") + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + expected_target_balance = ( + 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] == ( + state_before_consolidation.balances[source_index] - spec.MIN_ACTIVATION_BALANCE + ) + assert state.pending_consolidations == [] + + # Pending balance deposit to the target is not created, + # because the target already has compounding credentials + assert len(state.pending_deposits) == 0 + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_with_pending_deposit(spec, state): + 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] + # initiate source exit + spec.initiate_validator_exit(state, source_index) + # set withdrawable_epoch to exit_epoch + 1 + source = state.validators[source_index] + source.withdrawable_epoch = source.exit_epoch + spec.Epoch(1) + # append pending consolidation + state.pending_consolidations.append( + spec.PendingConsolidation(source_index=source_index, target_index=target_index) + ) + # append pending deposit + pending_deposit = spec.PendingDeposit( + pubkey=source.pubkey, + withdrawal_credentials=source.withdrawal_credentials, + amount=spec.MIN_ACTIVATION_BALANCE, + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + ) + state.pending_deposits.append(pending_deposit) + # Set the source and the target withdrawal credential to compounding + state.validators[source_index].withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20 + ) + state.validators[target_index].withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x12" * 20 + ) + + # Advance to withdrawable_epoch - 1 with full participation + target_epoch = source.withdrawable_epoch - spec.Epoch(1) + while spec.get_current_epoch(state) < target_epoch: + next_epoch_with_full_participation(spec, state) + + # Obtain state before the call to process_pending_balance_deposits + state_before_consolidation = state.copy() + run_epoch_processing_to(spec, state_before_consolidation, "process_pending_consolidations") + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + expected_target_balance = ( + spec.MIN_ACTIVATION_BALANCE + state_before_consolidation.balances[target_index] + ) + assert state.balances[target_index] == expected_target_balance + 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. + # It should only be processed in the next epoch transition + assert state.pending_deposits == [pending_deposit] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_source_balance_less_than_max_effective(spec, state): + 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] + # append pending consolidation + 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 credential to eth1 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set the source balance to be less than effective_balance + pre_balance_source = ( + state.validators[source_index].effective_balance - spec.EFFECTIVE_BALANCE_INCREMENT // 8 + ) + state.balances[source_index] = pre_balance_source + + pre_balance_target = state.balances[target_index] + + assert state.balances[source_index] < spec.get_max_effective_balance( + state.validators[source_index] + ) + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + assert state.balances[target_index] == pre_balance_target + pre_balance_source + assert state.balances[source_index] == 0 + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_source_balance_greater_than_max_effective(spec, state): + 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] + # append pending consolidation + 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 credential to eth1 + set_eth1_withdrawal_credential_with_balance(spec, state, source_index) + set_eth1_withdrawal_credential_with_balance(spec, state, target_index) + # Set the source balance to be greater than effective_balance + excess_source_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 8 + pre_balance_source = state.validators[source_index].effective_balance + excess_source_balance + state.balances[source_index] = pre_balance_source + + pre_balance_target = state.balances[target_index] + + source_max_effective_balance = spec.get_max_effective_balance(state.validators[source_index]) + assert state.balances[source_index] > source_max_effective_balance + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + assert state.balances[target_index] == pre_balance_target + source_max_effective_balance + assert state.balances[source_index] == excess_source_balance + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_source_balance_less_than_max_effective_compounding(spec, state): + 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] + # append pending consolidation + 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 credential to compounding + set_compounding_withdrawal_credential_with_balance(spec, state, source_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + # Set the source balance to be less than effective_balance + pre_balance_source = ( + state.validators[source_index].effective_balance - spec.EFFECTIVE_BALANCE_INCREMENT // 8 + ) + state.balances[source_index] = pre_balance_source + + pre_balance_target = state.balances[target_index] + + assert state.balances[source_index] < spec.get_max_effective_balance( + state.validators[source_index] + ) + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + assert state.balances[target_index] == pre_balance_target + pre_balance_source + assert state.balances[source_index] == 0 + assert state.pending_consolidations == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_consolidation_source_balance_greater_than_max_effective_compounding(spec, state): + 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] + # append pending consolidation + 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 credential to compounding + set_compounding_withdrawal_credential_with_balance(spec, state, source_index) + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + # Set the source balance to be greater than effective_balance + excess_source_balance = spec.EFFECTIVE_BALANCE_INCREMENT // 8 + pre_balance_source = state.validators[source_index].effective_balance + excess_source_balance + state.balances[source_index] = pre_balance_source + + pre_balance_target = state.balances[target_index] + + source_max_effective_balance = spec.get_max_effective_balance(state.validators[source_index]) + assert state.balances[source_index] > source_max_effective_balance + + yield from run_epoch_processing_with(spec, state, "process_pending_consolidations") + + # Pending consolidation was successfully processed + 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/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_registry_updates.py new file mode 100644 index 0000000000..72c82c5a26 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/epoch_processing/test_process_registry_updates.py @@ -0,0 +1,76 @@ +from eth2spec.test.context import spec_state_test, with_electra_and_later +from eth2spec.test.helpers.deposits import mock_deposit +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.state import next_epoch +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential_with_balance, + set_eth1_withdrawal_credential_with_balance, +) + + +def run_test_activation_queue_eligibility(spec, state, validator_index, balance): + # move past first two irregular epochs wrt finality + next_epoch(spec, state) + next_epoch(spec, state) + + state.balances[validator_index] = balance + state.validators[validator_index].effective_balance = ( + balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT + ) + + # ready for entrance into activation queue + mock_deposit(spec, state, validator_index) + + yield from run_epoch_processing_with(spec, state, "process_registry_updates") + + # validator moved into activation queue if eligible + validator = state.validators[validator_index] + if validator.effective_balance <= ( + spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + ): + assert validator.activation_eligibility_epoch == spec.FAR_FUTURE_EPOCH + else: + assert validator.activation_eligibility_epoch < spec.FAR_FUTURE_EPOCH + + +@with_electra_and_later +@spec_state_test +def test_activation_queue_eligibility__less_than_min_activation_balance(spec, state): + index = 3 + balance = spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + yield from run_test_activation_queue_eligibility(spec, state, index, balance) + + +@with_electra_and_later +@spec_state_test +def test_activation_queue_eligibility__min_activation_balance(spec, state): + index = 5 + balance = spec.MIN_ACTIVATION_BALANCE + yield from run_test_activation_queue_eligibility(spec, state, index, balance) + + +@with_electra_and_later +@spec_state_test +def test_activation_queue_eligibility__min_activation_balance_eth1_creds(spec, state): + index = 7 + balance = spec.MIN_ACTIVATION_BALANCE + set_eth1_withdrawal_credential_with_balance(spec, state, index) + yield from run_test_activation_queue_eligibility(spec, state, index, balance) + + +@with_electra_and_later +@spec_state_test +def test_activation_queue_eligibility__min_activation_balance_compounding_creds(spec, state): + index = 11 + balance = spec.MIN_ACTIVATION_BALANCE + set_compounding_withdrawal_credential_with_balance(spec, state, index) + yield from run_test_activation_queue_eligibility(spec, state, index, balance) + + +@with_electra_and_later +@spec_state_test +def test_activation_queue_eligibility__greater_than_min_activation_balance(spec, state): + index = 13 + balance = spec.MIN_ACTIVATION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + set_compounding_withdrawal_credential_with_balance(spec, state, index) + yield from run_test_activation_queue_eligibility(spec, state, index, balance) diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/__init__.py b/tests/core/pyspec/eth2spec/test/electra/fork/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py new file mode 100644 index 0000000000..10448ec2e8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_basic.py @@ -0,0 +1,263 @@ +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.electra.fork import ( + ELECTRA_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_pre_activation(spec, phases, state): + index = 0 + post_spec = phases[ELECTRA] + state.validators[index].activation_epoch = spec.FAR_FUTURE_EPOCH + post_state = yield from run_fork_test(post_spec, state) + + validator = post_state.validators[index] + assert post_state.balances[index] == 0 + assert validator.effective_balance == 0 + assert validator.activation_eligibility_epoch == spec.FAR_FUTURE_EPOCH + assert post_state.pending_deposits == [ + post_spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=state.balances[index], + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + ) + ] + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_pending_deposits_are_sorted(spec, phases, state): + post_spec = phases[ELECTRA] + state.validators[0].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validators[0].activation_eligibility_epoch = 2 + state.validators[1].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validators[1].activation_eligibility_epoch = 3 + state.validators[2].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validators[2].activation_eligibility_epoch = 2 + state.validators[3].activation_epoch = spec.FAR_FUTURE_EPOCH + state.validators[3].activation_eligibility_epoch = 1 + + post_state = yield from run_fork_test(post_spec, state) + + assert len(post_state.pending_deposits) == 4 + assert post_state.pending_deposits[0].pubkey == state.validators[3].pubkey + assert post_state.pending_deposits[1].pubkey == state.validators[0].pubkey + assert post_state.pending_deposits[2].pubkey == state.validators[2].pubkey + assert post_state.pending_deposits[3].pubkey == state.validators[1].pubkey + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_has_compounding_withdrawal_credential(spec, phases, state): + index = 0 + post_spec = phases[ELECTRA] + validator = state.validators[index] + state.balances[index] = post_spec.MIN_ACTIVATION_BALANCE + 1 + validator.withdrawal_credentials = ( + post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + ) + post_state = yield from run_fork_test(post_spec, state) + + assert post_state.balances[index] == post_spec.MIN_ACTIVATION_BALANCE + assert post_state.pending_deposits == [ + post_spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=state.balances[index] - post_spec.MIN_ACTIVATION_BALANCE, + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + ) + ] + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_inactive_compounding_validator_with_excess_balance(spec, phases, state): + index = 0 + post_spec = phases[ELECTRA] + validator = state.validators[index] + + # set validator balance greater than min_activation_balance + state.balances[index] = post_spec.MIN_ACTIVATION_BALANCE + 1 + # set validator as not active yet + validator.activation_epoch = spec.FAR_FUTURE_EPOCH + # set validator activation eligibility epoch to the latest finalized epoch + validator.activation_eligibility_epoch = state.finalized_checkpoint.epoch + # give the validator compounding withdrawal credentials + validator.withdrawal_credentials = ( + post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + ) + + post_state = yield from run_fork_test(post_spec, state) + + # the validator cannot be activated again + assert post_state.validators[index].activation_eligibility_epoch == spec.FAR_FUTURE_EPOCH + # the validator should now have a zero balance + assert post_state.balances[index] == 0 + # there should be a single pending deposit for this validator + assert post_state.pending_deposits == [ + post_spec.PendingDeposit( + pubkey=validator.pubkey, + withdrawal_credentials=validator.withdrawal_credentials, + amount=state.balances[index], + signature=spec.bls.G2_POINT_AT_INFINITY, + slot=spec.GENESIS_SLOT, + ) + ] + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_no_validator_exits(spec, phases, state): + # advance state so the current epoch is not zero + next_epoch(spec, state) + next_epoch(spec, state) + next_epoch(spec, state) + + post_spec = phases[ELECTRA] + post_state = yield from run_fork_test(post_spec, state) + + # the earliest exit epoch should be the compute_activation_exit_epoch + 1 + current_epoch = post_spec.compute_epoch_at_slot(post_state.slot) + expected_earliest_exit_epoch = post_spec.compute_activation_exit_epoch(current_epoch) + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_is_max_validator_exit_epoch(spec, phases, state): + # assign some validators exit epochs + state.validators[0].exit_epoch = 20 + state.validators[1].exit_epoch = 30 + state.validators[2].exit_epoch = 10 + + post_state = yield from run_fork_test(phases[ELECTRA], state) + + # the earliest exit epoch should be the greatest validator exit epoch + 1 + expected_earliest_exit_epoch = post_state.validators[1].exit_epoch + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_fork_earliest_exit_epoch_less_than_current_epoch(spec, phases, state): + # assign a validator an exit epoch + state.validators[0].exit_epoch = 1 + + # advance state so the current epoch is not zero + next_epoch(spec, state) + next_epoch(spec, state) + next_epoch(spec, state) + + post_spec = phases[ELECTRA] + post_state = yield from run_fork_test(post_spec, state) + + # the earliest exit epoch should be the compute_activation_exit_epoch + 1 + current_epoch = post_spec.compute_epoch_at_slot(post_state.slot) + expected_earliest_exit_epoch = post_spec.compute_activation_exit_epoch(current_epoch) + 1 + assert post_state.earliest_exit_epoch == expected_earliest_exit_epoch diff --git a/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_random.py b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_random.py new file mode 100644 index 0000000000..b165c783a7 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/fork/test_electra_fork_random.py @@ -0,0 +1,94 @@ +from random import Random + +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + DENEB, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.electra.fork import ( + ELECTRA_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_state +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@spec_test +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[ELECTRA], state) + + +@with_phases(phases=[DENEB], other_phases=[ELECTRA]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(ELECTRA_FORK_TEST_META_TAGS) +def test_electra_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[ELECTRA], state) diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py new file mode 100644 index 0000000000..37b1c977aa --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/fork_choice/test_deposit_with_reorg.py @@ -0,0 +1,102 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_presets, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + EIP7732, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.fork_choice import ( + apply_next_slots_with_attestations, + get_genesis_forkchoice_store_and_block, + tick_and_add_block, +) +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, +) + + +# TODO(jtraglia): In eip7732, how do we set execution requests in the payload envelope? +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_new_validator_deposit_with_multiple_epoch_transitions(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # yield anchor state and block + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + + test_steps = [] + + # (1) create deposit request for a new validator + deposit_request = prepare_deposit_request( + spec, len(state.validators), spec.MIN_ACTIVATION_BALANCE, signed=True + ) + deposit_block = build_empty_block_for_next_slot(spec, state) + deposit_block.body.execution_requests.deposits = [deposit_request] + deposit_block.body.execution_payload.block_hash = compute_el_block_hash_for_block( + spec, deposit_block + ) + signed_deposit_block = state_transition_and_sign_block(spec, state, deposit_block) + + pending_deposit = spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=deposit_block.slot, + ) + + assert state.pending_deposits == [pending_deposit] + + yield from tick_and_add_block(spec, store, signed_deposit_block, test_steps) + + # (2) finalize and process pending deposit on one fork + slots = 4 * spec.SLOTS_PER_EPOCH - state.slot + post_state, _, latest_block = yield from apply_next_slots_with_attestations( + spec, state, store, slots, True, True, test_steps + ) + + # check new validator has been created + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + # (3) create a conflicting block that triggers deposit processing on another fork + prev_epoch_ancestor = store.blocks[latest_block.message.parent_root] + # important to skip last block of the epoch to make client do the epoch processing + # otherwise, client can read the post-epoch from cache + prev_epoch_ancestor = store.blocks[prev_epoch_ancestor.parent_root] + another_fork_state = store.block_states[prev_epoch_ancestor.hash_tree_root()].copy() + + assert another_fork_state.pending_deposits == [pending_deposit] + + # skip a slot to create and process a fork block + next_slot(spec, another_fork_state) + post_state, _, _ = yield from apply_next_slots_with_attestations( + spec, another_fork_state, store, 1, True, True, test_steps + ) + + # check new validator has been created on another fork + assert post_state.pending_deposits == [] + new_validator = post_state.validators[len(post_state.validators) - 1] + assert new_validator.pubkey == pending_deposit.pubkey + assert new_validator.withdrawal_credentials == pending_deposit.withdrawal_credentials + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/electra/random/__init__.py b/tests/core/pyspec/eth2spec/test/electra/random/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/random/test_random.py b/tests/core/pyspec/eth2spec/test/electra/random/test_random.py new file mode 100644 index 0000000000..22a63b41f4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/random/test_random.py @@ -0,0 +1,1172 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.context import ( + always_bls, + misc_balances_in_default_range_with_many_validators, + only_generator, + single_phase, + spec_test, + with_custom_state, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.constants import ELECTRA +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([ELECTRA]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_electra + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_electra", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_electra", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/electra/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/__init__.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/__init__.py new file mode 100644 index 0000000000..805b38b2c1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/__init__.py @@ -0,0 +1,4 @@ +# This is a "hack" which allows other test files (e.g., test_deposit_transition.py) +# to reuse the sanity/block test format. +from .test_blocks import * # noqa: F401 F403 +from .test_deposit_transition import * # noqa: F401 F403 diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py new file mode 100644 index 0000000000..c5c8cf3f05 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_blocks.py @@ -0,0 +1,953 @@ +from eth2spec.test.context import ( + default_activation_threshold, + scaled_churn_balances_exceed_activation_exit_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_all_phases_from_except, + with_custom_state, + with_presets, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + transition_unsigned_block, +) +from eth2spec.test.helpers.bls_to_execution_changes import ( + get_signed_address_change, +) +from eth2spec.test.helpers.constants import ( + EIP7732, + ELECTRA, + MINIMAL, +) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import ( + prepare_signed_exits, +) +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential_with_balance, + set_eth1_withdrawal_credential_with_balance, +) + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_basic_el_withdrawal_request(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_index = 0 + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = [withdrawal_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_basic_btec_and_el_withdrawal_request_in_same_block(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_index = 0 + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + address = b"\x22" * 20 + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + to_execution_address=address, + ) + block.body.bls_to_execution_changes = [signed_address_change] + + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block.body.execution_requests.withdrawals = [withdrawal_request] + + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + validator = state.validators[validator_index] + assert validator.exit_epoch == state.earliest_exit_epoch + # Check if BTEC was applied + is_execution_address = ( + validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + ) + is_correct_source_address = validator.withdrawal_credentials[12:] == address + assert is_execution_address and is_correct_source_address + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_basic_btec_before_el_withdrawal_request(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_index = 0 + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + # block_1 contains a BTEC operation of the given validator + address = b"\x22" * 20 + signed_address_change = get_signed_address_change( + spec, + state, + validator_index=validator_index, + to_execution_address=address, + ) + block_1 = build_empty_block_for_next_slot(spec, state) + block_1.body.bls_to_execution_changes = [signed_address_change] + signed_block_1 = state_transition_and_sign_block(spec, state, block_1) + + validator = state.validators[validator_index] + assert validator.exit_epoch == spec.FAR_FUTURE_EPOCH + # Check if BTEC is effect + is_execution_address = ( + validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + ) + is_correct_source_address = validator.withdrawal_credentials[12:] == address + assert is_execution_address and is_correct_source_address + + # block_2 contains an EL-Exit operation of the given validator + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block_2 = build_empty_block_for_next_slot(spec, state) + block_2.body.execution_requests.withdrawals = [withdrawal_request] + block_2.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block_2) + signed_block_2 = state_transition_and_sign_block(spec, state, block_2) + + yield "blocks", [signed_block_1, signed_block_2] + yield "post", state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_cl_exit_and_el_withdrawal_request_in_same_block(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_index = 0 + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + # CL-Exit + signed_voluntary_exits = prepare_signed_exits(spec, state, indices=[validator_index]) + # EL-Exit + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + ) + block = build_empty_block_for_next_slot(spec, state) + block.body.voluntary_exits = signed_voluntary_exits + block.body.execution_requests.withdrawals = [withdrawal_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_multiple_el_partial_withdrawal_requests_same_validator(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_index = 0 + address = b"\x22" * 20 + balance = spec.MIN_ACTIVATION_BALANCE + 2000000000 + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, balance, balance, address + ) + + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request_1 = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.Gwei(1), + ) + withdrawal_request_2 = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.Gwei(2), + ) + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = [withdrawal_request_1, withdrawal_request_2] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert len(state.pending_partial_withdrawals) == 2 + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_multiple_el_partial_withdrawal_requests_different_validator(spec, state): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + validator_indices = [1, 2] + addresses = [bytes([v * 0x11]) * 20 for v in validator_indices] + balances = [spec.MIN_ACTIVATION_BALANCE + v * 2000000000 for v in validator_indices] + + for validator_index, address, balance in zip(validator_indices, addresses, balances): + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, balance, balance, address + ) + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + withdrawal_requests = [] + + for validator_index, address in zip(validator_indices, addresses): + validator_pubkey = state.validators[validator_index].pubkey + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=validator_pubkey, + amount=spec.Gwei(validator_index), + ) + withdrawal_requests.append(withdrawal_request) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = withdrawal_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + assert len(state.pending_partial_withdrawals) == 2 + for validator_index in validator_indices: + assert state.validators[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_withdrawal_and_withdrawal_request_same_validator(spec, state): + # Give a validator an excess balance + validator_index = 0 + excess_balance = 200000 + balance = spec.MAX_EFFECTIVE_BALANCE + excess_balance + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + balance=balance, + address=address, + ) + + # Ensure the validator has an upcoming withdrawal + # This will happen before the withdrawal request + expected_withdrawals, _ = spec.get_expected_withdrawals(state) + assert len(expected_withdrawals) == 1 + assert expected_withdrawals[0].validator_index == validator_index + + yield "pre", state + + # Create a 1 gwei withdrawal request for the same validator + withdrawal_request = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=state.validators[validator_index].pubkey, + amount=1, + ) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = [withdrawal_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure the withdrawal request was unsuccessful + assert len(state.pending_partial_withdrawals) == 0 + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_withdrawal_and_switch_to_compounding_request_same_validator(spec, state): + # Give a validator an excess balance + validator_index = 0 + excess_balance = 200000 + balance = spec.MAX_EFFECTIVE_BALANCE + excess_balance + address = b"\x22" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + balance=balance, + address=address, + ) + + # Ensure the validator has an upcoming withdrawal + # This will happen before the withdrawal request + expected_withdrawals, _ = spec.get_expected_withdrawals(state) + assert len(expected_withdrawals) == 1 + assert expected_withdrawals[0].validator_index == validator_index + + yield "pre", state + + # Create a switch to compounding validator request for the same validator + consolidation_request = spec.ConsolidationRequest( + source_address=address, + source_pubkey=state.validators[validator_index].pubkey, + target_pubkey=state.validators[validator_index].pubkey, + ) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = [consolidation_request] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure the validator has compounding credentials now + assert spec.is_compounding_withdrawal_credential( + state.validators[validator_index].withdrawal_credentials + ) + # Ensure there was no excess balance pending deposit + assert len(state.pending_deposits) == 0 + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_deposit_request_with_same_pubkey_different_withdrawal_credentials(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + # prepare three deposit requests, where + # 1st and 3rd have the same pubkey but different withdrawal credentials + deposit_request_0 = prepare_deposit_request( + spec, + len(state.validators), + spec.MIN_ACTIVATION_BALANCE, + state.eth1_deposit_index, + signed=True, + ) + deposit_request_1 = prepare_deposit_request( + spec, + len(state.validators) + 1, + spec.MIN_ACTIVATION_BALANCE, + state.eth1_deposit_index + 1, + signed=True, + ) + deposit_request_2 = prepare_deposit_request( + spec, + len(state.validators), + spec.MIN_ACTIVATION_BALANCE, + state.eth1_deposit_index + 2, + signed=True, + withdrawal_credentials=(spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + b"\x11" * 20), + ) + + # build a block with deposit requests + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.deposits = [ + deposit_request_0, + deposit_request_1, + deposit_request_2, + ] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + yield "pre", state + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # check deposit requests are processed correctly + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + assert state.pending_deposits[i] == spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=signed_block.message.slot, + ) + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_deposit_request_max_per_payload(spec, state): + # signify the eth1 bridge deprecation + state.deposit_requests_start_index = state.eth1_deposit_index + + validator_index = len(state.validators) + deposit_requests = [] + for i in range(spec.MAX_DEPOSIT_REQUESTS_PER_PAYLOAD): + deposit_requests.append( + prepare_deposit_request( + spec, + validator_index, + spec.EFFECTIVE_BALANCE_INCREMENT, + state.eth1_deposit_index + i, + signed=True, + ) + ) + + # build a block with deposit requests + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.deposits = deposit_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + yield "pre", state + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # check deposit requests are processed correctly + assert len(state.pending_deposits) == len(deposit_requests) + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + assert state.pending_deposits[i] == spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=signed_block.message.slot, + ) + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@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_withdrawal_and_consolidation_effective_balance_updates(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # We are going to process two blocks: + # 1) A block which processes a withdrawal and consolidation. + # 2) A block which forces epoch processing to happen. + # For this to work, we must transition to the 2nd to last slot of the epoch. + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 2 + transition_to(spec, state, slot) + + current_epoch = spec.get_current_epoch(state) + + # Initialize validator A (0x01 validator with 31.9 ETH) + a_index = spec.get_active_validator_indices(state, current_epoch)[0] + a_addr = b"\xaa" * 20 + assert state.validators[a_index].exit_epoch == spec.FAR_FUTURE_EPOCH + set_eth1_withdrawal_credential_with_balance( + spec, + state, + a_index, + # Given a balance of 31.9 ETH + # An excess balance isn't required for consolidations + balance=31_900_000_000, + # And given an effect balance of 32.0 ETH + # It's possible that its effective balance hasn't been updated yet + effective_balance=32_000_000_000, + address=a_addr, + ) + # Set withdrawable epoch to current epoch to allow processing + state.validators[a_index].withdrawable_epoch = current_epoch + + # Initialize validator B (0x02 validator with 64.0 ETH) + b_index = spec.get_active_validator_indices(state, current_epoch)[1] + b_addr = b"\xbb" * 20 + assert state.validators[b_index].exit_epoch == spec.FAR_FUTURE_EPOCH + set_compounding_withdrawal_credential_with_balance( + spec, + state, + b_index, + # 64 ETH + balance=64_000_000_000, + effective_balance=64_000_000_000, + address=b_addr, + ) + + # Add a pending consolidation from A -> B + state.validators[a_index].exit_epoch = spec.compute_consolidation_epoch_and_update_churn( + state, state.validators[a_index].effective_balance + ) + state.validators[a_index].withdrawable_epoch = current_epoch + 1 + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=a_index, target_index=b_index) + ] + + # Add a pending partial withdrawal for 32 ETH from B + state.pending_partial_withdrawals = [ + spec.PendingPartialWithdrawal( + validator_index=b_index, + amount=spec.MIN_ACTIVATION_BALANCE, + withdrawable_epoch=current_epoch, + ) + ] + + yield "pre", state + + # Process a block to process the pending withdrawal/consolidation + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block_a = state_transition_and_sign_block(spec, state, block) + + # Process another block to trigger epoch processing + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block_b = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block_a, signed_block_b] + yield "post", state + + # Ensure we are in the next epoch + assert spec.get_current_epoch(state) == current_epoch + 1 + # The pending consolidation should have been processed + assert state.pending_consolidations == [] + # The pending partial withdrawal should have been processed + assert state.pending_partial_withdrawals == [] + # Validator A should have exited, consolidation + assert state.validators[a_index].exit_epoch != spec.FAR_FUTURE_EPOCH + # Validator B should have an effective balance of 64 ETH + assert state.validators[b_index].effective_balance == 64 * spec.EFFECTIVE_BALANCE_INCREMENT + # Validator B's balance should be less than its effective balance, hysteria + assert state.balances[b_index] < state.validators[b_index].effective_balance + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@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_consolidation_requests_when_pending_consolidation_queue_is_full(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Fill up the queue with invalid pending consolidations + # Making these legit would be too much work + # One less than the limit, to ensure another can be added + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0x1111, target_index=0x2222) + ] * (spec.PENDING_CONSOLIDATIONS_LIMIT - 1) + + # This will consolidate 0->1, 2->3, 4->5, ... + consolidation_requests = [] + for i in range(0, 2 * spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD, 2): + # Setup the source validator + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[i + 0] + source_address = b"\x11" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + source_index, + address=source_address, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + ) + # Setup the target validator + target_index = spec.get_active_validator_indices(state, current_epoch)[i + 1] + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Make the consolidation request + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + ) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = consolidation_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure another consolidation was added and the other one was rejected + assert len(state.pending_consolidations) == spec.PENDING_CONSOLIDATIONS_LIMIT + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@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_switch_to_compounding_requests_when_pending_consolidation_queue_is_full(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Fill up the queue with invalid pending consolidations + # Making these legit would be too much work + # + # Note: If a client optimizes the `process_consolidation_request` function to be a single + # function with a for-loop, it's possible that they stop processing all consolidation requests + # after the consolidation request. For this reason, the pending consolidations queue in this + # test starts off as full and consolidations requests are made. + state.pending_consolidations = [ + spec.PendingConsolidation(source_index=0x1111, target_index=0x2222) + ] * spec.PENDING_CONSOLIDATIONS_LIMIT + + # This will contain two requests: + # 1. A regular consolidation request + # 2. A switch to compounding request + consolidation_requests = [] + + # Setup the source validator + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + source_address = b"\x11" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + source_index, + address=source_address, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + ) + + # Setup the target validator + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Make the consolidation request + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + ) + + # Make the switch to compounding validator request + # We can reuse the source validator because it wasn't processed + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + ) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = consolidation_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure the pending consolidations queue is still full + assert len(state.pending_consolidations) == spec.PENDING_CONSOLIDATIONS_LIMIT + # Ensure the validators have been upgraded to compounding validators + assert spec.has_compounding_withdrawal_credential(state.validators[source_index]) + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@spec_state_test +def test_switch_to_compounding_requests_when_too_little_consolidation_churn_limit(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # We didn't use the `scaled_churn_balances_exceed_activation_exit_churn_limit` state, so this + # state shouldn't have enough churn to process any consolidation requests. + assert spec.get_consolidation_churn_limit(state) <= spec.MIN_ACTIVATION_BALANCE + + # This will contain two requests: + # 1. A regular consolidation request + # 2. A switch to compounding request + consolidation_requests = [] + + # Setup the source validator + current_epoch = spec.get_current_epoch(state) + source_index = spec.get_active_validator_indices(state, current_epoch)[0] + source_address = b"\x11" * 20 + set_eth1_withdrawal_credential_with_balance( + spec, + state, + source_index, + address=source_address, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + ) + + # Setup the target validator + target_index = spec.get_active_validator_indices(state, current_epoch)[1] + set_compounding_withdrawal_credential_with_balance(spec, state, target_index) + + # Make the consolidation request + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + ) + + # Make the switch to compounding validator request + # We can reuse the source validator because it wasn't processed + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[source_index].pubkey, + ) + ) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = consolidation_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure the validators have been upgraded to compounding validators + assert spec.has_compounding_withdrawal_credential(state.validators[source_index]) + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@with_presets([MINIMAL], "Keep the size of the test reasonable") +@spec_state_test +def test_withdrawal_requests_when_pending_withdrawal_queue_is_full(spec, state): + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for withdrawal + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Fill up the queue with invalid pending withdrawals + # Making these legit would be too much work + # One less than the limit, to ensure another can be added + state.pending_partial_withdrawals = [ + spec.PendingPartialWithdrawal( + validator_index=0x1111, + amount=spec.Gwei(1), + # Withdrawable next epoch, so they aren't processed now + withdrawable_epoch=spec.get_current_epoch(state) + 1, + ) + ] * (spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT - 1) + + # Setup a compounding validator with an excess balance + index = 0 + address = b"\x22" * 20 + balance = spec.MIN_ACTIVATION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + set_compounding_withdrawal_credential_with_balance( + spec, state, index, balance, balance, address + ) + assert state.validators[index].exit_epoch == spec.FAR_FUTURE_EPOCH + + yield "pre", state + + # Setup two withdrawal requests with different amounts + withdrawal_request_1 = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=state.validators[index].pubkey, + amount=spec.Gwei(1), + ) + withdrawal_request_2 = spec.WithdrawalRequest( + source_address=address, + validator_pubkey=state.validators[index].pubkey, + amount=spec.Gwei(2), + ) + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.withdrawals = [withdrawal_request_1, withdrawal_request_2] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + signed_block = state_transition_and_sign_block(spec, state, block) + + yield "blocks", [signed_block] + yield "post", state + + # Ensure the pending withdrawals queue is full + assert len(state.pending_partial_withdrawals) == spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT + # Ensure the last pending withdrawal is for the first withdrawal request + last_withdrawal = state.pending_partial_withdrawals[spec.PENDING_PARTIAL_WITHDRAWALS_LIMIT - 1] + assert last_withdrawal.validator_index == index + assert last_withdrawal.amount == withdrawal_request_1.amount + assert withdrawal_request_1.amount != withdrawal_request_2.amount + + +@with_all_phases_from_except(ELECTRA, [EIP7732]) +@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_multi_epoch_consolidation_chain(spec, state): + """ + This doesn't work the has I had envisioned, but I guess that's a good reason to keep it. When + chaining consolidations like this, the transferred balance is limited by the effective balance + of the source validator, which doesn't update until after all consolidations are processed. + Given that all validators have the same balance, this is effectively a consolidation from the + first validator in the consolidation to the final target validator. + """ + + # Move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # Check that we're at the first slot of the epoch + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + current_epoch = spec.get_current_epoch(state) + + # This will consolidate 0->1, 1->2, 2->3, ... + consolidation_request_count = 0 + for i in range(spec.SLOTS_PER_EPOCH): + consolidation_requests = [] + for j in range(0, spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD): + # Setup the source validator + k = i * spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD + j + source_index = spec.get_active_validator_indices(state, current_epoch)[k] + source_address = b"\x11" * 20 + set_compounding_withdrawal_credential_with_balance( + spec, + state, + source_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + address=source_address, + ) + # Setup the target validator + k = i * spec.MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD + j + 1 + target_index = spec.get_active_validator_indices(state, current_epoch)[k] + set_compounding_withdrawal_credential_with_balance( + spec, + state, + target_index, + effective_balance=spec.MIN_ACTIVATION_BALANCE, + balance=spec.MIN_ACTIVATION_BALANCE, + ) + + # Make the consolidation request + consolidation_requests.append( + spec.ConsolidationRequest( + source_address=source_address, + source_pubkey=state.validators[source_index].pubkey, + target_pubkey=state.validators[target_index].pubkey, + ) + ) + consolidation_request_count += 1 + + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_requests.consolidations = consolidation_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + transition_unsigned_block(spec, state, block) + + # Check that we're in the next epoch + assert spec.get_current_epoch(state) == current_epoch + 1 + # Check that validators at the beginning of the chain are exited + assert len(state.pending_consolidations) == consolidation_request_count + for i in range(consolidation_request_count): + assert state.validators[i].exit_epoch != spec.FAR_FUTURE_EPOCH + + # Remove MIN_VALIDATOR_WITHDRAWABILITY_DELAY to speed things up + for i, consolidation in enumerate(state.pending_consolidations): + state.validators[consolidation.source_index].withdrawable_epoch = ( + state.validators[consolidation.source_index].exit_epoch + 1 + ) + + # Get the first slot that consolidations will be processed + first_consolidation = state.pending_consolidations[0] + first_slot = ( + state.validators[first_consolidation.source_index].withdrawable_epoch * spec.SLOTS_PER_EPOCH + ) + # Get the last slot that consolidations will be processed + final_consolidation = state.pending_consolidations[consolidation_request_count - 1] + last_slot = ( + state.validators[final_consolidation.source_index].withdrawable_epoch * spec.SLOTS_PER_EPOCH + ) + + # Transition to the slot/epoch when the first consolidation will be processed + transition_to(spec, state, first_slot - 1) + # Ensure the none of the pending consolidations were processed + assert len(state.pending_consolidations) == consolidation_request_count + + yield "pre", state + + # Process slots until all pending consolidations are processed + blocks = [] + for _ in range(last_slot - first_slot + 1): + block = build_empty_block_for_next_slot(spec, state) + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + blocks.append(state_transition_and_sign_block(spec, state, block)) + + yield "blocks", blocks + yield "post", state + + # Ensure all pending consolidations have been processed + assert len(state.pending_consolidations) == 0 + # Check that the final target validator's effective balance changed. + # The effective balance of the 2nd to last (~32ETH) validator is added to it. + final_target_validator = state.validators[final_consolidation.target_index] + assert final_target_validator.effective_balance > spec.MIN_ACTIVATION_BALANCE + assert final_target_validator.effective_balance <= 2 * spec.MIN_ACTIVATION_BALANCE diff --git a/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py new file mode 100644 index 0000000000..4311b0fdb4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/blocks/test_deposit_transition.py @@ -0,0 +1,337 @@ +from eth2spec.test.context import ( + ELECTRA, + spec_state_test, + with_phases, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.deposits import ( + build_deposit_data, + deposit_from_context, + prepare_deposit_request, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) + + +def run_deposit_transition_block(spec, state, block, top_up_keys=[], valid=True): + """ + Run ``process_block``, yielding: + - pre-state ('pre') + - block ('block') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + yield "pre", state + + pre_pending_deposits_len = len(state.pending_deposits) + pre_validators_len = len(state.validators) + + # Include deposits into a block + signed_block = state_transition_and_sign_block(spec, state, block, not valid) + + yield "blocks", [signed_block] + yield "post", state if valid else None + + # Check that deposits are applied + if valid: + # Check that deposits are applied + for i, deposit in enumerate(block.body.deposits): + # Validator is created with 0 balance + validator = state.validators[pre_validators_len + i] + assert validator.pubkey == deposit.data.pubkey + assert validator.withdrawal_credentials == deposit.data.withdrawal_credentials + assert validator.effective_balance == spec.Gwei(0) + assert state.balances[pre_validators_len + i] == spec.Gwei(0) + + # The corresponding pending deposit is created + pending_deposit = state.pending_deposits[pre_pending_deposits_len + i] + assert pending_deposit.pubkey == deposit.data.pubkey + assert pending_deposit.withdrawal_credentials == deposit.data.withdrawal_credentials + assert pending_deposit.amount == deposit.data.amount + assert pending_deposit.signature == deposit.data.signature + assert pending_deposit.slot == spec.GENESIS_SLOT + + # Assert that no unexpected validators were created + assert len(state.validators) == pre_validators_len + len(block.body.deposits) + + # Check that deposit requests are processed + for i, deposit_request in enumerate(block.body.execution_requests.deposits): + # The corresponding pending deposit is created + pending_deposit = state.pending_deposits[ + pre_pending_deposits_len + len(block.body.deposits) + i + ] + assert pending_deposit.pubkey == deposit_request.pubkey + assert pending_deposit.withdrawal_credentials == deposit_request.withdrawal_credentials + assert pending_deposit.amount == deposit_request.amount + assert pending_deposit.signature == deposit_request.signature + assert pending_deposit.slot == signed_block.message.slot + + # Assert that no unexpected pending deposits were created + assert len(state.pending_deposits) == pre_pending_deposits_len + len( + block.body.deposits + ) + len(block.body.execution_requests.deposits) + + +def prepare_state_and_block( + spec, + state, + deposit_cnt, + deposit_request_cnt, + first_deposit_request_index=0, + deposit_requests_start_index=None, + eth1_data_deposit_count=None, +): + deposits = [] + deposit_requests = [] + keypair_index = len(state.validators) + + # Prepare deposits + deposit_data_list = [] + for index in range(deposit_cnt): + deposit_data = build_deposit_data( + spec, + pubkeys[keypair_index], + privkeys[keypair_index], + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, + # insecurely use pubkey as withdrawal key + spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkeys[keypair_index])[1:], + signed=True, + ) + deposit_data_list.append(deposit_data) + keypair_index += 1 + + deposit_root = None + for index in range(deposit_cnt): + deposit, deposit_root, _ = deposit_from_context(spec, deposit_data_list, index) + deposits.append(deposit) + + if deposit_root: + state.eth1_deposit_index = 0 + if not eth1_data_deposit_count: + eth1_data_deposit_count = deposit_cnt + state.eth1_data = spec.Eth1Data( + deposit_root=deposit_root, + deposit_count=eth1_data_deposit_count, + block_hash=state.eth1_data.block_hash, + ) + + # Prepare deposit requests + for offset in range(deposit_request_cnt): + deposit_request = prepare_deposit_request( + spec, + keypair_index, + # use min activation balance + spec.MIN_ACTIVATION_BALANCE, + first_deposit_request_index + offset, + signed=True, + ) + deposit_requests.append(deposit_request) + keypair_index += 1 + + # Set start index if defined + if deposit_requests_start_index: + state.deposit_requests_start_index = deposit_requests_start_index + + block = build_empty_block_for_next_slot(spec, state) + + # Assign deposits and deposit requests + block.body.deposits = deposits + block.body.execution_requests.deposits = deposit_requests + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + return state, block + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__start_index_is_set(spec, state): + # 0 deposits, 2 deposit requests, unset deposit_requests_start_index + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=0, + deposit_request_cnt=2, + first_deposit_request_index=state.eth1_data.deposit_count + 11, + ) + + yield from run_deposit_transition_block(spec, state, block) + + # deposit_requests_start_index must be set to the index of the first request + assert state.deposit_requests_start_index == block.body.execution_requests.deposits[0].index + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__process_eth1_deposits(spec, state): + # 3 deposits, 1 deposit request, state.eth1_data.deposit_count < state.deposit_requests_start_index + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=3, + deposit_request_cnt=1, + first_deposit_request_index=11, + deposit_requests_start_index=7, + ) + + yield from run_deposit_transition_block(spec, state, block) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__process_max_eth1_deposits(spec, state): + # spec.MAX_DEPOSITS deposits, 1 deposit request, state.eth1_data.deposit_count > state.deposit_requests_start_index + # state.deposit_requests_start_index == spec.MAX_DEPOSITS + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=spec.MAX_DEPOSITS, + deposit_request_cnt=1, + first_deposit_request_index=spec.MAX_DEPOSITS + 1, + deposit_requests_start_index=spec.MAX_DEPOSITS, + eth1_data_deposit_count=23, + ) + + yield from run_deposit_transition_block(spec, state, block) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__process_eth1_deposits_up_to_start_index(spec, state): + # 3 deposits, 1 deposit request, state.eth1_data.deposit_count == state.deposit_requests_start_index + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=3, + deposit_request_cnt=1, + first_deposit_request_index=7, + deposit_requests_start_index=3, + ) + + yield from run_deposit_transition_block(spec, state, block) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__invalid_not_enough_eth1_deposits(spec, state): + # 3 deposits, 1 deposit request, state.eth1_data.deposit_count < state.deposit_requests_start_index + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=3, + deposit_request_cnt=1, + first_deposit_request_index=29, + deposit_requests_start_index=23, + eth1_data_deposit_count=17, + ) + + yield from run_deposit_transition_block(spec, state, block, valid=False) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__invalid_too_many_eth1_deposits(spec, state): + # 3 deposits, 1 deposit request, state.eth1_data.deposit_count < state.eth1_data_index + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=3, + deposit_request_cnt=1, + first_deposit_request_index=11, + deposit_requests_start_index=7, + eth1_data_deposit_count=2, + ) + + yield from run_deposit_transition_block(spec, state, block, valid=False) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__invalid_eth1_deposits_overlap_in_protocol_deposits(spec, state): + # spec.MAX_DEPOSITS deposits, 1 deposit request, state.eth1_data.deposit_count > state.deposit_requests_start_index + # state.deposit_requests_start_index == spec.MAX_DEPOSITS - 1 + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=spec.MAX_DEPOSITS, + deposit_request_cnt=1, + first_deposit_request_index=spec.MAX_DEPOSITS, + deposit_requests_start_index=spec.MAX_DEPOSITS - 1, + eth1_data_deposit_count=23, + ) + + yield from run_deposit_transition_block(spec, state, block, valid=False) + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__deposit_and_top_up_same_block(spec, state): + # 1 deposit, 1 deposit request that top ups deposited validator + state, block = prepare_state_and_block( + spec, + state, + deposit_cnt=1, + deposit_request_cnt=1, + first_deposit_request_index=11, + deposit_requests_start_index=7, + ) + + # Artificially assign deposit's pubkey to a deposit request of the same block + top_up_keys = [block.body.deposits[0].data.pubkey] + block.body.execution_requests.deposits[0].pubkey = top_up_keys[0] + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + pre_pending_deposits = len(state.pending_deposits) + + yield from run_deposit_transition_block(spec, state, block, top_up_keys=top_up_keys) + + # Check the top up + assert len(state.pending_deposits) == pre_pending_deposits + 2 + assert state.pending_deposits[pre_pending_deposits].amount == block.body.deposits[0].data.amount + amount_from_deposit = block.body.execution_requests.deposits[0].amount + assert state.pending_deposits[pre_pending_deposits + 1].amount == amount_from_deposit + + +@with_phases([ELECTRA]) +@spec_state_test +def test_deposit_transition__deposit_with_same_pubkey_different_withdrawal_credentials(spec, state): + deposit_count = 1 + deposit_request_count = 4 + + state, block = prepare_state_and_block( + spec, state, deposit_cnt=deposit_count, deposit_request_cnt=deposit_request_count + ) + + # pick 2 indices among deposit requests to have the same pubkey as the deposit + indices_with_same_pubkey = [1, 3] + for index in indices_with_same_pubkey: + block.body.execution_requests.deposits[index].pubkey = block.body.deposits[0].data.pubkey + # ensure top-up deposit request withdrawal credentials are different than the deposit + assert ( + block.body.execution_requests.deposits[index].withdrawal_credentials + != block.body.deposits[0].data.withdrawal_credentials + ) + + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + deposit_requests = block.body.execution_requests.deposits.copy() + + yield from run_deposit_transition_block(spec, state, block) + + assert len(state.pending_deposits) == deposit_request_count + deposit_count + for index in indices_with_same_pubkey: + assert ( + state.pending_deposits[deposit_count + index].pubkey == deposit_requests[index].pubkey + ) + # ensure withdrawal credentials are retained, rather than being made the same + assert ( + state.pending_deposits[deposit_count + index].withdrawal_credentials + == deposit_requests[index].withdrawal_credentials + ) 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..19a94a0d84 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/sanity/test_slots.py @@ -0,0 +1,197 @@ +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_pending_deposit_extra_gwei(spec, state): + # Deposit where the amount has a little extra gwei + index = len(state.validators) + deposit = prepare_pending_deposit( + spec, + validator_index=index, + # The deposit amount includes some gwei (the +1 at the end) + amount=spec.MIN_ACTIVATION_BALANCE + spec.Gwei(1), + signed=True, + ) + + yield from run_epoch_processing(spec, state, pending_deposits=[deposit]) + + # Check deposit balance is applied correctly + assert state.balances[index] == deposit.amount + assert state.validators[index].effective_balance == spec.MIN_ACTIVATION_BALANCE + + +@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_different_signature(spec, state): + # Create multiple deposits with the same pubkey, but only the first has a valid signature + index = len(state.validators) + + # First deposit with valid signature + deposit0 = prepare_pending_deposit( + spec, validator_index=index, amount=spec.MIN_ACTIVATION_BALANCE // 2, signed=True + ) + + # Second deposit without signature + deposit1 = prepare_pending_deposit( + spec, validator_index=index, amount=spec.MIN_ACTIVATION_BALANCE // 2, signed=False + ) + + pending_deposits = [deposit0, deposit1] + + yield from run_epoch_processing(spec, state, pending_deposits=pending_deposits) + + # Check that both deposits are accepted + assert state.balances[index] == deposit0.amount + deposit1.amount + 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[source_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/transition/__init__.py b/tests/core/pyspec/eth2spec/test/electra/transition/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py b/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py new file mode 100644 index 0000000000..3bb31c2039 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/transition/test_operations.py @@ -0,0 +1,104 @@ +from eth2spec.test.context import ( + always_bls, + ForkMeta, + with_fork_metas, + with_presets, +) +from eth2spec.test.helpers.constants import ( + AFTER_ELECTRA_PRE_POST_FORKS, + MINIMAL, +) +from eth2spec.test.helpers.fork_transition import ( + OperationType, + run_transition_with_operation, +) + +# +# DepositRequest +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_deposit_request_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a DEPOSIT_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.DEPOSIT_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +# +# WithdrawalRequest +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=66) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS + ] +) +@with_presets([MINIMAL], reason="too slow") +@always_bls +def test_transition_with_full_withdrawal_request_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a WITHDRAWAL_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.WITHDRAWAL_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) + + +# +# ConsolidationRequest +# + + +@with_fork_metas( + [ + ForkMeta(pre_fork_name=pre, post_fork_name=post, fork_epoch=2) + for pre, post in AFTER_ELECTRA_PRE_POST_FORKS + ] +) +@always_bls +def test_transition_with_consolidation_request_right_after_fork( + state, fork_epoch, spec, post_spec, pre_tag, post_tag +): + """ + Create a CONSOLIDATION_REQUEST right *after* the transition + """ + yield from run_transition_with_operation( + state, + fork_epoch, + spec, + post_spec, + pre_tag, + post_tag, + operation_type=OperationType.CONSOLIDATION_REQUEST, + operation_at_slot=fork_epoch * spec.SLOTS_PER_EPOCH, + ) diff --git a/tests/core/pyspec/eth2spec/test/electra/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/electra/unittests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 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 new file mode 100644 index 0000000000..1c7a3ce00a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/unittests/test_config_invariants.py @@ -0,0 +1,25 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_electra_and_later, +) + + +@with_electra_and_later +@spec_test +@single_phase +def test_processing_pending_partial_withdrawals(spec): + assert 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/electra/unittests/test_execution_engine_interface.py b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_engine_interface.py new file mode 100644 index 0000000000..21700ac8f4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_engine_interface.py @@ -0,0 +1,46 @@ +from eth2spec.test.context import ( + ELECTRA, + spec_state_test, + with_phases, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, +) +from eth2spec.test.helpers.state import next_slot + + +@with_phases([ELECTRA]) +@spec_state_test +def test_noop_execution_engine_notify_new_payload_electra(spec, state): + """ + Test NoopExecutionEngine.notify_new_payload returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.notify_new_payload( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests_list=[], + ) + assert result is True + + +@with_phases([ELECTRA]) +@spec_state_test +def test_noop_execution_engine_is_valid_block_hash_electra(spec, state): + """ + Test NoopExecutionEngine.is_valid_block_hash returns True and doesn't modify state + """ + engine = spec.NoopExecutionEngine() + + next_slot(spec, state) + payload = build_empty_execution_payload(spec, state) + result = engine.is_valid_block_hash( + execution_payload=payload, + parent_beacon_block_root=state.latest_block_header.parent_root, + execution_requests_list=[], + ) + + assert result is True diff --git a/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_requests.py b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_requests.py new file mode 100644 index 0000000000..d57e724312 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/electra/unittests/test_execution_requests.py @@ -0,0 +1,119 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_electra_and_later, +) + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_serialization_round_trip__empty(spec): + execution_requests = spec.ExecutionRequests() + serialized_execution_requests = spec.get_execution_requests_list(execution_requests) + deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests) + assert deserialized_execution_requests == execution_requests + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_serialization_round_trip__one_request(spec): + execution_requests = spec.ExecutionRequests( + deposits=[spec.DepositRequest()], + ) + serialized_execution_requests = spec.get_execution_requests_list(execution_requests) + deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests) + assert deserialized_execution_requests == execution_requests + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_serialization_round_trip__multiple_requests(spec): + execution_requests = spec.ExecutionRequests( + deposits=[spec.DepositRequest()], + withdrawals=[spec.WithdrawalRequest()], + consolidations=[spec.ConsolidationRequest()], + ) + serialized_execution_requests = spec.get_execution_requests_list(execution_requests) + deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests) + assert deserialized_execution_requests == execution_requests + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_serialization_round_trip__one_request_with_real_data(spec): + execution_requests = spec.ExecutionRequests( + deposits=[ + spec.DepositRequest( + pubkey=spec.BLSPubkey(48 * "aa"), + withdrawal_credentials=spec.Bytes32(32 * "bb"), + amount=spec.Gwei(11111111), + signature=spec.BLSSignature(96 * "cc"), + index=spec.uint64(22222222), + ), + ] + ) + serialized_execution_requests = spec.get_execution_requests_list(execution_requests) + deserialized_execution_requests = spec.get_execution_requests(serialized_execution_requests) + assert deserialized_execution_requests == execution_requests + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_deserialize__reject_duplicate_request(spec): + serialized_withdrawal = 76 * b"\x0a" + serialized_execution_requests = [ + spec.WITHDRAWAL_REQUEST_TYPE + serialized_withdrawal, + spec.WITHDRAWAL_REQUEST_TYPE + serialized_withdrawal, + ] + try: + spec.get_execution_requests(serialized_execution_requests) + assert False, "expected exception" + except Exception: + pass + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_deserialize__reject_out_of_order_requests(spec): + serialized_execution_requests = [ + spec.WITHDRAWAL_REQUEST_TYPE + 76 * b"\x0a", + spec.DEPOSIT_REQUEST_TYPE + 192 * b"\x0b", + ] + assert int(serialized_execution_requests[0][0]) > int(serialized_execution_requests[1][0]) + try: + spec.get_execution_requests(serialized_execution_requests) + assert False, "expected exception" + except Exception: + pass + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_deserialize__reject_empty_request(spec): + serialized_execution_requests = [b"\x01"] + try: + spec.get_execution_requests(serialized_execution_requests) + assert False, "expected exception" + except Exception: + pass + + +@with_electra_and_later +@spec_test +@single_phase +def test_requests_deserialize__reject_unexpected_request_type(spec): + serialized_execution_requests = [ + b"\x03\xff\xff\xff", + ] + try: + spec.get_execution_requests(serialized_execution_requests) + assert False, "expected exception" + except Exception: + pass diff --git a/tests/core/pyspec/eth2spec/test/exceptions.py b/tests/core/pyspec/eth2spec/test/exceptions.py index c553ec3744..eb16b7b39c 100644 --- a/tests/core/pyspec/eth2spec/test/exceptions.py +++ b/tests/core/pyspec/eth2spec/test/exceptions.py @@ -1,2 +1,4 @@ -class SkippedTest(Exception): - ... +class SkippedTest(Exception): ... + + +class BlockNotFoundException(Exception): ... diff --git a/tests/core/pyspec/eth2spec/test/fulu/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/epoch_processing/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/epoch_processing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/epoch_processing/test_process_proposer_lookahead.py b/tests/core/pyspec/eth2spec/test/fulu/epoch_processing/test_process_proposer_lookahead.py new file mode 100644 index 0000000000..2159e60bf4 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/epoch_processing/test_process_proposer_lookahead.py @@ -0,0 +1,70 @@ +from eth2spec.test.context import spec_state_test, with_fulu_and_later +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.state import next_epoch + + +@with_fulu_and_later +@spec_state_test +def test_proposer_lookahead_in_state_matches_computed_lookahead(spec, state): + """Test that the proposer lookahead in the state matches the computed lookahead.""" + # Transition few epochs to past the MIN_SEED_LOOKAHEAD + for _ in range(spec.MIN_SEED_LOOKAHEAD + 1): + next_epoch(spec, state) + + # Get initial lookahead + initial_lookahead = state.proposer_lookahead.copy() + + # Run epoch processing + yield from run_epoch_processing_with(spec, state, "process_proposer_lookahead") + + # Verify lookahead was shifted correctly + assert ( + state.proposer_lookahead[: spec.SLOTS_PER_EPOCH * spec.MIN_SEED_LOOKAHEAD] + == initial_lookahead[spec.SLOTS_PER_EPOCH :] + ) + + # run_epoch_processing_with does not increment the slot + state.slot += 1 + + # Verify lookahead in state matches the computed lookahead + computed_lookahead = spec.initialize_proposer_lookahead(state) + assert state.proposer_lookahead == computed_lookahead + + +@with_fulu_and_later +@spec_state_test +def test_proposer_lookahead_does_not_contain_exited_validators(spec, state): + """ + Test proposer lookahead does not contain exited validators. + """ + # Transition few epochs to past the MIN_SEED_LOOKAHEAD + for _ in range(spec.MIN_SEED_LOOKAHEAD + 1): + next_epoch(spec, state) + + # Exit first half of active validators + active_validators = spec.get_active_validator_indices(state, spec.get_current_epoch(state)) + validators_to_exit = active_validators[: len(active_validators) // 2] + + # Initiate validator exits + for validator_index in validators_to_exit: + spec.initiate_validator_exit(state, validator_index) + + # Check when these validators are scheduled to exit + exit_epochs = [state.validators[i].exit_epoch for i in validators_to_exit] + min_exit_epoch = min(exit_epochs) + + # Progress epoch until we reach the epoch with the first validator exit + while spec.get_current_epoch(state) < min_exit_epoch - 1: + next_epoch(spec, state) + + # Run epoch processing, many validators will exit in this epoch + yield from run_epoch_processing_with(spec, state, "process_proposer_lookahead") + + # run_epoch_processing_with does not increment the slot + state.slot += 1 + + # Check that the proposer lookahead does not contain exited validators + for validator_index in state.proposer_lookahead: + assert spec.is_active_validator( + state.validators[validator_index], spec.get_current_epoch(state) + ), f"Validator {validator_index} in lookahead should be active" diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/fork/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py new file mode 100644 index 0000000000..7c4cbab771 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_basic.py @@ -0,0 +1,92 @@ +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + ELECTRA, + FULU, + MINIMAL, +) +from eth2spec.test.helpers.fulu.fork import ( + FULU_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_epoch_via_block, +) +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_base_state(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_next_epoch(spec, phases, state): + next_epoch(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_next_epoch_with_block(spec, phases, state): + next_epoch_via_block(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_many_next_epoch(spec, phases, state): + for _ in range(3): + next_epoch(spec, state) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_low_balances(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_misc_balances(spec, phases, state): + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@spec_test +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fork_random_large_validator_set(spec, phases, state): + yield from run_fork_test(phases[FULU], state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_proposer_lookahead.py b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_proposer_lookahead.py new file mode 100644 index 0000000000..8af971e337 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_proposer_lookahead.py @@ -0,0 +1,113 @@ +from eth2spec.test.context import ( + spec_test, + with_phases, + with_state, +) +from eth2spec.test.helpers.constants import ( + ELECTRA, + FULU, +) +from eth2spec.test.helpers.fork_transition import do_fork_generate +from eth2spec.test.helpers.fulu.fork import ( + FULU_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.state import ( + cause_effective_balance_decrease_below_threshold, + simulate_lookahead, + simulate_lookahead_with_thresholds, +) +from eth2spec.test.helpers.withdrawals import set_compounding_withdrawal_credential +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_lookahead_consistency_at_fork(spec, phases, state): + """ + Test that lookahead is consistent before/after the Fulu fork. + """ + + # Calculate the current and next epoch lookahead by simulating the state progression + # with empty slots and calling `get_beacon_proposer_index` (how it was done pre-Fulu) + pre_fork_proposers = simulate_lookahead(spec, state) + + # Upgrade to Fulu + spec = phases[FULU] + state = yield from run_fork_test(spec, state) + + # Check if the pre-fork simulation matches the post-fork `state.proposer_lookahead` + assert pre_fork_proposers == state.proposer_lookahead + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_proposer_lookahead_init_at_fork_only_contains_active_validators(spec, phases, state): + """ + Test proposer lookahead does not contain exited validators at Fulu fork activation. + """ + current_epoch = spec.get_current_epoch(state) + + # Change the active validator set by exiting half of the validators in future epochs + # within the MIN_SEED_LOOKAHEAD range + for validator_index in range(len(state.validators) // 2): + validator = state.validators[validator_index] + # Set exit_epoch to a future epoch within MIN_SEED_LOOKAHEAD + 1 range + # This makes the validator active at current_epoch but exited in future epochs + validator.exit_epoch = current_epoch + 1 + + # Upgrade to Fulu + spec = phases[FULU] + state = yield from run_fork_test(spec, state) + + # Check that the proposer lookahead does not contain inactive validators + for slot_index, validator_index in enumerate(state.proposer_lookahead): + epoch_for_slot = current_epoch + (slot_index // spec.SLOTS_PER_EPOCH) + assert spec.is_active_validator(state.validators[validator_index], epoch_for_slot), ( + f"Validator {validator_index} in lookahead at slot {slot_index} (epoch {epoch_for_slot}) should be active" + ) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_lookahead_consistency_with_effective_balance_change_at_fork(spec, phases, state): + # Move to the last slot of the current epoch + spec.process_slots( + state, state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1 + ) + assert state.slot % spec.SLOTS_PER_EPOCH == spec.SLOTS_PER_EPOCH - 1 + + # Calculate the lookahead of next epoch, including the thresholds of effective balance that + # make a validator be a proposer at each slot. + next_epoch_lookahead_threshold = simulate_lookahead_with_thresholds(spec, state)[ + spec.SLOTS_PER_EPOCH : + ] + next_epoch_lookahead = simulate_lookahead(spec, state)[spec.SLOTS_PER_EPOCH :] + assert next_epoch_lookahead_threshold[0][0] == next_epoch_lookahead[0], ( + "The first index in the lookahead should match the first index in the threshold lookahead." + ) + + # Change the validator balance enough to trigger a change in the effective balance that goes below the threshold. + validator_change_index = next_epoch_lookahead_threshold[0][0] + validator_change_threshold = next_epoch_lookahead_threshold[0][1] + set_compounding_withdrawal_credential(spec, state, validator_change_index) + cause_effective_balance_decrease_below_threshold( + spec, state, validator_change_index, validator_change_threshold + ) + + state, _ = yield from do_fork_generate( + state, spec, phases[FULU], spec.get_current_epoch(state) + 1 + ) + + # Calculate the actual lookahead + actual_lookahead = simulate_lookahead(spec, state)[: spec.SLOTS_PER_EPOCH] + + # Because the Electra epoch processing is always run right before the Fulu upgrade, + # the proposers lookahead will change depending on the effective balance change. + assert next_epoch_lookahead != actual_lookahead diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py new file mode 100644 index 0000000000..c1d3060f1a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork/test_fulu_fork_random.py @@ -0,0 +1,94 @@ +from random import Random + +from eth2spec.test.context import ( + large_validator_set, + low_balances, + misc_balances, + spec_test, + with_custom_state, + with_phases, + with_presets, + with_state, +) +from eth2spec.test.helpers.constants import ( + ELECTRA, + FULU, + MINIMAL, +) +from eth2spec.test.helpers.fulu.fork import ( + FULU_FORK_TEST_META_TAGS, + run_fork_test, +) +from eth2spec.test.helpers.random import randomize_state +from eth2spec.test.utils import with_meta_tags + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_0(spec, phases, state): + randomize_state(spec, state, rng=Random(1010)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_1(spec, phases, state): + randomize_state(spec, state, rng=Random(2020)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_2(spec, phases, state): + randomize_state(spec, state, rng=Random(3030)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_state +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_3(spec, phases, state): + randomize_state(spec, state, rng=Random(4040)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_low_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(5050)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@spec_test +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_misc_balances(spec, phases, state): + randomize_state(spec, state, rng=Random(6060)) + yield from run_fork_test(phases[FULU], state) + + +@with_phases(phases=[ELECTRA], other_phases=[FULU]) +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) +@spec_test +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) +@with_meta_tags(FULU_FORK_TEST_META_TAGS) +def test_fulu_fork_random_large_validator_set(spec, phases, state): + randomize_state(spec, state, rng=Random(7070)) + yield from run_fork_test(phases[FULU], state) diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py new file mode 100644 index 0000000000..421eed26ab --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/fork_choice/test_on_block.py @@ -0,0 +1,340 @@ +from random import Random + +from eth2spec.test.context import ( + spec_state_test, + with_fulu_and_later, +) +from eth2spec.test.helpers.blob import get_block_with_blob_and_sidecars +from eth2spec.test.helpers.fork_choice import ( + BlobData, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block_with_data, +) + + +def flip_one_bit_in_bytes(data: bytes, index: int = 0) -> bytes: + """ + Flip one bit in a bytes object at the given index. + """ + constr = data.__class__ + byte_index = index // 8 + bit_index = 7 - (index % 8) + byte = data[byte_index] + flipped_byte = byte ^ (1 << bit_index) + + return constr(bytes(data[:byte_index]) + bytes([flipped_byte]) + bytes(data[byte_index + 1 :])) + + +def get_alt_sidecars(spec, state): + """ + Get alternative sidecars for negative test cases. + """ + rng = Random(4321) + state_copy = state.copy() + _, _, _, _, alt_sidecars = get_block_with_blob_and_sidecars( + spec, state_copy, rng=rng, blob_count=2 + ) + return alt_sidecars + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__ok(spec, state): + """ + Similar to test_simple_blob_data, but in PeerDAS version that is from Fulu onwards. + It covers code related to the blob sidecars because on_block calls `is_data_available` + and we are calling `get_data_column_sidecars_from_block` in the test itself. + """ + rng = Random(1234) + + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + _, _, _, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + blob_data = BlobData(sidecars=sidecars) + + yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) + + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # On receiving a block of next epoch + _, _, _, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + blob_data = BlobData(sidecars=sidecars) + + yield from tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data) + + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +def run_on_block_peerdas_invalid_test(spec, state, fn): + """ + Run a invalid PeerDAS on_block test with a sidecars mutation function. + """ + rng = Random(1234) + + test_steps = [] + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + _, _, _, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + sidecars = fn(sidecars) + blob_data = BlobData(sidecars=sidecars) + + yield from tick_and_add_block_with_data( + spec, store, signed_block, test_steps, blob_data, valid=False + ) + assert spec.get_head(store) != signed_block.message.hash_tree_root() + + yield "steps", test_steps + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__not_available(spec, state): + """ + Test is_data_available throws an exception when not enough columns are sampled. + """ + yield from run_on_block_peerdas_invalid_test( + spec, + state, + # Empty sidecars will trigger the simulation of not enough columns being sampled + lambda _: [], + ) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_zero_blobs(spec, state): + """ + Test is_data_available returns false when there are no blobs in the sidecars. + """ + + def invalid_zero_blobs(sidecars): + sidecars[0].column = [] + sidecars[0].kzg_commitments = [] + sidecars[0].kzg_proofs = [] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_zero_blobs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_index_1(spec, state): + """ + Test invalid index in sidecars for negative PeerDAS on_block test. + """ + + def invalid_index(sidecars): + sidecars[0].index = 128 # Invalid index + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_index) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_index_2(spec, state): + """ + Test invalid index in sidecars for negative PeerDAS on_block test. + """ + + def invalid_index(sidecars): + sidecars[0].index = 256 # Invalid index + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_index) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_column_1(spec, state): + """ + Test mismatch length in column for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_column(sidecars): + sidecars[0].column = sidecars[0].column[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_column_2(spec, state): + """ + Test mismatch length in column for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_column(sidecars): + sidecars[1].column = sidecars[1].column[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_commitments_1(spec, state): + """ + Test mismatch length in kzg_commitments for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_commitments(sidecars): + sidecars[0].kzg_commitments = sidecars[0].kzg_commitments[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_commitments) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_commitments_2(spec, state): + """ + Test mismatch length in kzg_commitments for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_commitments(sidecars): + sidecars[1].kzg_commitments = sidecars[1].kzg_commitments[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_commitments) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_proofs_1(spec, state): + """ + Test mismatch length in kzg_proofs for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_proofs(sidecars): + sidecars[0].kzg_proofs = sidecars[0].kzg_proofs[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_proofs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_mismatch_len_kzg_proofs_2(spec, state): + """ + Test mismatch length in kzg_proofs for negative PeerDAS on_block test. + """ + + def invalid_mismatch_len_kzg_proofs(sidecars): + sidecars[1].kzg_proofs = sidecars[1].kzg_proofs[1:] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_mismatch_len_kzg_proofs) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_column_1(spec, state): + """ + Test wrong column for negative PeerDAS on_block test. + """ + + def invalid_wrong_column(sidecars): + sidecars[0].column[0] = flip_one_bit_in_bytes(sidecars[0].column[0], 80) + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_column_2(spec, state): + """ + Test wrong column for negative PeerDAS on_block test. + """ + + def invalid_wrong_column(sidecars): + sidecars[1].column[1] = flip_one_bit_in_bytes(sidecars[1].column[1], 20) + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_column) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_commitment_1(spec, state): + """ + Test wrong commitment for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_commitment(sidecars, alt_sidecars=alt_sidecars): + sidecars[0].kzg_commitments[0] = alt_sidecars[0].kzg_commitments[0] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_commitment) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_commitment_2(spec, state): + """ + Test wrong commitment for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_commitment(sidecars, alt_sidecars=alt_sidecars): + sidecars[1].kzg_commitments[1] = alt_sidecars[1].kzg_commitments[1] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_commitment) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_proof_1(spec, state): + """ + Test wrong proof for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_proof(sidecars, alt_sidecars=alt_sidecars): + sidecars[0].kzg_proofs[0] = alt_sidecars[0].kzg_proofs[0] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_proof) + + +@with_fulu_and_later +@spec_state_test +def test_on_block_peerdas__invalid_wrong_proof_2(spec, state): + """ + Test wrong proof for negative PeerDAS on_block test. + """ + alt_sidecars = get_alt_sidecars(spec, state) + + def invalid_wrong_proof(sidecars, alt_sidecars=alt_sidecars): + sidecars[1].kzg_proofs[1] = alt_sidecars[1].kzg_proofs[1] + return sidecars + + yield from run_on_block_peerdas_invalid_test(spec, state, invalid_wrong_proof) diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py new file mode 100644 index 0000000000..966b57ac00 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/merkle_proof/test_single_merkle_proof.py @@ -0,0 +1,103 @@ +import random + +from eth2spec.debug.random_value import ( + get_random_ssz_object, + RandomizationMode, +) +from eth2spec.test.context import ( + spec_state_test, + with_fulu_and_later, + with_test_suite_name, +) +from eth2spec.test.helpers.blob import ( + get_sample_blob_tx, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, + sign_block, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) + + +def _run_blob_kzg_commitments_merkle_proof_test(spec, state, rng=None, blob_count=1): + opaque_tx, blobs, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=blob_count) + if rng is None: + block = build_empty_block_for_next_slot(spec, state) + else: + block = get_random_ssz_object( + rng, + spec.BeaconBlock, + max_bytes_length=2000, + max_list_length=2000, + mode=RandomizationMode, + chaos=True, + ) + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = sign_block(spec, state, block, proposer_index=0) + cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + column_sidcars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) + column_sidcar = column_sidcars[0] + + yield "object", block.body + kzg_commitments_inclusion_proof = column_sidcar.kzg_commitments_inclusion_proof + gindex = spec.get_generalized_index(spec.BeaconBlockBody, "blob_kzg_commitments") + yield ( + "proof", + { + "leaf": "0x" + column_sidcar.kzg_commitments.hash_tree_root().hex(), + "leaf_index": gindex, + "branch": ["0x" + root.hex() for root in kzg_commitments_inclusion_proof], + }, + ) + assert spec.is_valid_merkle_branch( + leaf=column_sidcar.kzg_commitments.hash_tree_root(), + branch=column_sidcar.kzg_commitments_inclusion_proof, + depth=spec.floorlog2(gindex), + index=spec.get_subtree_index(gindex), + root=column_sidcar.signed_block_header.message.body_root, + ) + assert spec.verify_data_column_sidecar_kzg_proofs(column_sidcar) + assert spec.verify_data_column_sidecar_inclusion_proof(column_sidcar) + + +@with_test_suite_name("BeaconBlockBody") +@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_fulu_and_later +@spec_state_test +def test_blob_kzg_commitments_merkle_proof__random_block_1(spec, state): + rng = random.Random(1111) + yield from _run_blob_kzg_commitments_merkle_proof_test(spec, state, rng=rng) + + +@with_test_suite_name("BeaconBlockBody") +@with_fulu_and_later +@spec_state_test +def test_blob_kzg_commitments_merkle_proof__multiple_blobs(spec, state): + blob_count = spec.get_blob_parameters(spec.get_current_epoch(state)).max_blobs_per_block // 2 + rng = random.Random(2222) + yield from _run_blob_kzg_commitments_merkle_proof_test( + spec, state, rng=rng, blob_count=blob_count + ) + + +@with_test_suite_name("BeaconBlockBody") +@with_fulu_and_later +@spec_state_test +def test_blob_kzg_commitments_merkle_proof__max_blobs(spec, state): + max_blobs = spec.get_blob_parameters(spec.get_current_epoch(state)).max_blobs_per_block + rng = random.Random(3333) + yield from _run_blob_kzg_commitments_merkle_proof_test( + spec, state, rng=rng, blob_count=max_blobs + ) diff --git a/tests/core/pyspec/eth2spec/test/fulu/networking/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/networking/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/networking/test_compute_columns_for_custody_group.py b/tests/core/pyspec/eth2spec/test/fulu/networking/test_compute_columns_for_custody_group.py new file mode 100644 index 0000000000..60799d0a50 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/networking/test_compute_columns_for_custody_group.py @@ -0,0 +1,64 @@ +import random + +from eth2spec.test.context import ( + single_phase, + spec_test, + with_fulu_and_later, +) + + +def _run_compute_columns_for_custody_group(spec, rng, custody_group=None): + if custody_group is None: + custody_group = rng.randint(0, spec.config.NUMBER_OF_CUSTODY_GROUPS - 1) + + result = spec.compute_columns_for_custody_group(custody_group) + yield "custody_group", "meta", custody_group + + assert len(result) == len(set(result)) + assert len(result) == spec.config.NUMBER_OF_COLUMNS // spec.config.NUMBER_OF_CUSTODY_GROUPS + 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_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group__min_custody_group(spec): + rng = random.Random(1111) + yield from _run_compute_columns_for_custody_group(spec, rng, custody_group=0) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group__max_custody_group(spec): + rng = random.Random(1111) + yield from _run_compute_columns_for_custody_group( + spec, rng, custody_group=spec.config.NUMBER_OF_CUSTODY_GROUPS - 1 + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group__1(spec): + rng = random.Random(1111) + yield from _run_compute_columns_for_custody_group(spec, rng) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group__2(spec): + rng = random.Random(2222) + yield from _run_compute_columns_for_custody_group(spec, rng) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_columns_for_custody_group__3(spec): + rng = random.Random(3333) + yield from _run_compute_columns_for_custody_group(spec, rng) diff --git a/tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_groups.py b/tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_groups.py new file mode 100644 index 0000000000..0fbd7b896c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/networking/test_get_custody_groups.py @@ -0,0 +1,136 @@ +import random + +from eth2spec.test.context import ( + single_phase, + spec_test, + with_fulu_and_later, +) + + +def _run_get_custody_groups(spec, rng, node_id=None, custody_group_count=None): + if node_id is None: + node_id = rng.randint(0, 2**256 - 1) + + if custody_group_count is None: + custody_group_count = rng.randint(0, spec.config.NUMBER_OF_CUSTODY_GROUPS) + + result = spec.get_custody_groups(node_id, custody_group_count) + yield "node_id", "meta", node_id + yield "custody_group_count", "meta", int(custody_group_count) + + assert len(result) == len(set(result)) + assert len(result) == custody_group_count + assert all(i < spec.config.NUMBER_OF_CUSTODY_GROUPS for i in result) + python_list_result = [int(i) for i in result] + + yield "result", "meta", python_list_result + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_min_node_id_min_custody_group_count(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups(spec, rng, node_id=0, custody_group_count=0) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_min_node_id_max_custody_group_count(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups( + spec, rng, node_id=0, custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_max_node_id_min_custody_group_count(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups(spec, rng, node_id=2**256 - 1, custody_group_count=0) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_max_node_id_max_custody_group_count(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups( + spec, + rng, + node_id=2**256 - 1, + custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_max_node_id_minus_1_max_custody_group_count(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups( + spec, + rng, + node_id=2**256 - 2, + custody_group_count=spec.config.NUMBER_OF_CUSTODY_GROUPS, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_short_node_id(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups(spec, rng, node_id=1048576, custody_group_count=1) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_max_node_id_custody_group_count_is_4(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups( + spec, + rng, + node_id=2**256 - 1, + custody_group_count=4, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_max_node_id_minus_1_custody_group_count_is_4(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups( + spec, + rng, + node_id=2**256 - 2, + custody_group_count=4, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_1(spec): + rng = random.Random(1111) + yield from _run_get_custody_groups(spec, rng) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_2(spec): + rng = random.Random(2222) + yield from _run_get_custody_groups(spec, rng) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_get_custody_groups_3(spec): + rng = random.Random(3333) + yield from _run_get_custody_groups(spec, rng) diff --git a/tests/core/pyspec/eth2spec/test/fulu/random/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/random/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py b/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py new file mode 100644 index 0000000000..f7c9ce51e5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/random/test_random.py @@ -0,0 +1,1172 @@ +""" +This module is generated from the ``random`` test generator. +Please do not edit this file manually. +See the README for that generator for more information. +""" + +from eth2spec.test.context import ( + always_bls, + misc_balances_in_default_range_with_many_validators, + only_generator, + single_phase, + spec_test, + with_custom_state, + with_phases, + zero_activation_threshold, +) +from eth2spec.test.helpers.constants import FULU +from eth2spec.test.utils.randomized_block_tests import ( + run_generated_randomized_test, +) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_0(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_1(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_2(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_3(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_4(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_5(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_6(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_7(spec, state): + # scenario as high-level, informal text: + # epochs:0,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_8(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_9(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_10(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_11(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_12(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:last_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_13(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:random_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_14(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) + + +@only_generator("randomized test for broad coverage, not point-to-point CI") +@with_phases([FULU]) +@with_custom_state( + balances_fn=misc_balances_in_default_range_with_many_validators, + threshold_fn=zero_activation_threshold, +) +@spec_test +@single_phase +@always_bls +def test_randomized_15(spec, state): + # scenario as high-level, informal text: + # epochs:epochs_until_leak,slots:0,with-block:no_block + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + # epochs:1,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:no_block + # epochs:0,slots:0,with-block:random_block_fulu + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block_fulu", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state_fulu", + } # noqa: E501 + yield from run_generated_randomized_test( + spec, + state, + scenario, + ) diff --git a/tests/core/pyspec/eth2spec/test/fulu/sanity/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/sanity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead.py b/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead.py new file mode 100644 index 0000000000..326b08ee28 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead.py @@ -0,0 +1,90 @@ +from eth2spec.test.context import ( + spec_state_test, + with_phases, +) +from eth2spec.test.helpers.attestations import ( + state_transition_with_full_block, +) +from eth2spec.test.helpers.constants import ELECTRA, FULU +from eth2spec.test.helpers.state import ( + next_epoch, + simulate_lookahead, +) +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential, +) + + +def run_test_effective_balance_increase_changes_lookahead( + spec, state, randao_setup_epochs, expect_lookahead_changed +): + # Advance few epochs to adjust the RANDAO + for _ in range(randao_setup_epochs): + next_epoch(spec, state) + + # Set all active validators to have balance close to the hysteresis threshold + current_epoch = spec.get_current_epoch(state) + active_validator_indices = spec.get_active_validator_indices(state, current_epoch) + for validator_index in active_validator_indices: + # Set compounding withdrawal credentials for the validator + set_compounding_withdrawal_credential(spec, state, validator_index) + state.validators[validator_index].effective_balance = 32000000000 + # Set balance to close the next hysteresis threshold + state.balances[validator_index] = 33250000000 - 1 + + # Calculate the lookahead of next epoch + next_epoch_lookahead = simulate_lookahead(spec, state)[spec.SLOTS_PER_EPOCH :] + + blocks = [] + yield "pre", state + + # Process 1-epoch worth of blocks with attestations + for _ in range(spec.SLOTS_PER_EPOCH): + block = state_transition_with_full_block( + spec, state, fill_cur_epoch=True, fill_prev_epoch=True + ) + blocks.append(block) + + yield "blocks", blocks + yield "post", state + + # Calculate the actual lookahead + actual_lookahead = simulate_lookahead(spec, state)[: spec.SLOTS_PER_EPOCH] + + if expect_lookahead_changed: + assert next_epoch_lookahead != actual_lookahead + else: + assert next_epoch_lookahead == actual_lookahead + + +def run_test_with_randao_setup_epochs(spec, state, randao_setup_epochs): + if spec.fork == ELECTRA: + # Pre-EIP-7917, effective balance changes due to attestation rewards + # changes the next epoch's lookahead + expect_lookahead_changed = True + else: + # Post-EIP-7917, effective balance changes due to attestation rewards + # do not change the next epoch's lookahead + expect_lookahead_changed = False + + yield from run_test_effective_balance_increase_changes_lookahead( + spec, state, randao_setup_epochs, expect_lookahead_changed=expect_lookahead_changed + ) + + +@with_phases(phases=[ELECTRA, FULU]) +@spec_state_test +def test_effective_balance_increase_changes_lookahead(spec, state): + # Since this test relies on the RANDAO, we adjust the number of next_epoch transitions + # we do at the setup of the test run until the assertion passes. + # We start with 4 epochs because the test is known to pass with 4 epochs. + for randao_setup_epochs in range(4, 20): + try: + state_copy = state.copy() + yield from run_test_with_randao_setup_epochs(spec, state_copy, randao_setup_epochs) + return + except AssertionError: + # If the randao_setup_epochs is not the right one to make the test pass, + # then try again in the next iteration + pass + assert False, "The test should have succeeded with one of the iterations." diff --git a/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead_slots.py b/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead_slots.py new file mode 100644 index 0000000000..8ea4b1d613 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/sanity/test_lookahead_slots.py @@ -0,0 +1,66 @@ +from eth2spec.test.context import ( + spec_state_test, + with_electra_and_later, +) +from eth2spec.test.helpers.forks import is_post_fulu +from eth2spec.test.helpers.state import ( + cause_effective_balance_decrease_below_threshold, + simulate_lookahead, + simulate_lookahead_with_thresholds, + transition_to, +) +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential, +) + + +@with_electra_and_later +@spec_state_test +def test_effective_decrease_balance_updates_lookahead(spec, state): + """ + Test that effective balance updates change the proposer lookahead with EIP-7917. + """ + # Calculate the lookahead of next epoch, including the thresholds of effective balance that + # make a validator be a proposer at each slot. + next_epoch_lookahead_threshold = simulate_lookahead_with_thresholds(spec, state)[ + spec.SLOTS_PER_EPOCH : + ] + next_epoch_lookahead = simulate_lookahead(spec, state)[spec.SLOTS_PER_EPOCH :] + assert next_epoch_lookahead_threshold[0][0] == next_epoch_lookahead[0], ( + "The first index in the lookahead should match the first index in the threshold lookahead." + ) + + # Change the validator balance enough to trigger a change in the effective balance that goes below the threshold. + validator_change_index = next_epoch_lookahead_threshold[0][0] + validator_change_threshold = next_epoch_lookahead_threshold[0][1] + set_compounding_withdrawal_credential(spec, state, validator_change_index) + cause_effective_balance_decrease_below_threshold( + spec, state, validator_change_index, validator_change_threshold + ) + + pre_eb = state.validators[validator_change_index].effective_balance + + # 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) + + # Do the epoch transition that should change the validator balance. + yield "pre", state + yield "slots", 1 + spec.process_slots(state, state.slot + 1) + yield "post", state + + post_eb = state.validators[validator_change_index].effective_balance + + assert pre_eb != post_eb, "Effective balance should have changed." + assert post_eb < validator_change_threshold, "Effective balance should be below the threshold." + + # Calculate the actual lookahead + actual_lookahead = simulate_lookahead(spec, state)[: spec.SLOTS_PER_EPOCH] + + if not is_post_fulu(spec): + # Pre-EIP-7917, effective balance changes changes the next epoch's lookahead + assert next_epoch_lookahead != actual_lookahead + else: + # Post-EIP-7917, effective balance changes do not change the next epoch's lookahead + assert next_epoch_lookahead == actual_lookahead diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py new file mode 100644 index 0000000000..6b12586196 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/das/test_das.py @@ -0,0 +1,131 @@ +import random + +from deepdiff import DeepDiff + +from eth2spec.test.context import ( + single_phase, + spec_state_test, + spec_test, + with_fulu_and_later, +) +from eth2spec.test.helpers.blob import ( + get_block_with_blob_and_sidecars, + get_sample_blob, +) +from eth2spec.test.helpers.fork_choice import BlobData, with_blob_data + + +def chunks(lst, n): + """Helper that splits a list into N sized chunks.""" + return [lst[i : i + n] for i in range(0, len(lst), n)] + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_matrix(spec): + rng = random.Random(5566) + + blob_count = 2 + input_blobs = [get_sample_blob(spec, rng=rng) for _ in range(blob_count)] + matrix = spec.compute_matrix(input_blobs) + assert len(matrix) == spec.CELLS_PER_EXT_BLOB * blob_count + + rows = chunks(matrix, spec.CELLS_PER_EXT_BLOB) + assert len(rows) == blob_count + for row in rows: + assert len(row) == spec.CELLS_PER_EXT_BLOB + + for blob_index, row in enumerate(rows): + extended_blob = [] + for entry in row: + extended_blob.extend(spec.cell_to_coset_evals(entry.cell)) + blob_part = extended_blob[0 : len(extended_blob) // 2] + blob = b"".join([spec.bls_field_to_bytes(x) for x in blob_part]) + assert blob == input_blobs[blob_index] + + +@with_fulu_and_later +@spec_test +@single_phase +def test_recover_matrix(spec): + rng = random.Random(5566) + + # Number of samples we will be recovering from + N_SAMPLES = spec.CELLS_PER_EXT_BLOB // 2 + + # Compute an extended matrix with two blobs + blob_count = 2 + blobs = [get_sample_blob(spec, rng=rng) for _ in range(blob_count)] + matrix = spec.compute_matrix(blobs) + + # Construct a matrix with some entries missing + partial_matrix = [] + for blob_entries in chunks(matrix, spec.CELLS_PER_EXT_BLOB): + rng.shuffle(blob_entries) + partial_matrix.extend(blob_entries[:N_SAMPLES]) + + # Given the partial matrix, recover the missing entries + recovered_matrix = spec.recover_matrix(partial_matrix, blob_count) + + # Ensure that the recovered matrix matches the original matrix + assert recovered_matrix == matrix + + +def run_is_data_available_peerdas_test(spec, blob_data): + def callback(): + yield spec.is_data_available(spec.Root(b"\x00" * 32)) + + return next(with_blob_data(spec, blob_data, callback)) + + +@with_fulu_and_later +@spec_state_test +def test_is_data_available_peerdas(spec, state): + rng = random.Random(1234) + _, blobs, blob_kzg_proofs, _, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + blob_data = BlobData(blobs, blob_kzg_proofs, sidecars) + + result = run_is_data_available_peerdas_test(spec, blob_data) + + assert result is True, "Data should be available for the block with blob data" + + +@with_fulu_and_later +@spec_state_test +def test_get_data_column_sidecars(spec, state): + rng = random.Random(1234) + _, blobs, _, signed_block, sidecars = get_block_with_blob_and_sidecars( + spec, state, rng=rng, blob_count=2 + ) + + sidecars_result = spec.get_data_column_sidecars( + signed_block_header=spec.compute_signed_block_header(signed_block), + kzg_commitments=sidecars[0].kzg_commitments, + kzg_commitments_inclusion_proof=sidecars[0].kzg_commitments_inclusion_proof, + cells_and_kzg_proofs=[spec.compute_cells_and_kzg_proofs(blob) for blob in blobs], + ) + + assert len(sidecars_result) == len(sidecars), ( + "Should return the same number of sidecars as input" + ) + assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" + + +@with_fulu_and_later +@spec_state_test +def test_get_data_column_sidecars_from_column_sidecar(spec, state): + rng = random.Random(1234) + _, blobs, _, _, sidecars = get_block_with_blob_and_sidecars(spec, state, rng=rng, blob_count=2) + + sidecars_result = spec.get_data_column_sidecars_from_column_sidecar( + sidecar=sidecars[0], + cells_and_kzg_proofs=[spec.compute_cells_and_kzg_proofs(blob) for blob in blobs], + ) + + assert len(sidecars_result) == len(sidecars), ( + "Should return the same number of sidecars as input" + ) + assert DeepDiff(sidecars, sidecars_result) == {}, "Sidecars should match the expected sidecars" diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py new file mode 100644 index 0000000000..b804509e57 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/polynomial_commitments/test_polynomial_commitments.py @@ -0,0 +1,296 @@ +import random + +from eth2spec.test.context import ( + expect_assertion_error, + single_phase, + spec_test, + with_fulu_and_later, +) +from eth2spec.test.helpers.blob import ( + get_sample_blob, +) +from eth2spec.utils.bls import BLS_MODULUS + + +@with_fulu_and_later +@spec_test +@single_phase +def test_fft(spec): + # in this test we sample a random polynomial in coefficient form + # then we apply an FFT to get evaluations over the roots of unity + # we then apply an inverse FFT to the evaluations to get coefficients + + # we check two things: + # 1) the original coefficients and the resulting coefficients match + # 2) the evaluations that we got are the same as if we would have evaluated individually + + rng = random.Random(5566) + + roots_of_unity = spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + + # sample a random polynomial + poly_coeff = [ + spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + + # do an FFT and then an inverse FFT + poly_eval = spec.fft_field(poly_coeff, roots_of_unity) + poly_coeff_inversed = spec.fft_field(poly_eval, roots_of_unity, inv=True) + + # first check: inverse FFT after FFT results in original coefficients + assert len(poly_eval) == len(poly_coeff) == len(poly_coeff_inversed) + assert poly_coeff_inversed == poly_coeff + + # second check: result of FFT are really the evaluations + for i, w in enumerate(roots_of_unity): + individual_evaluation = spec.evaluate_polynomialcoeff(poly_coeff, w) + assert individual_evaluation == poly_eval[i] + + +@with_fulu_and_later +@spec_test +@single_phase +def test_coset_fft(spec): + # in this test we sample a random polynomial in coefficient form + # then we apply a Coset FFT to get evaluations over the coset of the roots of unity + # we then apply an inverse Coset FFT to the evaluations to get coefficients + + # we check two things: + # 1) the original coefficients and the resulting coefficients match + # 2) the evaluations that we got are the same as if we would have evaluated individually + + rng = random.Random(5566) + + roots_of_unity = spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + + # this is the shift that generates the coset + coset_shift = spec.BLSFieldElement(spec.PRIMITIVE_ROOT_OF_UNITY) + + # sample a random polynomial + poly_coeff = [ + spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + + # do a coset FFT and then an inverse coset FFT + poly_eval = spec.coset_fft_field(poly_coeff, roots_of_unity) + poly_coeff_inversed = spec.coset_fft_field(poly_eval, roots_of_unity, inv=True) + + # first check: inverse coset FFT after coset FFT results in original coefficients + assert len(poly_eval) == len(poly_coeff) == len(poly_coeff_inversed) + assert poly_coeff_inversed == poly_coeff + + # second check: result of FFT are really the evaluations over the coset + for i, w in enumerate(roots_of_unity): + # the element of the coset is coset_shift * w + shifted_w = coset_shift * w + individual_evaluation = spec.evaluate_polynomialcoeff(poly_coeff, shifted_w) + assert individual_evaluation == poly_eval[i] + + +@with_fulu_and_later +@spec_test +@single_phase +def test_construct_vanishing_polynomial(spec): + rng = random.Random(5566) + + num_missing_cells = rng.randint(0, spec.CELLS_PER_EXT_BLOB - 1) + # Get a unique list of `num_missing_cells` cell indices + unique_missing_cell_indices = rng.sample(range(spec.CELLS_PER_EXT_BLOB), num_missing_cells) + + zero_poly_coeff = spec.construct_vanishing_polynomial(unique_missing_cell_indices) + roots_of_unity = spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_EXT_BLOB) + zero_poly_eval = spec.fft_field(zero_poly_coeff, roots_of_unity) + zero_poly_eval_brp = spec.bit_reversal_permutation(zero_poly_eval) + + for cell_index in range(spec.CELLS_PER_EXT_BLOB): + start = cell_index * spec.FIELD_ELEMENTS_PER_CELL + end = (cell_index + 1) * spec.FIELD_ELEMENTS_PER_CELL + if cell_index in unique_missing_cell_indices: + assert all(a == spec.BLSFieldElement(0) for a in zero_poly_eval_brp[start:end]) + else: # cell_index in cell_indices + assert all(a != spec.BLSFieldElement(0) for a in zero_poly_eval_brp[start:end]) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_verify_cell_kzg_proof_batch_zero_cells(spec): + # Verify with zero cells should return true + assert spec.verify_cell_kzg_proof_batch( + commitments_bytes=[], + cell_indices=[], + cells=[], + proofs_bytes=[], + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_verify_cell_kzg_proof_batch(spec): + # test with a single blob / commitment + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + assert spec.verify_cell_kzg_proof_batch( + commitments_bytes=[commitment, commitment], + cell_indices=[0, 4], + cells=[cells[0], cells[4]], + proofs_bytes=[proofs[0], proofs[4]], + ) + + # now test with three blobs / commitments + all_blobs = [] + all_commitments = [] + all_cells = [] + all_proofs = [] + for _ in range(3): + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + all_blobs.append(blob) + all_commitments.append(commitment) + all_cells.append(cells) + all_proofs.append(proofs) + + # the cells of interest + commitment_indices = [0, 0, 1, 2, 1] + cell_indices = [0, 4, 0, 1, 2] + cells = [all_cells[i][j] for (i, j) in zip(commitment_indices, cell_indices)] + proofs = [all_proofs[i][j] for (i, j) in zip(commitment_indices, cell_indices)] + commitments = [all_commitments[i] for i in commitment_indices] + + # do the check + assert spec.verify_cell_kzg_proof_batch( + commitments_bytes=commitments, + cell_indices=cell_indices, + cells=cells, + proofs_bytes=proofs, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_verify_cell_kzg_proof_batch_invalid(spec): + # test with a single blob / commitment + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + return + + assert len(cells) == len(proofs) + + assert not spec.verify_cell_kzg_proof_batch( + commitments_bytes=[commitment, commitment], + cell_indices=[0, 4], + cells=[cells[0], cells[5]], # Note: this is where it should go wrong + proofs_bytes=[proofs[0], proofs[4]], + ) + + # now test with three blobs / commitments + all_blobs = [] + all_commitments = [] + all_cells = [] + all_proofs = [] + for _ in range(3): + blob = get_sample_blob(spec) + commitment = spec.blob_to_kzg_commitment(blob) + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + assert len(cells) == len(proofs) + + all_blobs.append(blob) + all_commitments.append(commitment) + all_cells.append(cells) + all_proofs.append(proofs) + + # the cells of interest + commitment_indices = [0, 0, 1, 2, 1] + cell_indices = [0, 4, 0, 1, 2] + cells = [all_cells[i][j] for (i, j) in zip(commitment_indices, cell_indices)] + proofs = [all_proofs[i][j] for (i, j) in zip(commitment_indices, cell_indices)] + commitments = [all_commitments[i] for i in commitment_indices] + + # let's change one of the cells. Then it should not verify + cells[1] = all_cells[1][3] + + # do the check + assert not spec.verify_cell_kzg_proof_batch( + commitments_bytes=commitments, + cell_indices=cell_indices, + cells=cells, + proofs_bytes=proofs, + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_recover_cells_and_kzg_proofs(spec): + rng = random.Random(5566) + + # Number of samples we will be recovering from + N_SAMPLES = spec.CELLS_PER_EXT_BLOB // 2 + + # Get the data we will be working with + blob = get_sample_blob(spec) + + # Extend data with Reed-Solomon and split the extended data in cells + cells, proofs = spec.compute_cells_and_kzg_proofs(blob) + + # Compute the cells we will be recovering from + cell_indices = [] + # First figure out just the indices of the cells + for i in range(N_SAMPLES): + j = rng.randint(0, spec.CELLS_PER_EXT_BLOB - 1) + while j in cell_indices: + j = rng.randint(0, spec.CELLS_PER_EXT_BLOB - 1) + cell_indices.append(j) + # Now the cells themselves + known_cells = [cells[cell_index] for cell_index in cell_indices] + + # Recover the missing cells and proofs + recovered_cells, recovered_proofs = spec.recover_cells_and_kzg_proofs(cell_indices, known_cells) + recovered_data = [x for xs in recovered_cells for x in xs] + + # Check that the original data match the non-extended portion of the recovered data + blob_byte_array = [b for b in blob] + assert blob_byte_array == recovered_data[: len(recovered_data) // 2] + + # Check that the recovered cells/proofs match the original cells/proofs + assert cells == recovered_cells + assert proofs == recovered_proofs + + +@with_fulu_and_later +@spec_test +@single_phase +def test_multiply_polynomial_degree_overflow(spec): + rng = random.Random(5566) + + # Perform a legitimate-but-maxed-out polynomial multiplication + poly1_coeff = [ + spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + poly2_coeff = [ + spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + _ = spec.multiply_polynomialcoeff(poly1_coeff, poly2_coeff) + + # Now overflow the degree by pumping the degree of one of the inputs by one + poly2_coeff = [ + spec.BLSFieldElement(rng.randint(0, BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB + 1) + ] + expect_assertion_error(lambda: spec.multiply_polynomialcoeff(poly1_coeff, poly2_coeff)) diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py new file mode 100644 index 0000000000..27663df781 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_config_invariants.py @@ -0,0 +1,43 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_fulu_and_later, + with_presets, +) +from eth2spec.test.helpers.constants import ( + MAINNET, +) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_invariants(spec): + assert spec.FIELD_ELEMENTS_PER_BLOB % spec.FIELD_ELEMENTS_PER_CELL == 0 + assert spec.FIELD_ELEMENTS_PER_EXT_BLOB % spec.config.NUMBER_OF_COLUMNS == 0 + assert spec.config.SAMPLES_PER_SLOT <= spec.config.NUMBER_OF_COLUMNS + assert spec.config.CUSTODY_REQUIREMENT <= spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT + assert spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT <= spec.config.NUMBER_OF_COLUMNS + assert spec.config.NUMBER_OF_COLUMNS % spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT == 0 + assert spec.config.MAX_REQUEST_DATA_COLUMN_SIDECARS == ( + spec.config.MAX_REQUEST_BLOCKS_DENEB * spec.config.NUMBER_OF_COLUMNS + ) + + +@with_fulu_and_later +@spec_test +@single_phase +def test_polynomial_commitments_sampling(spec): + assert spec.FIELD_ELEMENTS_PER_EXT_BLOB == 2 * spec.FIELD_ELEMENTS_PER_BLOB + + +@with_fulu_and_later +@spec_test +@single_phase +@with_presets([MAINNET], reason="to have fork epoch number") +def test_blob_schedule(spec): + for entry in spec.config.BLOB_SCHEDULE: + # Check that all epochs are post-Deneb + assert entry["EPOCH"] >= spec.config.DENEB_FORK_EPOCH + # Check that all blob counts are less than the limit + assert entry["MAX_BLOBS_PER_BLOCK"] <= spec.MAX_BLOB_COMMITMENTS_PER_BLOCK 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..1588451c6d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_custody.py @@ -0,0 +1,70 @@ +from eth2spec.test.context import ( + expect_assertion_error, + single_phase, + spec_test, + 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/fulu/unittests/test_networking.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py new file mode 100644 index 0000000000..569dd7e14d --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_networking.py @@ -0,0 +1,188 @@ +import random + +from eth2spec.debug.random_value import ( + get_random_ssz_object, + RandomizationMode, +) +from eth2spec.test.context import ( + single_phase, + spec_state_test, + spec_test, + with_fulu_and_later, +) +from eth2spec.test.helpers.blob import ( + get_sample_blob_tx, +) +from eth2spec.test.helpers.block import ( + sign_block, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_block_hash, +) + +# Helper functions + + +def compute_data_column_sidecar(spec, state): + rng = random.Random(5566) + opaque_tx, blobs, blob_kzg_commitments, _ = get_sample_blob_tx(spec, blob_count=2) + block = get_random_ssz_object( + rng, + spec.BeaconBlock, + max_bytes_length=2000, + max_list_length=2000, + mode=RandomizationMode, + chaos=True, + ) + block.body.blob_kzg_commitments = blob_kzg_commitments + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + signed_block = sign_block(spec, state, block, proposer_index=0) + cells_and_kzg_proofs = [spec.compute_cells_and_kzg_proofs(blob) for blob in blobs] + return spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs)[0] + + +# Tests for verify_data_column_sidecar + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar__valid(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + assert spec.verify_data_column_sidecar(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar__invalid_zero_blobs(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.column = [] + sidecar.kzg_commitments = [] + sidecar.kzg_proofs = [] + assert not spec.verify_data_column_sidecar(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar__invalid_index(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.index = 128 + assert not spec.verify_data_column_sidecar(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar__invalid_mismatch_len_column(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.column = sidecar.column[1:] + assert not spec.verify_data_column_sidecar(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar__invalid_mismatch_len_kzg_commitments(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_commitments = sidecar.kzg_commitments[1:] + assert not spec.verify_data_column_sidecar(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecars__invalid_mismatch_len_kzg_proofs(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_proofs = sidecar.kzg_proofs[1:] + assert not spec.verify_data_column_sidecar(sidecar) + + +# Tests for verify_data_column_sidecar_kzg_proofs + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_kzg_proofs__valid(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + assert spec.verify_data_column_sidecar_kzg_proofs(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_column(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.column[0] = sidecar.column[1] + assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_commitment(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_commitments[0] = sidecar.kzg_commitments[1] + assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_kzg_proofs__invalid_wrong_proof(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_proofs[0] = sidecar.kzg_proofs[1] + assert not spec.verify_data_column_sidecar_kzg_proofs(sidecar) + + +# Tests for verify_data_column_sidecar_inclusion_proof + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_inclusion_proof__valid(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + assert spec.verify_data_column_sidecar_inclusion_proof(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_inclusion_proof__invalid_missing_commitment(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_commitments = sidecar.kzg_commitments[1:] + assert not spec.verify_data_column_sidecar_inclusion_proof(sidecar) + + +@with_fulu_and_later +@spec_state_test +@single_phase +def test_verify_data_column_sidecar_inclusion_proof__invalid_duplicate_commitment(spec, state): + sidecar = compute_data_column_sidecar(spec, state) + sidecar.kzg_commitments = sidecar.kzg_commitments + [sidecar.kzg_commitments[0]] + assert not spec.verify_data_column_sidecar_inclusion_proof(sidecar) + + +# Tests for compute_subnet_for_data_column_sidecar + + +@with_fulu_and_later +@spec_test +@single_phase +def test_compute_subnet_for_data_column_sidecar(spec): + subnet_results = [] + for column_index in range(spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT): + subnet_results.append(spec.compute_subnet_for_data_column_sidecar(column_index)) + # no duplicates + assert len(subnet_results) == len(set(subnet_results)) + # next one should be duplicate + next_subnet = spec.compute_subnet_for_data_column_sidecar( + spec.config.DATA_COLUMN_SIDECAR_SUBNET_COUNT + ) + assert next_subnet == subnet_results[0] diff --git a/tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py new file mode 100644 index 0000000000..69c2ce35a8 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/unittests/test_security.py @@ -0,0 +1,30 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_fulu_and_later, + with_phases, +) +from eth2spec.test.helpers.constants import ( + MAINNET, +) + + +@with_fulu_and_later +@spec_test +@single_phase +@with_phases([MAINNET]) +def test_sampling_config(spec): + probability_of_unavailable = 2 ** (-int(spec.SAMPLES_PER_SLOT)) + # TODO: What is the security requirement? + security_requirement = 0.01 + assert probability_of_unavailable <= security_requirement + + column_size_in_bytes = ( + spec.FIELD_ELEMENTS_PER_CELL + * spec.BYTES_PER_FIELD_ELEMENT + * spec.config.MAX_BLOBS_PER_BLOCK + ) + bytes_per_slot = column_size_in_bytes * spec.SAMPLES_PER_SLOT + # TODO: What is the bandwidth requirement? + bandwidth_requirement = 10000 # bytes/s + assert bytes_per_slot // spec.config.SECONDS_PER_SLOT < bandwidth_requirement diff --git a/tests/core/pyspec/eth2spec/test/fulu/validator/__init__.py b/tests/core/pyspec/eth2spec/test/fulu/validator/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/fulu/validator/test_compute_fork_digest.py b/tests/core/pyspec/eth2spec/test/fulu/validator/test_compute_fork_digest.py new file mode 100644 index 0000000000..ba20356e54 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/fulu/validator/test_compute_fork_digest.py @@ -0,0 +1,194 @@ +from eth2spec.test.context import ( + single_phase, + spec_test, + with_config_overrides, + with_fulu_and_later, +) + + +@with_fulu_and_later +@spec_test +@single_phase +@with_config_overrides( + { + "BLOB_SCHEDULE": [ + {"EPOCH": 9, "MAX_BLOBS_PER_BLOCK": 9}, + {"EPOCH": 100, "MAX_BLOBS_PER_BLOCK": 100}, + {"EPOCH": 150, "MAX_BLOBS_PER_BLOCK": 175}, + {"EPOCH": 200, "MAX_BLOBS_PER_BLOCK": 200}, + {"EPOCH": 250, "MAX_BLOBS_PER_BLOCK": 275}, + {"EPOCH": 300, "MAX_BLOBS_PER_BLOCK": 300}, + ], + }, + emit=False, +) +def test_compute_fork_digest(spec): + test_cases = [ + # Different epochs and blob limits: + { + "epoch": 9, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xab3ae6c8", + }, + { + "epoch": 10, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xab3ae6c8", + }, + { + "epoch": 11, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xab3ae6c8", + }, + { + "epoch": 99, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xab3ae6c8", + }, + { + "epoch": 100, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xdf67557b", + }, + { + "epoch": 101, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xdf67557b", + }, + { + "epoch": 150, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x8ab38b59", + }, + { + "epoch": 199, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x8ab38b59", + }, + { + "epoch": 200, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xd9b81438", + }, + { + "epoch": 201, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xd9b81438", + }, + { + "epoch": 250, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x4ef32a62", + }, + { + "epoch": 299, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x4ef32a62", + }, + { + "epoch": 300, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xca100d64", + }, + { + "epoch": 301, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0xca100d64", + }, + # Different genesis validators roots: + { + "epoch": 9, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x01" * 32, + "expected_fork_digest": "0x89671111", + }, + { + "epoch": 9, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x02" * 32, + "expected_fork_digest": "0xf49b0e24", + }, + { + "epoch": 9, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x03" * 32, + "expected_fork_digest": "0x86544e4f", + }, + { + "epoch": 100, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x01" * 32, + "expected_fork_digest": "0xfd3aa2a2", + }, + { + "epoch": 100, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x02" * 32, + "expected_fork_digest": "0x80c6bd97", + }, + { + "epoch": 100, + "fork_version": "0x06000000", + "genesis_validators_root": b"\x03" * 32, + "expected_fork_digest": "0xf209fdfc", + }, + # Different fork versions + { + "epoch": 9, + "fork_version": "0x06000001", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x30f8c25b", + }, + { + "epoch": 9, + "fork_version": "0x07000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x0432f5a9", + }, + { + "epoch": 9, + "fork_version": "0x07000001", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x6e69a671", + }, + { + "epoch": 100, + "fork_version": "0x06000001", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x44a571e8", + }, + { + "epoch": 100, + "fork_version": "0x07000000", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x706f461a", + }, + { + "epoch": 100, + "fork_version": "0x07000001", + "genesis_validators_root": b"\x00" * 32, + "expected_fork_digest": "0x1a3415c2", + }, + ] + + for case in test_cases: + # Override function to return fork version in test case + spec.compute_fork_version = lambda _: case["fork_version"] + # Compute the fork digest given the inputs from the test case + fork_digest = spec.compute_fork_digest(case["genesis_validators_root"], case["epoch"]) + # Check that the computed fork digest matches our expected value + assert f"0x{fork_digest.hex()}" == case["expected_fork_digest"] diff --git a/tests/core/pyspec/eth2spec/test/helpers/altair/fork.py b/tests/core/pyspec/eth2spec/test/helpers/altair/fork.py index 78f37c50e0..befbb5ec1a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/altair/fork.py +++ b/tests/core/pyspec/eth2spec/test/helpers/altair/fork.py @@ -1,5 +1,5 @@ ALTAIR_FORK_TEST_META_TAGS = { - 'fork': 'altair', + "fork": "altair", } @@ -7,31 +7,42 @@ def run_fork_test(post_spec, pre_state): # Clean up state to be more realistic pre_state.current_epoch_attestations = [] - yield 'pre', pre_state + yield "pre", pre_state post_state = post_spec.upgrade_to_altair(pre_state) # Stable fields stable_fields = [ - 'genesis_time', 'genesis_validators_root', 'slot', + "genesis_time", + "genesis_validators_root", + "slot", # History - 'latest_block_header', 'block_roots', 'state_roots', 'historical_roots', + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", # Eth1 - 'eth1_data', 'eth1_data_votes', 'eth1_deposit_index', + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", # Registry - 'validators', 'balances', + "validators", + "balances", # Randomness - 'randao_mixes', + "randao_mixes", # Slashings - 'slashings', + "slashings", # Finality - 'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint', + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", ] for field in stable_fields: assert getattr(pre_state, field) == getattr(post_state, field) # Modified fields - modified_fields = ['fork'] + modified_fields = ["fork"] for field in modified_fields: assert getattr(pre_state, field) != getattr(post_state, field) @@ -39,4 +50,4 @@ def run_fork_test(post_spec, pre_state): assert post_state.fork.current_version == post_spec.config.ALTAIR_FORK_VERSION assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) - yield 'post', post_state + yield "post", post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/attestations.py b/tests/core/pyspec/eth2spec/test/helpers/attestations.py index ffd484ecdf..5a2d73aecc 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attestations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attestations.py @@ -1,11 +1,15 @@ from lru import LRU -from typing import List - -from eth2spec.test.context import expect_assertion_error, is_post_altair -from eth2spec.test.helpers.state import state_transition_and_sign_block, next_epoch, next_slot +from eth2spec.test.context import expect_assertion_error from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.forks import is_post_altair, is_post_deneb, is_post_electra from eth2spec.test.helpers.keys import privkeys +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, + payload_state_transition_no_store, + state_transition_and_sign_block, +) from eth2spec.utils import bls from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -19,14 +23,14 @@ def run_attestation_processing(spec, state, attestation, valid=True): If ``valid == False``, run expecting ``AssertionError`` """ # yield pre-state - yield 'pre', state + yield "pre", state - yield 'attestation', attestation + yield "attestation", attestation # If the attestation is invalid, processing is aborted, and there is no post-state. if not valid: expect_assertion_error(lambda: spec.process_attestation(state, attestation)) - yield 'post', None + yield "post", None return if not is_post_altair(spec): @@ -47,22 +51,24 @@ def run_attestation_processing(spec, state, attestation, valid=True): pass # yield post-state - yield 'post', state + yield "post", state -def build_attestation_data(spec, state, slot, index, shard=None): +def build_attestation_data(spec, state, slot, index, beacon_block_root=None, shard=None): assert state.slot >= slot - if slot == state.slot: - block_root = build_empty_block_for_next_slot(spec, state).parent_root + if beacon_block_root is not None: + pass + elif slot == state.slot: + beacon_block_root = build_empty_block_for_next_slot(spec, state).parent_root else: - block_root = spec.get_block_root_at_slot(state, slot) + beacon_block_root = spec.get_block_root_at_slot(state, slot) current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) if slot < current_epoch_start_slot: epoch_boundary_root = spec.get_block_root(state, spec.get_previous_epoch(state)) elif slot == current_epoch_start_slot: - epoch_boundary_root = block_root + epoch_boundary_root = beacon_block_root else: epoch_boundary_root = spec.get_block_root(state, spec.get_current_epoch(state)) @@ -75,52 +81,55 @@ def build_attestation_data(spec, state, slot, index, shard=None): data = spec.AttestationData( slot=slot, - index=index, - beacon_block_root=block_root, + index=0 if is_post_electra(spec) else index, + beacon_block_root=beacon_block_root, source=spec.Checkpoint(epoch=source_epoch, root=source_root), target=spec.Checkpoint(epoch=spec.compute_epoch_at_slot(slot), root=epoch_boundary_root), ) - # if spec.fork == SHARDING # TODO: add extra data for shard voting return data -def get_valid_attestation(spec, - state, - slot=None, - index=None, - filter_participant_set=None, - signed=False): - # If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. - # Thus strictly speaking invalid when no participant is added later. +def get_valid_attestation( + spec, + state, + slot=None, + index=None, + filter_participant_set=None, + beacon_block_root=None, + signed=False, +): + """ + Return a valid attestation at `slot` and committee index `index`. + + If filter_participant_set filters everything, the attestation has 0 participants, and cannot be signed. + Thus strictly speaking invalid when no participant is added later. + """ if slot is None: slot = state.slot if index is None: index = 0 attestation_data = build_attestation_data( - spec, state, slot=slot, index=index + spec, state, slot=slot, index=index, beacon_block_root=beacon_block_root ) - beacon_committee = spec.get_beacon_committee( - state, - attestation_data.slot, - attestation_data.index, - ) + attestation = spec.Attestation(data=attestation_data) - committee_size = len(beacon_committee) - aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE](*([0] * committee_size)) - attestation = spec.Attestation( - aggregation_bits=aggregation_bits, - data=attestation_data, - ) # 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) + fill_aggregate_attestation( + spec, + state, + attestation, + signed=signed, + filter_participant_set=filter_participant_set, + committee_index=index, + ) return attestation -def sign_aggregate_attestation(spec, state, attestation_data, participants: List[int]): +def sign_aggregate_attestation(spec, state, attestation_data, participants: list[int]): signatures = [] for validator_index in participants: privkey = privkeys[validator_index] @@ -129,7 +138,7 @@ def sign_aggregate_attestation(spec, state, attestation_data, participants: List spec, state, attestation_data, - privkey + privkey, ) ) return bls.Aggregate(signatures) @@ -142,11 +151,7 @@ def sign_indexed_attestation(spec, state, indexed_attestation): def sign_attestation(spec, state, attestation): - participants = spec.get_attesting_indices( - state, - attestation.data, - attestation.aggregation_bits, - ) + participants = spec.get_attesting_indices(state, attestation) attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants) @@ -157,23 +162,56 @@ def get_attestation_signature(spec, state, attestation_data, privkey): return bls.Sign(privkey, signing_root) -def fill_aggregate_attestation(spec, state, attestation, signed=False, filter_participant_set=None): +def compute_max_inclusion_slot(spec, attestation): + if is_post_deneb(spec): + next_epoch = spec.compute_epoch_at_slot(attestation.data.slot) + 1 + end_of_next_epoch = spec.compute_start_slot_at_epoch(next_epoch + 1) - 1 + return end_of_next_epoch + return attestation.data.slot + spec.SLOTS_PER_EPOCH + + +def fill_aggregate_attestation( + spec, state, attestation, committee_index, signed=False, filter_participant_set=None +): """ - `signed`: Signing is optional. - `filter_participant_set`: Optional, filters the full committee indices set (default) to a subset that participates + `signed`: Signing is optional. + `filter_participant_set`: Optional, filters the full committee indices set (default) to a subset that participates """ beacon_committee = spec.get_beacon_committee( state, attestation.data.slot, - attestation.data.index, + committee_index, ) # By default, have everyone participate participants = set(beacon_committee) # But optionally filter the participants to a smaller amount if filter_participant_set is not None: participants = filter_participant_set(participants) + + # initialize `aggregation_bits` + if is_post_electra(spec): + attestation.committee_bits[committee_index] = True + attestation.aggregation_bits = get_empty_eip7549_aggregation_bits( + spec, state, attestation.committee_bits, attestation.data.slot + ) + else: + committee_size = len(beacon_committee) + attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]( + *([0] * committee_size) + ) + + # fill in the `aggregation_bits` for i in range(len(beacon_committee)): - attestation.aggregation_bits[i] = beacon_committee[i] in participants + if is_post_electra(spec): + offset = get_eip7549_aggregation_bits_offset( + spec, state, attestation.data.slot, attestation.committee_bits, committee_index + ) + aggregation_bits_index = offset + i + attestation.aggregation_bits[aggregation_bits_index] = ( + beacon_committee[i] in participants + ) + else: + attestation.aggregation_bits[i] = beacon_committee[i] in participants if signed and len(participants) > 0: sign_attestation(spec, state, attestation) @@ -186,31 +224,60 @@ def add_attestations_to_state(spec, state, attestations, slot): spec.process_attestation(state, attestation) -def _get_valid_attestation_at_slot(state, spec, slot_to_attest, participation_fn=None): - committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot_to_attest)) +def get_valid_attestations_at_slot( + state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None +): + """ + Return attestations at slot `slot_to_attest`. + """ + committees_per_slot = spec.get_committee_count_per_slot( + state, spec.compute_epoch_at_slot(slot_to_attest) + ) for index in range(committees_per_slot): + def participants_filter(comm): if participation_fn is None: return comm else: return participation_fn(state.slot, index, comm) - # if spec.fork == SHARDING: TODO: add shard data to attestation, include shard headers in block + yield get_valid_attestation( spec, state, slot_to_attest, index=index, signed=True, - filter_participant_set=participants_filter + filter_participant_set=participants_filter, + beacon_block_root=beacon_block_root, ) -def next_slots_with_attestations(spec, - state, - slot_count, - fill_cur_epoch, - fill_prev_epoch, - participation_fn=None): +def get_valid_attestation_at_slot( + state, spec, slot_to_attest, participation_fn=None, beacon_block_root=None +): + """ + Return the aggregate attestation post Electra. + Note: this EIP supports dense packing of on-chain aggregates so we can just return a single `Attestation`. + """ + assert is_post_electra(spec) + attestations = list( + get_valid_attestations_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn, + beacon_block_root=beacon_block_root, + ) + ) + if not attestations: + raise Exception("No valid attestations found") + + return spec.compute_on_chain_aggregate(attestations) + + +def next_slots_with_attestations( + spec, state, slot_count, fill_cur_epoch, fill_prev_epoch, participation_fn=None +): """ participation_fn: (slot, committee_index, committee_indices_set) -> participants_indices_set """ @@ -225,15 +292,34 @@ def next_slots_with_attestations(spec, participation_fn, ) signed_blocks.append(signed_block) + payload_state_transition_no_store(spec, post_state, signed_block.message) return state, signed_blocks, post_state -def next_epoch_with_attestations(spec, - state, - fill_cur_epoch, - fill_prev_epoch, - participation_fn=None): +def _add_valid_attestations(spec, state, block, slot_to_attest, participation_fn=None): + if is_post_electra(spec): + attestation = get_valid_attestation_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn, + ) + block.body.attestations.append(attestation) + else: + attestations = get_valid_attestations_at_slot( + state, + spec, + slot_to_attest, + participation_fn=participation_fn, + ) + for attestation in attestations: + block.body.attestations.append(attestation) + + +def next_epoch_with_attestations( + spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None +): assert state.slot % spec.SLOTS_PER_EPOCH == 0 return next_slots_with_attestations( @@ -246,32 +332,33 @@ def next_epoch_with_attestations(spec, ) -def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None): +def state_transition_with_full_block( + spec, + state, + fill_cur_epoch, + fill_prev_epoch, + participation_fn=None, + sync_aggregate=None, + block=None, +): """ - Build and apply a block with attestions at the calculated `slot_to_attest` of current epoch and/or previous epoch. + Build and apply a block with attestations at the calculated `slot_to_attest` of current epoch and/or previous epoch. """ - block = build_empty_block_for_next_slot(spec, state) + if block is None: + block = build_empty_block_for_next_slot(spec, state) if fill_cur_epoch and state.slot >= spec.MIN_ATTESTATION_INCLUSION_DELAY: slot_to_attest = state.slot - spec.MIN_ATTESTATION_INCLUSION_DELAY + 1 if slot_to_attest >= spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)): - attestations = _get_valid_attestation_at_slot( - state, - spec, - slot_to_attest, - participation_fn=participation_fn + _add_valid_attestations( + spec, state, block, slot_to_attest, participation_fn=participation_fn ) - for attestation in attestations: - block.body.attestations.append(attestation) - if fill_prev_epoch: + if fill_prev_epoch and state.slot >= spec.SLOTS_PER_EPOCH: slot_to_attest = state.slot - spec.SLOTS_PER_EPOCH + 1 - attestations = _get_valid_attestation_at_slot( - state, - spec, - slot_to_attest, - participation_fn=participation_fn + _add_valid_attestations( + spec, state, block, slot_to_attest, participation_fn=participation_fn ) - for attestation in attestations: - block.body.attestations.append(attestation) + if sync_aggregate is not None: + block.body.sync_aggregate = sync_aggregate signed_block = state_transition_and_sign_block(spec, state, block) return signed_block @@ -279,7 +366,7 @@ def state_transition_with_full_block(spec, state, fill_cur_epoch, fill_prev_epoc def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, fill_prev_epoch): """ - Build and apply a block with attestions at all valid slots of current epoch and/or previous epoch. + Build and apply a block with attestations at all valid slots of current epoch and/or previous epoch. """ # Build a block with previous attestations block = build_empty_block_for_next_slot(spec, state) @@ -290,7 +377,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f slots = state.slot % spec.SLOTS_PER_EPOCH for slot_offset in range(slots): target_slot = state.slot - slot_offset - attestations += _get_valid_attestation_at_slot( + attestations += get_valid_attestations_at_slot( state, spec, target_slot, @@ -301,7 +388,7 @@ def state_transition_with_full_attestations_block(spec, state, fill_cur_epoch, f slots = spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH for slot_offset in range(1, slots): target_slot = state.slot - (state.slot % spec.SLOTS_PER_EPOCH) - slot_offset - attestations += _get_valid_attestation_at_slot( + attestations += get_valid_attestations_at_slot( state, spec, target_slot, @@ -329,14 +416,23 @@ def prepare_state_with_attestations(spec, state, participation_fn=None): for _ in range(spec.SLOTS_PER_EPOCH + spec.MIN_ATTESTATION_INCLUSION_DELAY): # create an attestation for each index in each slot in epoch if state.slot < next_epoch_start_slot: - for committee_index in range(spec.get_committee_count_per_slot(state, spec.get_current_epoch(state))): + for committee_index in range( + spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) + ): + def temp_participants_filter(comm): if participation_fn is None: return comm else: return participation_fn(state.slot, committee_index, comm) - attestation = get_valid_attestation(spec, state, index=committee_index, - filter_participant_set=temp_participants_filter, signed=True) + + attestation = get_valid_attestation( + spec, + state, + index=committee_index, + filter_participant_set=temp_participants_filter, + signed=True, + ) if any(attestation.aggregation_bits): # Only if there is at least 1 participant. attestations.append(attestation) # fill each created slot in state after inclusion delay @@ -365,10 +461,45 @@ def cached_prepare_state_with_attestations(spec, state): # prepare it with attestations, and put it in the LRU. # The input state is likely already cached, so the hash-tree-root does not affect speed. key = (spec.fork, state.hash_tree_root()) - global _prep_state_cache_dict if key not in _prep_state_cache_dict: prepare_state_with_attestations(spec, state) - _prep_state_cache_dict[key] = state.get_backing() # cache the tree structure, not the view wrapping it. + _prep_state_cache_dict[key] = ( + state.get_backing() + ) # cache the tree structure, not the view wrapping it. # Put the LRU cache result into the state view, as if we transitioned the original view state.set_backing(_prep_state_cache_dict[key]) + + +def get_max_attestations(spec): + if is_post_electra(spec): + return spec.MAX_ATTESTATIONS_ELECTRA + else: + return spec.MAX_ATTESTATIONS + + +def get_empty_eip7549_aggregation_bits(spec, state, committee_bits, slot): + committee_indices = spec.get_committee_indices(committee_bits) + participants_count = 0 + for index in committee_indices: + committee = spec.get_beacon_committee(state, slot, index) + participants_count += len(committee) + aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE * spec.MAX_COMMITTEES_PER_SLOT]( + [False] * participants_count + ) + return aggregation_bits + + +def get_eip7549_aggregation_bits_offset(spec, state, slot, committee_bits, committee_index): + """ + Calculate the offset for the aggregation bits based on the committee index. + """ + committee_indices = spec.get_committee_indices(committee_bits) + assert committee_index in committee_indices + offset = 0 + for i in committee_indices: + if committee_index == i: + break + committee = spec.get_beacon_committee(state, slot, committee_indices[i]) + offset += len(committee) + return offset diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index 06e904805c..9c2568a4d1 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -1,14 +1,20 @@ -from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, + sign_attestation, + sign_indexed_attestation, +) +from eth2spec.test.helpers.forks import is_post_electra -def get_valid_attester_slashing(spec, state, slot=None, signed_1=False, signed_2=False, filter_participant_set=None): +def get_valid_attester_slashing( + spec, state, slot=None, signed_1=False, signed_2=False, filter_participant_set=None +): attestation_1 = get_valid_attestation( - spec, state, - slot=slot, signed=signed_1, filter_participant_set=filter_participant_set + spec, state, slot=slot, signed=signed_1, filter_participant_set=filter_participant_set ) attestation_2 = attestation_1.copy() - attestation_2.data.target.root = b'\x01' * 32 + attestation_2.data.target.root = b"\x01" * 32 if signed_2: sign_attestation(spec, state, attestation_2) @@ -19,10 +25,9 @@ def get_valid_attester_slashing(spec, state, slot=None, signed_1=False, signed_2 ) -def get_valid_attester_slashing_by_indices(spec, state, - indices_1, indices_2=None, - slot=None, - signed_1=False, signed_2=False): +def get_valid_attester_slashing_by_indices( + spec, state, indices_1, indices_2=None, slot=None, signed_1=False, signed_2=False +): if indices_2 is None: indices_2 = indices_1 @@ -62,3 +67,10 @@ def get_attestation_1_data(spec, att_slashing): def get_attestation_2_data(spec, att_slashing): return att_slashing.attestation_2.data + + +def get_max_attester_slashings(spec): + if is_post_electra(spec): + return spec.MAX_ATTESTER_SLASHINGS_ELECTRA + else: + return spec.MAX_ATTESTER_SLASHINGS diff --git a/tests/core/pyspec/eth2spec/test/helpers/bellatrix/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py new file mode 100644 index 0000000000..35ed1e4280 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/bellatrix/fork.py @@ -0,0 +1,59 @@ +BELLATRIX_FORK_TEST_META_TAGS = { + "fork": "bellatrix", +} + + +def run_fork_test(post_spec, pre_state): + yield "pre", pre_state + + post_state = post_spec.upgrade_to_bellatrix(pre_state) + + # Stable fields + stable_fields = [ + "genesis_time", + "genesis_validators_root", + "slot", + # History + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", + # Eth1 + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", + # Registry + "validators", + "balances", + # Randomness + "randao_mixes", + # Slashings + "slashings", + # Participation + "previous_epoch_participation", + "current_epoch_participation", + # Finality + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", + # Inactivity + "inactivity_scores", + # Sync + "current_sync_committee", + "next_sync_committee", + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ["fork"] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.BELLATRIX_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + assert post_state.latest_execution_payload_header == post_spec.ExecutionPayloadHeader() + + yield "post", post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/blob.py b/tests/core/pyspec/eth2spec/test/helpers/blob.py new file mode 100644 index 0000000000..910db36e90 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/blob.py @@ -0,0 +1,162 @@ +import random +from functools import cache +from random import Random + +from rlp import encode, Serializable +from rlp.sedes import big_endian_int, Binary, binary, CountableList, List as RLPList + +from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.execution_payload import compute_el_block_hash +from eth2spec.test.helpers.forks import ( + is_post_electra, + is_post_fulu, +) +from eth2spec.test.helpers.state import state_transition_and_sign_block + + +class Eip4844RlpTransaction(Serializable): + fields = ( + ("chain_id", big_endian_int), + ("nonce", big_endian_int), + ("max_priority_fee_per_gas", big_endian_int), + ("max_fee_per_gas", big_endian_int), + ("gas_limit", big_endian_int), + ("to", Binary(20, 20)), + ("value", big_endian_int), + ("data", binary), + ( + "access_list", + CountableList( + RLPList( + [ + Binary(20, 20), + CountableList(Binary(32, 32)), + ] + ) + ), + ), + ("max_fee_per_blob_gas", big_endian_int), + ("blob_versioned_hashes", CountableList(Binary(32, 32))), + ("signature_y_parity", big_endian_int), + ("signature_r", big_endian_int), + ("signature_s", big_endian_int), + ) + + +def get_sample_blob(spec, rng=random.Random(5566), is_valid_blob=True): + values = [ + rng.randint(0, spec.BLS_MODULUS - 1) if is_valid_blob else spec.BLS_MODULUS + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + + b = b"" + for v in values: + b += v.to_bytes(32, spec.KZG_ENDIANNESS) + + return spec.Blob(b) + + +def eval_poly_in_coeff_form(spec, coeffs, x): + """ + Evaluate a polynomial in coefficient form at 'x' using Horner's rule + """ + total = spec.BLSFieldElement(0) + for a in reversed(coeffs): + total = total * x + a + return total + + +def get_poly_in_both_forms(spec, rng=None): + """ + Generate and return a random polynomial in both coefficient form and evaluation form + """ + if rng is None: + rng = random.Random(5566) + + roots_of_unity_brp = spec.bit_reversal_permutation( + spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB) + ) + coeffs = [ + spec.BLSFieldElement(rng.randint(0, spec.BLS_MODULUS - 1)) + for _ in range(spec.FIELD_ELEMENTS_PER_BLOB) + ] + evals = [eval_poly_in_coeff_form(spec, coeffs, z) for z in roots_of_unity_brp] + + return coeffs, evals + + +def get_sample_blob_tx(spec, blob_count=1, rng=random.Random(5566), is_valid_blob=True): + blobs = [] + blob_kzg_commitments = [] + blob_kzg_proofs = [] + blob_versioned_hashes = [] + for _ in range(blob_count): + blob = get_sample_blob(spec, rng, is_valid_blob=is_valid_blob) + if is_valid_blob: + blob_commitment = spec.KZGCommitment(spec.blob_to_kzg_commitment(blob)) + blob_kzg_proof = spec.compute_blob_kzg_proof(blob, blob_commitment) + else: + blob_commitment = spec.KZGCommitment() + blob_kzg_proof = spec.KZGProof() + blob_versioned_hash = spec.kzg_commitment_to_versioned_hash(blob_commitment) + blobs.append(blob) + blob_kzg_commitments.append(blob_commitment) + blob_kzg_proofs.append(blob_kzg_proof) + blob_versioned_hashes.append(blob_versioned_hash) + + signed_blob_tx = Eip4844RlpTransaction( + chain_id=0, + nonce=0, + max_priority_fee_per_gas=0, + max_fee_per_gas=0, + gas_limit=0, + to=bytes.fromhex("0000000000000000000000000000000000000000"), + value=0, + data=bytes.fromhex(""), + access_list=[], + max_fee_per_blob_gas=0, + blob_versioned_hashes=[bytes(h) for h in blob_versioned_hashes], + signature_y_parity=0, + signature_r=0, + signature_s=0, + ) + opaque_tx = bytes([0x03]) + encode(signed_blob_tx) + return opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs + + +def get_max_blob_count(spec, state): + if is_post_fulu(spec): + return spec.get_blob_parameters(spec.get_current_epoch(state)).max_blobs_per_block + elif is_post_electra(spec): + return spec.config.MAX_BLOBS_PER_BLOCK_ELECTRA + else: + return spec.config.MAX_BLOBS_PER_BLOCK + + +def get_block_with_blob(spec, state, rng: Random | None = None, blob_count=1): + block = build_empty_block_for_next_slot(spec, state) + opaque_tx, blobs, blob_kzg_commitments, blob_kzg_proofs = get_sample_blob_tx( + spec, blob_count=blob_count, rng=rng or random.Random(5566) + ) + block.body.execution_payload.transactions = [opaque_tx] + block.body.execution_payload.block_hash = compute_el_block_hash( + spec, block.body.execution_payload, state + ) + block.body.blob_kzg_commitments = blob_kzg_commitments + return block, blobs, blob_kzg_proofs + + +def get_block_with_blob_and_sidecars(spec, state, rng=None, blob_count=1): + block, blobs, blob_kzg_proofs = get_block_with_blob(spec, state, rng=rng, blob_count=blob_count) + cells_and_kzg_proofs = [_cached_compute_cells_and_kzg_proofs(spec, blob) for blob in blobs] + + # We need a signed block to call `get_data_column_sidecars_from_block` + signed_block = state_transition_and_sign_block(spec, state, block) + + sidecars = spec.get_data_column_sidecars_from_block(signed_block, cells_and_kzg_proofs) + return block, blobs, blob_kzg_proofs, signed_block, sidecars + + +@cache +def _cached_compute_cells_and_kzg_proofs(spec, blob): + return spec.compute_cells_and_kzg_proofs(blob) diff --git a/tests/core/pyspec/eth2spec/test/helpers/block.py b/tests/core/pyspec/eth2spec/test/helpers/block.py index b8f7c4bcb3..b7e355c1aa 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block.py @@ -1,10 +1,39 @@ -from eth2spec.test.context import is_post_altair, is_post_merge -from eth2spec.test.helpers.execution_payload import build_empty_execution_payload -from eth2spec.test.helpers.keys import privkeys +from curdleproofs import ( + GenerateWhiskShuffleProof, + GenerateWhiskTrackerProof, + WhiskTracker, +) +from py_arkworks_bls12381 import Scalar +from py_ecc.bls.g2_primitives import ( + G1_to_pubkey as py_ecc_G1_to_bytes48, + pubkey_to_G1 as py_ecc_bytes48_to_G1, +) +from py_ecc.optimized_bls12_381.optimized_curve import G1, multiply +from py_ecc.typing import Optimized_Field, Optimized_Point3D + +from eth2spec.test.helpers.eip7441 import ( + compute_whisk_tracker_and_commitment, + is_first_proposal, + resolve_known_tracker, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_empty_signed_execution_payload_header, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_bellatrix, + is_post_eip7441, + is_post_eip7732, + is_post_electra, +) +from eth2spec.test.helpers.keys import privkeys, whisk_ks_final, whisk_ks_initial from eth2spec.utils import bls from eth2spec.utils.bls import only_with_bls from eth2spec.utils.ssz.ssz_impl import hash_tree_root +PointProjective = Optimized_Point3D[Optimized_Field] + def get_proposer_index_maybe(spec, state, slot, proposer_index=None): if proposer_index is None: @@ -12,9 +41,6 @@ def get_proposer_index_maybe(spec, state, slot, proposer_index=None): if slot == state.slot: proposer_index = spec.get_beacon_proposer_index(state) else: - if spec.compute_epoch_at_slot(state.slot) + 1 > spec.compute_epoch_at_slot(slot): - print("warning: block slot far away, and no proposer index manually given." - " Signing block is slow due to transition for proposer index calculation.") # use stub state to get proposer index of future slot stub_state = state.copy() if stub_state.slot < slot: @@ -24,10 +50,9 @@ def get_proposer_index_maybe(spec, state, slot, proposer_index=None): @only_with_bls() -def apply_randao_reveal(spec, state, block, proposer_index=None): +def apply_randao_reveal(spec, state, block, proposer_index): assert state.slot <= block.slot - proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index) privkey = privkeys[proposer_index] domain = spec.get_domain(state, spec.DOMAIN_RANDAO, spec.compute_epoch_at_slot(block.slot)) @@ -42,7 +67,9 @@ def apply_sig(spec, state, signed_block, proposer_index=None): proposer_index = get_proposer_index_maybe(spec, state, block.slot, proposer_index) privkey = privkeys[proposer_index] - domain = spec.get_domain(state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot)) + domain = spec.get_domain( + state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot) + ) signing_root = spec.compute_signing_root(block, domain) signed_block.signature = bls.Sign(privkey, signing_root) @@ -55,10 +82,14 @@ def sign_block(spec, state, block, proposer_index=None): def transition_unsigned_block(spec, state, block): - assert state.slot < block.slot # Preserve assertion from state transition to avoid strange pre-states from testing + assert ( + state.slot < block.slot + ) # Preserve assertion from state transition to avoid strange pre-states from testing if state.slot < block.slot: spec.process_slots(state, block.slot) - assert state.latest_block_header.slot < block.slot # There may not already be a block in this slot or past it. + assert ( + state.latest_block_header.slot < block.slot + ) # There may not already be a block in this slot or past it. assert state.slot == block.slot # The block must be for this slot spec.process_block(state, block) return block @@ -72,7 +103,7 @@ def apply_empty_block(spec, state, slot=None): return transition_unsigned_block(spec, state, block) -def build_empty_block(spec, state, slot=None): +def build_empty_block(spec, state, slot=None, proposer_index=None): """ Build empty block for ``slot``, built upon the latest block header seen by ``state``. Slot must be greater than or equal to the current slot in ``state``. @@ -87,26 +118,132 @@ def build_empty_block(spec, state, slot=None): spec.process_slots(state, slot) state, parent_block_root = get_state_and_beacon_parent_root_at_slot(spec, state, slot) + proposer_index = get_beacon_proposer_to_build(spec, state, proposer_index) empty_block = spec.BeaconBlock() empty_block.slot = slot - empty_block.proposer_index = spec.get_beacon_proposer_index(state) + empty_block.proposer_index = proposer_index empty_block.body.eth1_data.deposit_count = state.eth1_deposit_index empty_block.parent_root = parent_block_root - apply_randao_reveal(spec, state, empty_block) + apply_randao_reveal(spec, state, empty_block, proposer_index) if is_post_altair(spec): empty_block.body.sync_aggregate.sync_committee_signature = spec.G2_POINT_AT_INFINITY - if is_post_merge(spec): - randao_mix = spec.compute_randao_mix(state, empty_block.body.randao_reveal) - empty_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix) + if is_post_eip7732(spec): + signed_header = build_empty_signed_execution_payload_header(spec, state) + empty_block.body.signed_execution_payload_header = signed_header + return empty_block + + if is_post_bellatrix(spec): + empty_block.body.execution_payload = build_empty_execution_payload(spec, state) + + if is_post_electra(spec): + empty_block.body.execution_requests.deposits = [] + empty_block.body.execution_requests.withdrawals = [] + empty_block.body.execution_requests.consolidations = [] + + if is_post_eip7441(spec): + # Whisk opening proof + ####### + + # Create valid whisk opening proof + # TODO: Use k_initial or k_final to handle first and subsequent proposals + k_initial = whisk_ks_initial(proposer_index) + + # Sanity check proposer is correct + proposer_k_commitment = state.whisk_k_commitments[proposer_index] + k_commitment = py_ecc_G1_to_bytes48(multiply(G1, int(k_initial))) + if proposer_k_commitment != k_commitment: + raise Exception( + "k proposer_index not eq proposer_k_commitment", proposer_k_commitment, k_commitment + ) + + proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.PROPOSER_TRACKERS_COUNT] + if not is_whisk_proposer(proposer_tracker, k_initial): + raise Exception("k proposer_index does not match proposer_tracker") + + empty_block.body.whisk_opening_proof = GenerateWhiskTrackerProof( + proposer_tracker, Scalar(k_initial) + ) + + # Whisk shuffle proof + ####### + + shuffle_indices = spec.get_shuffle_indices(empty_block.body.randao_reveal) + pre_shuffle_trackers = [state.whisk_candidate_trackers[i] for i in shuffle_indices] + + post_trackers, shuffle_proof = GenerateWhiskShuffleProof( + spec.CURDLEPROOFS_CRS, pre_shuffle_trackers + ) + empty_block.body.whisk_post_shuffle_trackers = post_trackers + empty_block.body.whisk_shuffle_proof = shuffle_proof + + # Whisk registration proof + ####### + + # Branching logic depending if first proposal or not + if is_first_proposal(spec, state, proposer_index): + # Register new tracker + k_final = whisk_ks_final(proposer_index) + # TODO: Actual logic should pick a random r, but may need to do something fancy to locate trackers quickly + r = 2 + tracker, k_commitment = compute_whisk_tracker_and_commitment(k_final, r) + empty_block.body.whisk_registration_proof = GenerateWhiskTrackerProof( + tracker, Scalar(k_final) + ) + empty_block.body.whisk_tracker = tracker + empty_block.body.whisk_k_commitment = k_commitment + + else: + # Subsequent proposals, just leave empty + empty_block.body.whisk_registration_proof = spec.WhiskTrackerProof() + empty_block.body.whisk_tracker = spec.WhiskTracker() + empty_block.body.whisk_k_commitment = spec.BLSG1Point() return empty_block -def build_empty_block_for_next_slot(spec, state): - return build_empty_block(spec, state, state.slot + 1) +def is_whisk_proposer(tracker: WhiskTracker, k: int) -> bool: + return py_ecc_G1_to_bytes48(multiply(py_ecc_bytes48_to_G1(tracker.r_G), k)) == tracker.k_r_G + + +def get_beacon_proposer_to_build(spec, state, proposer_index=None): + if is_post_eip7441(spec): + if proposer_index is None: + return find_whisk_proposer(spec, state) + else: + return proposer_index + else: + return spec.get_beacon_proposer_index(state) + + +def find_whisk_proposer(spec, state): + proposer_tracker = state.whisk_proposer_trackers[state.slot % spec.PROPOSER_TRACKERS_COUNT] + + # Check record of known trackers + # During the first shuffling phase (epoch < EPOCHS_PER_SHUFFLING_PHASE) + # proposer trackers are those inserted on the genesis state, and have not gone + # through any shuffling. We cache those initial trackers and use `resolve_known_tracker` + # to check if the tracker is known, and skip the need to actually find the matching tracker + proposer_index = resolve_known_tracker(proposer_tracker) + if proposer_index is not None: + return proposer_index + + print("proposer_tracker", proposer_tracker) + # # First attempt direct equality with trackers + # for i, validator in enumerate(state.validators): + # # # This is insanely slow + # # if validator.whisk_tracker == proposer_tracker: + # if True: + # return i + # # In Whisk where to get proposer from? + # raise Exception("proposer_tracker not matched") + raise Exception("proposer not known without heavy math") + + +def build_empty_block_for_next_slot(spec, state, proposer_index=None): + return build_empty_block(spec, state, state.slot + 1, proposer_index) def get_state_and_beacon_parent_root_at_slot(spec, state, slot): diff --git a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py index 8721a772e8..a2d2ff97da 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/block_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/block_processing.py @@ -6,34 +6,35 @@ def for_ops(state, operations, fn) -> None: def get_process_calls(spec): return { # PHASE0 - 'process_block_header': - lambda state, block: spec.process_block_header(state, block), - 'process_randao': - lambda state, block: spec.process_randao(state, block.body), - 'process_eth1_data': - lambda state, block: spec.process_eth1_data(state, block.body), - 'process_proposer_slashing': - lambda state, block: for_ops(state, block.body.proposer_slashings, spec.process_proposer_slashing), - 'process_attester_slashing': - lambda state, block: for_ops(state, block.body.attester_slashings, spec.process_attester_slashing), - 'process_shard_header': - lambda state, block: for_ops(state, block.body.shard_headers, spec.process_shard_header), - 'process_attestation': - lambda state, block: for_ops(state, block.body.attestations, spec.process_attestation), - 'process_deposit': - lambda state, block: for_ops(state, block.body.deposits, spec.process_deposit), - 'process_voluntary_exit': - lambda state, block: for_ops(state, block.body.voluntary_exits, spec.process_voluntary_exit), + "process_block_header": lambda state, block: spec.process_block_header(state, block), + "process_randao": lambda state, block: spec.process_randao(state, block.body), + "process_eth1_data": lambda state, block: spec.process_eth1_data(state, block.body), + "process_proposer_slashing": lambda state, block: for_ops( + state, block.body.proposer_slashings, spec.process_proposer_slashing + ), + "process_attester_slashing": lambda state, block: for_ops( + state, block.body.attester_slashings, spec.process_attester_slashing + ), + "process_shard_header": lambda state, block: for_ops( + state, block.body.shard_headers, spec.process_shard_header + ), + "process_attestation": lambda state, block: for_ops( + state, block.body.attestations, spec.process_attestation + ), + "process_deposit": lambda state, block: for_ops( + state, block.body.deposits, spec.process_deposit + ), + "process_voluntary_exit": lambda state, block: for_ops( + state, block.body.voluntary_exits, spec.process_voluntary_exit + ), # Altair - 'process_sync_aggregate': - lambda state, block: spec.process_sync_aggregate(state, block.body.sync_aggregate), - # Merge - 'process_application_payload': - lambda state, block: spec.process_application_payload(state, block.body), - # TODO: add sharding processing functions when spec stabilizes. - # Custody Game - 'process_custody_game_operations': - lambda state, block: spec.process_custody_game_operations(state, block.body), + "process_sync_aggregate": lambda state, block: spec.process_sync_aggregate( + state, block.body.sync_aggregate + ), + # Bellatrix + "process_application_payload": lambda state, block: spec.process_application_payload( + state, block.body + ), } diff --git a/tests/core/pyspec/eth2spec/test/helpers/bls_to_execution_changes.py b/tests/core/pyspec/eth2spec/test/helpers/bls_to_execution_changes.py new file mode 100644 index 0000000000..c2ad9f75b1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/bls_to_execution_changes.py @@ -0,0 +1,46 @@ +from eth2spec.test.helpers.keys import privkeys, pubkey_to_privkey, pubkeys +from eth2spec.utils import bls + + +def get_signed_address_change( + spec, + state, + validator_index=None, + withdrawal_pubkey=None, + to_execution_address=None, + fork_version=None, + genesis_validators_root=None, +): + if validator_index is None: + validator_index = 0 + + if withdrawal_pubkey is None: + key_index = -1 - validator_index + withdrawal_pubkey = pubkeys[key_index] + withdrawal_privkey = privkeys[key_index] + else: + withdrawal_privkey = pubkey_to_privkey[withdrawal_pubkey] + + if to_execution_address is None: + to_execution_address = b"\x42" * 20 + + if genesis_validators_root is None: + genesis_validators_root = state.genesis_validators_root + + domain = spec.compute_domain( + spec.DOMAIN_BLS_TO_EXECUTION_CHANGE, + fork_version=fork_version, + genesis_validators_root=genesis_validators_root, + ) + + address_change = spec.BLSToExecutionChange( + validator_index=validator_index, + from_bls_pubkey=withdrawal_pubkey, + to_execution_address=to_execution_address, + ) + + signing_root = spec.compute_signing_root(address_change, domain) + return spec.SignedBLSToExecutionChange( + message=address_change, + signature=bls.Sign(withdrawal_privkey, signing_root), + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/capella/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py new file mode 100644 index 0000000000..56129a083e --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/capella/fork.py @@ -0,0 +1,73 @@ +CAPELLA_FORK_TEST_META_TAGS = { + "fork": "capella", +} + + +def run_fork_test(post_spec, pre_state): + yield "pre", pre_state + + post_state = post_spec.upgrade_to_capella(pre_state) + + # Stable fields + stable_fields = [ + "genesis_time", + "genesis_validators_root", + "slot", + # History + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", + # Eth1 + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", + # Registry + "validators", + "balances", + # Randomness + "randao_mixes", + # Slashings + "slashings", + # Participation + "previous_epoch_participation", + "current_epoch_participation", + # Finality + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", + # Inactivity + "inactivity_scores", + # Sync + "current_sync_committee", + "next_sync_committee", + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ["fork", "latest_execution_payload_header"] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + "pubkey", + "withdrawal_credentials", + "effective_balance", + "slashed", + "activation_eligibility_epoch", + "activation_epoch", + "exit_epoch", + "withdrawable_epoch", + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.CAPELLA_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield "post", post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/consolidations.py b/tests/core/pyspec/eth2spec/test/helpers/consolidations.py new file mode 100644 index 0000000000..78056e184a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/consolidations.py @@ -0,0 +1,13 @@ +from eth2spec.test.helpers.withdrawals import set_eth1_withdrawal_credential_with_balance + + +def prepare_switch_to_compounding_request(spec, state, validator_index, address=None): + validator = state.validators[validator_index] + if not spec.has_execution_withdrawal_credential(validator): + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + + return spec.ConsolidationRequest( + source_address=state.validators[validator_index].withdrawal_credentials[12:], + source_pubkey=state.validators[validator_index].pubkey, + target_pubkey=state.validators[validator_index].pubkey, + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/constants.py b/tests/core/pyspec/eth2spec/test/helpers/constants.py index 5ab8473279..aa937980ee 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/constants.py +++ b/tests/core/pyspec/eth2spec/test/helpers/constants.py @@ -1,32 +1,92 @@ -from .typing import SpecForkName, PresetBaseName - +from .typing import PresetBaseName, SpecForkName # # SpecForkName # + # Some of the Spec module functionality is exposed here to deal with phase-specific changes. -PHASE0 = SpecForkName('phase0') -ALTAIR = SpecForkName('altair') -MERGE = SpecForkName('merge') +PHASE0 = SpecForkName("phase0") +ALTAIR = SpecForkName("altair") +BELLATRIX = SpecForkName("bellatrix") +CAPELLA = SpecForkName("capella") +DENEB = SpecForkName("deneb") +ELECTRA = SpecForkName("electra") # Experimental phases (not included in default "ALL_PHASES"): -SHARDING = SpecForkName('sharding') -CUSTODY_GAME = SpecForkName('custody_game') -DAS = SpecForkName('das') +FULU = SpecForkName("fulu") +EIP7441 = SpecForkName("eip7441") +EIP7732 = SpecForkName("eip7732") +EIP7805 = SpecForkName("eip7805") -# The forks that pytest runs with. -ALL_PHASES = (PHASE0, ALTAIR, MERGE) +# +# SpecFork settings +# + +# The forks that are deployed on Mainnet +MAINNET_FORKS = (PHASE0, ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA) +LATEST_FORK = MAINNET_FORKS[-1] +# The forks that pytest can run with. +# Note: when adding a new fork here, all tests from previous forks with decorator `with_X_and_later` +# will run on the new fork. To skip this behaviour, add the fork to `ALLOWED_TEST_RUNNER_FORKS` +ALL_PHASES = ( + # Formal forks + *MAINNET_FORKS, + FULU, + # Experimental patches + EIP7732, + EIP7805, +) +# The forks that have light client specs +LIGHT_CLIENT_TESTING_FORKS = [item for item in MAINNET_FORKS if item != PHASE0] # The forks that output to the test vectors. -TESTGEN_FORKS = (PHASE0, ALTAIR, MERGE) +TESTGEN_FORKS = (*MAINNET_FORKS, FULU, EIP7732, EIP7805) +# Forks allowed in the test runner `--fork` flag, to fail fast in case of typos +ALLOWED_TEST_RUNNER_FORKS = (*ALL_PHASES, EIP7441) + +# NOTE: the same definition as in `pysetup/md_doc_paths.py` +PREVIOUS_FORK_OF = { + # post_fork_name: pre_fork_name + PHASE0: None, + ALTAIR: PHASE0, + BELLATRIX: ALTAIR, + CAPELLA: BELLATRIX, + DENEB: CAPELLA, + ELECTRA: DENEB, + # Experimental patches + FULU: ELECTRA, + EIP7441: CAPELLA, + EIP7732: ELECTRA, + EIP7805: ELECTRA, +} + +# For fork transition tests +POST_FORK_OF = { + # pre_fork_name: post_fork_name + PHASE0: ALTAIR, + ALTAIR: BELLATRIX, + BELLATRIX: CAPELLA, + CAPELLA: DENEB, + DENEB: ELECTRA, + ELECTRA: FULU, +} -FORKS_BEFORE_ALTAIR = (PHASE0,) -FORKS_BEFORE_MERGE = (PHASE0, ALTAIR) +ALL_PRE_POST_FORKS = POST_FORK_OF.items() +DENEB_TRANSITION_UPGRADES_AND_AFTER = { + key: value for key, value in POST_FORK_OF.items() if key not in [PHASE0, ALTAIR, BELLATRIX] +} +ELECTRA_TRANSITION_UPGRADES_AND_AFTER = { + key: value + for key, value in POST_FORK_OF.items() + if key not in [PHASE0, ALTAIR, BELLATRIX, CAPELLA] +} +AFTER_DENEB_PRE_POST_FORKS = DENEB_TRANSITION_UPGRADES_AND_AFTER.items() +AFTER_ELECTRA_PRE_POST_FORKS = ELECTRA_TRANSITION_UPGRADES_AND_AFTER.items() # -# Config +# Config and Preset # -MAINNET = PresetBaseName('mainnet') -MINIMAL = PresetBaseName('minimal') +MAINNET = PresetBaseName("mainnet") +MINIMAL = PresetBaseName("minimal") ALL_PRESETS = (MINIMAL, MAINNET) @@ -34,4 +94,4 @@ # # Number # -MAX_UINT_64 = 2**64 - 1 +UINT64_MAX = 2**64 - 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/custody.py b/tests/core/pyspec/eth2spec/test/helpers/custody.py deleted file mode 100644 index 8e9aafa665..0000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/custody.py +++ /dev/null @@ -1,177 +0,0 @@ -from eth2spec.test.helpers.keys import privkeys -from eth2spec.test.helpers.merkle import build_proof -from eth2spec.utils import bls -from eth2spec.utils.ssz.ssz_typing import Bitlist, ByteVector, ByteList - -BYTES_PER_CHUNK = 32 - - -def get_valid_early_derived_secret_reveal(spec, state, epoch=None): - current_epoch = spec.get_current_epoch(state) - revealed_index = spec.get_active_validator_indices(state, current_epoch)[-1] - masker_index = spec.get_active_validator_indices(state, current_epoch)[0] - - if epoch is None: - epoch = current_epoch + spec.CUSTODY_PERIOD_TO_RANDAO_PADDING - - # Generate the secret that is being revealed - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch) - signing_root = spec.compute_signing_root(spec.Epoch(epoch), domain) - reveal = bls.Sign(privkeys[revealed_index], signing_root) - # Generate the mask (any random 32 bytes that don't reveal the masker's secret will do) - mask = spec.hash(reveal) - # Generate masker's signature on the mask - signing_root = spec.compute_signing_root(mask, domain) - masker_signature = bls.Sign(privkeys[masker_index], signing_root) - masked_reveal = bls.Aggregate([reveal, masker_signature]) - - return spec.EarlyDerivedSecretReveal( - revealed_index=revealed_index, - epoch=epoch, - reveal=masked_reveal, - masker_index=masker_index, - mask=mask, - ) - - -def get_valid_custody_key_reveal(spec, state, period=None, validator_index=None): - current_epoch = spec.get_current_epoch(state) - revealer_index = (spec.get_active_validator_indices(state, current_epoch)[0] - if validator_index is None else validator_index) - revealer = state.validators[revealer_index] - - if period is None: - period = revealer.next_custody_secret_to_reveal - - epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, revealer_index) - - # Generate the secret that is being revealed - domain = spec.get_domain(state, spec.DOMAIN_RANDAO, epoch_to_sign) - signing_root = spec.compute_signing_root(spec.Epoch(epoch_to_sign), domain) - reveal = bls.Sign(privkeys[revealer_index], signing_root) - return spec.CustodyKeyReveal( - revealer_index=revealer_index, - reveal=reveal, - ) - - -def bitlist_from_int(max_len, num_bits, n): - return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)]) - - -def get_valid_custody_slashing(spec, state, attestation, shard_transition, custody_secret, data, data_index=0): - beacon_committee = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index, - ) - malefactor_index = beacon_committee[0] - whistleblower_index = beacon_committee[-1] - - slashing = spec.CustodySlashing( - data_index=data_index, - malefactor_index=malefactor_index, - malefactor_secret=custody_secret, - whistleblower_index=whistleblower_index, - shard_transition=shard_transition, - attestation=attestation, - data=data, - ) - slashing_domain = spec.get_domain(state, spec.DOMAIN_CUSTODY_BIT_SLASHING) - slashing_root = spec.compute_signing_root(slashing, slashing_domain) - - signed_slashing = spec.SignedCustodySlashing( - message=slashing, - signature=bls.Sign(privkeys[whistleblower_index], slashing_root) - ) - - return signed_slashing - - -def get_valid_chunk_challenge(spec, state, attestation, shard_transition, data_index=None, chunk_index=None): - crosslink_committee = spec.get_beacon_committee( - state, - attestation.data.slot, - attestation.data.index - ) - responder_index = crosslink_committee[0] - data_index = len(shard_transition.shard_block_lengths) - 1 if not data_index else data_index - - chunk_count = (shard_transition.shard_block_lengths[data_index] - + spec.BYTES_PER_CUSTODY_CHUNK - 1) // spec.BYTES_PER_CUSTODY_CHUNK - chunk_index = chunk_count - 1 if not chunk_index else chunk_index - - return spec.CustodyChunkChallenge( - responder_index=responder_index, - attestation=attestation, - chunk_index=chunk_index, - data_index=data_index, - shard_transition=shard_transition, - ) - - -def custody_chunkify(spec, x): - chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)] - chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0") - return [ByteVector[spec.BYTES_PER_CUSTODY_CHUNK](c) for c in chunks] - - -def get_valid_custody_chunk_response(spec, state, chunk_challenge, challenge_index, - block_length_or_custody_data, - invalid_chunk_data=False): - if isinstance(block_length_or_custody_data, int): - custody_data = get_custody_test_vector(block_length_or_custody_data) - else: - custody_data = block_length_or_custody_data - - custody_data_block = ByteList[spec.MAX_SHARD_BLOCK_SIZE](custody_data) - chunks = custody_chunkify(spec, custody_data_block) - - chunk_index = chunk_challenge.chunk_index - - leaf_index = chunk_index + 2**spec.CUSTODY_RESPONSE_DEPTH - serialized_length = len(custody_data_block).to_bytes(32, 'little') - data_branch = build_proof(custody_data_block.get_backing().get_left(), leaf_index) + [serialized_length] - - return spec.CustodyChunkResponse( - challenge_index=challenge_index, - chunk_index=chunk_index, - chunk=chunks[chunk_index], - branch=data_branch, - ) - - -def get_custody_test_vector(bytelength, offset=0): - ints = bytelength // 4 + 1 - return (b"".join((i + offset).to_bytes(4, "little") for i in range(ints)))[:bytelength] - - -def get_sample_shard_transition(spec, start_slot, block_lengths): - b = [spec.hash_tree_root(ByteList[spec.MAX_SHARD_BLOCK_SIZE](get_custody_test_vector(x))) - for x in block_lengths] - shard_transition = spec.ShardTransition( - start_slot=start_slot, - shard_block_lengths=block_lengths, - shard_data_roots=b, - shard_states=[spec.ShardState() for x in block_lengths], - proposer_signature_aggregate=spec.BLSSignature(), - ) - return shard_transition - - -def get_custody_slashable_test_vector(spec, custody_secret, length, slashable=True): - test_vector = get_custody_test_vector(length) - offset = 0 - while spec.compute_custody_bit(custody_secret, test_vector) != slashable: - offset += 1 - test_vector = get_custody_test_vector(length, offset) - return test_vector - - -def get_custody_slashable_shard_transition(spec, start_slot, block_lengths, custody_secret, slashable=True): - shard_transition = get_sample_shard_transition(spec, start_slot, block_lengths) - slashable_test_vector = get_custody_slashable_test_vector(spec, custody_secret, - block_lengths[0], slashable=slashable) - block_data = ByteList[spec.MAX_SHARD_BLOCK_SIZE](slashable_test_vector) - shard_transition.shard_data_roots[0] = spec.hash_tree_root(block_data) - return shard_transition, slashable_test_vector diff --git a/tests/core/pyspec/eth2spec/test/helpers/das.py b/tests/core/pyspec/eth2spec/test/helpers/das.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/deneb/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/deneb/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/deneb/fork.py b/tests/core/pyspec/eth2spec/test/helpers/deneb/fork.py new file mode 100644 index 0000000000..43bed9ec10 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/deneb/fork.py @@ -0,0 +1,82 @@ +from eth2spec.test.helpers.constants import ( + DENEB, +) + +DENEB_FORK_TEST_META_TAGS = { + "fork": DENEB, +} + + +def run_fork_test(post_spec, pre_state): + yield "pre", pre_state + + post_state = post_spec.upgrade_to_deneb(pre_state) + + # Stable fields + stable_fields = [ + "genesis_time", + "genesis_validators_root", + "slot", + # History + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", + # Eth1 + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", + # Registry + "validators", + "balances", + # Randomness + "randao_mixes", + # Slashings + "slashings", + # Participation + "previous_epoch_participation", + "current_epoch_participation", + # Finality + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", + # Inactivity + "inactivity_scores", + # Sync + "current_sync_committee", + "next_sync_committee", + # Withdrawals + "next_withdrawal_index", + "next_withdrawal_validator_index", + # Deep history valid from Capella onwards + "historical_summaries", + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ["fork", "latest_execution_payload_header"] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + "pubkey", + "withdrawal_credentials", + "effective_balance", + "slashed", + "activation_eligibility_epoch", + "activation_epoch", + "exit_epoch", + "withdrawable_epoch", + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.DENEB_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield "post", post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 4e26215228..750724fc67 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -1,7 +1,14 @@ from random import Random -from eth2spec.test.context import is_post_altair -from eth2spec.test.helpers.keys import pubkeys, privkeys +from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.epoch_processing import ( + run_epoch_processing_from, + run_epoch_processing_to, + run_process_slots_up_to_epoch_boundary, +) +from eth2spec.test.helpers.forks import is_post_altair, is_post_electra +from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.state import get_balance from eth2spec.utils import bls from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof from eth2spec.utils.ssz.ssz_impl import hash_tree_root @@ -21,35 +28,37 @@ def mock_deposit(spec, state, index): assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) -def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=False): +def build_deposit_data( + spec, pubkey, privkey, amount, withdrawal_credentials, fork_version=None, signed=False +): deposit_data = spec.DepositData( pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, ) if signed: - sign_deposit_data(spec, deposit_data, privkey) + sign_deposit_data(spec, deposit_data, privkey, fork_version) return deposit_data -def sign_deposit_data(spec, deposit_data, privkey): +def sign_deposit_data(spec, deposit_data, privkey, fork_version=None): deposit_message = spec.DepositMessage( pubkey=deposit_data.pubkey, withdrawal_credentials=deposit_data.withdrawal_credentials, - amount=deposit_data.amount) - domain = spec.compute_domain(spec.DOMAIN_DEPOSIT) + amount=deposit_data.amount, + ) + if fork_version is not None: + domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=fork_version) + else: + domain = spec.compute_domain(spec.DOMAIN_DEPOSIT) signing_root = spec.compute_signing_root(deposit_message, domain) deposit_data.signature = bls.Sign(privkey, signing_root) -def build_deposit(spec, - deposit_data_list, - pubkey, - privkey, - amount, - withdrawal_credentials, - signed): - deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed) +def build_deposit(spec, deposit_data_list, pubkey, privkey, amount, withdrawal_credentials, signed): + deposit_data = build_deposit_data( + spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed + ) index = len(deposit_data_list) deposit_data_list.append(deposit_data) return deposit_from_context(spec, deposit_data_list, index) @@ -57,25 +66,25 @@ def build_deposit(spec, def deposit_from_context(spec, deposit_data_list, index): deposit_data = deposit_data_list[index] - root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list)) - tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) - proof = ( - list(get_merkle_proof(tree, item_index=index, tree_len=32)) - + [len(deposit_data_list).to_bytes(32, 'little')] + root = hash_tree_root( + List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list) ) + tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) + proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [ + len(deposit_data_list).to_bytes(32, "little") + ] leaf = deposit_data.hash_tree_root() - assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root) + assert spec.is_valid_merkle_branch( + leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root + ) deposit = spec.Deposit(proof=proof, data=deposit_data) return deposit, root, deposit_data_list -def prepare_full_genesis_deposits(spec, - amount, - deposit_count, - min_pubkey_index=0, - signed=False, - deposit_data_list=None): +def prepare_full_genesis_deposits( + spec, amount, deposit_count, min_pubkey_index=0, signed=False, deposit_data_list=None +): if deposit_data_list is None: deposit_data_list = [] genesis_deposits = [] @@ -98,14 +107,16 @@ def prepare_full_genesis_deposits(spec, return genesis_deposits, root, deposit_data_list -def prepare_random_genesis_deposits(spec, - deposit_count, - max_pubkey_index, - min_pubkey_index=0, - max_amount=None, - min_amount=None, - deposit_data_list=None, - rng=Random(3131)): +def prepare_random_genesis_deposits( + spec, + deposit_count, + max_pubkey_index, + min_pubkey_index=0, + max_amount=None, + min_amount=None, + deposit_data_list=None, + rng=Random(3131), +): if max_amount is None: max_amount = spec.MAX_EFFECTIVE_BALANCE if min_amount is None: @@ -133,14 +144,26 @@ def prepare_random_genesis_deposits(spec, return deposits, root, deposit_data_list -def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False): +def prepare_state_and_deposit( + spec, + state, + validator_index, + amount, + pubkey=None, + privkey=None, + withdrawal_credentials=None, + signed=False, +): """ Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount. """ deposit_data_list = [] - pubkey = pubkeys[validator_index] - privkey = privkeys[validator_index] + if pubkey is None: + pubkey = pubkeys[validator_index] + + if privkey is None: + privkey = privkeys[validator_index] # insecurely use pubkey as withdrawal key if no credentials provided if withdrawal_credentials is None: @@ -160,3 +183,311 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c state.eth1_data.deposit_root = root state.eth1_data.deposit_count = len(deposit_data_list) return deposit + + +def prepare_deposit_request( + spec, + validator_index, + amount, + index=None, + pubkey=None, + privkey=None, + withdrawal_credentials=None, + signed=False, +): + """ + Create a deposit request for the given validator, depositing the given amount. + """ + if index is None: + index = validator_index + + if pubkey is None: + pubkey = pubkeys[validator_index] + + if privkey is None: + privkey = privkeys[validator_index] + + # insecurely use pubkey as withdrawal key if no credentials provided + if withdrawal_credentials is None: + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + + deposit_data = build_deposit_data( + spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed + ) + return spec.DepositRequest( + pubkey=deposit_data.pubkey, + withdrawal_credentials=deposit_data.withdrawal_credentials, + amount=deposit_data.amount, + signature=deposit_data.signature, + index=index, + ) + + +def prepare_pending_deposit( + spec, + validator_index, + amount, + pubkey=None, + privkey=None, + withdrawal_credentials=None, + fork_version=None, + signed=False, + slot=None, +): + """ + Create a pending deposit for the given validator, depositing the given amount. + """ + if pubkey is None: + pubkey = pubkeys[validator_index] + + if privkey is None: + privkey = privkeys[validator_index] + + # insecurely use pubkey as withdrawal key if no credentials provided + if withdrawal_credentials is None: + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + + # use GENESIS_SLOT which is always finalized if no slot provided + if slot is None: + slot = spec.GENESIS_SLOT + + deposit_data = build_deposit_data( + spec, pubkey, privkey, amount, withdrawal_credentials, fork_version, signed + ) + + return spec.PendingDeposit( + pubkey=deposit_data.pubkey, + amount=deposit_data.amount, + withdrawal_credentials=deposit_data.withdrawal_credentials, + signature=deposit_data.signature, + slot=slot, + ) + + +# +# Run processing +# + + +def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True): + """ + Run ``process_deposit``, yielding: + - pre-state ('pre') + - deposit ('deposit') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + pre_validator_count = len(state.validators) + pre_balance = 0 + pre_effective_balance = 0 + is_top_up = False + # is a top-up + if validator_index < pre_validator_count: + is_top_up = True + pre_balance = get_balance(state, validator_index) + pre_effective_balance = state.validators[validator_index].effective_balance + + if is_post_electra(spec): + pre_pending_deposits_count = len(state.pending_deposits) + + yield "pre", state + yield "deposit", deposit + + if not valid: + expect_assertion_error(lambda: spec.process_deposit(state, deposit)) + yield "post", None + return + + spec.process_deposit(state, deposit) + + yield "post", state + + if not effective or not bls.KeyValidate(deposit.data.pubkey): + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + if is_top_up: + assert get_balance(state, validator_index) == pre_balance + if is_post_electra(spec): + assert len(state.pending_deposits) == pre_pending_deposits_count + else: + if is_top_up: + # Top-ups don't add validators + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + else: + # new validator is added + assert len(state.validators) == pre_validator_count + 1 + assert len(state.balances) == pre_validator_count + 1 + if not is_post_electra(spec): + if is_top_up: + # Top-ups do not change effective balance + assert state.validators[validator_index].effective_balance == pre_effective_balance + else: + effective_balance = min(spec.MAX_EFFECTIVE_BALANCE, deposit.data.amount) + effective_balance -= effective_balance % spec.EFFECTIVE_BALANCE_INCREMENT + assert state.validators[validator_index].effective_balance == effective_balance + assert get_balance(state, validator_index) == pre_balance + deposit.data.amount + else: + # no balance or effective balance changes on deposit processing post electra + assert get_balance(state, validator_index) == pre_balance + assert state.validators[validator_index].effective_balance == pre_effective_balance + # new correct balance deposit queued up + assert len(state.pending_deposits) == pre_pending_deposits_count + 1 + assert state.pending_deposits[pre_pending_deposits_count].pubkey == deposit.data.pubkey + assert ( + state.pending_deposits[pre_pending_deposits_count].withdrawal_credentials + == deposit.data.withdrawal_credentials + ) + assert state.pending_deposits[pre_pending_deposits_count].amount == deposit.data.amount + assert ( + state.pending_deposits[pre_pending_deposits_count].signature + == deposit.data.signature + ) + assert state.pending_deposits[pre_pending_deposits_count].slot == spec.GENESIS_SLOT + + assert state.eth1_deposit_index == state.eth1_data.deposit_count + + +def run_deposit_processing_with_specific_fork_version( + spec, state, fork_version, valid=True, effective=True +): + validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + + pubkey = pubkeys[validator_index] + privkey = privkeys[validator_index] + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] + + deposit_message = spec.DepositMessage( + pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount + ) + domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=fork_version) + deposit_data = spec.DepositData( + pubkey=pubkey, + withdrawal_credentials=withdrawal_credentials, + amount=amount, + signature=bls.Sign(privkey, spec.compute_signing_root(deposit_message, domain)), + ) + deposit, root, _ = deposit_from_context(spec, [deposit_data], 0) + + state.eth1_deposit_index = 0 + state.eth1_data.deposit_root = root + state.eth1_data.deposit_count = 1 + + yield from run_deposit_processing( + spec, state, deposit, validator_index, valid=valid, effective=effective + ) + + +def run_deposit_request_processing(spec, state, deposit_request, validator_index, effective=True): + """ + Run ``process_deposit_request``, yielding: + - pre-state ('pre') + - deposit_request ('deposit_request') + - post-state ('post'). + """ + assert is_post_electra(spec) + + pre_validator_count = len(state.validators) + pre_balance = 0 + is_top_up = False + # is a top-up + if validator_index < pre_validator_count: + is_top_up = True + pre_balance = get_balance(state, validator_index) + pre_effective_balance = state.validators[validator_index].effective_balance + + yield "pre", state + yield "deposit_request", deposit_request + + spec.process_deposit_request(state, deposit_request) + + yield "post", state + + # New validator is only created after the pending_deposits processing + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + + if is_top_up: + assert state.validators[validator_index].effective_balance == pre_effective_balance + assert state.balances[validator_index] == pre_balance + + pending_deposit = spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + ) + + assert state.pending_deposits == [pending_deposit] + + +def run_pending_deposit_applying(spec, state, pending_deposit, validator_index, effective=True): + """ + Enqueue ``pending_deposit`` and run epoch processing with ``process_pending_deposits``, yielding: + - pre-state ('pre') + - post-state ('post'). + - pre-epoch-state ('pre_epoch'), state before epoch transition + - post-epoch-state ('post_epoch'), state after epoch transition + """ + assert is_post_electra(spec) + + # ensure the transition from eth1 bridge is complete + state.deposit_requests_start_index = state.eth1_deposit_index + + # ensure there is enough churn to apply the deposit + if pending_deposit.amount > spec.get_activation_exit_churn_limit(state): + state.deposit_balance_to_consume = ( + pending_deposit.amount - spec.get_activation_exit_churn_limit(state) + ) + + # append pending deposit + state.pending_deposits.append(pending_deposit) + + pre_validator_count = len(state.validators) + pre_balance = 0 + pre_effective_balance = 0 + is_top_up = False + # is a top-up + if validator_index < pre_validator_count: + is_top_up = True + pre_balance = get_balance(state, validator_index) + pre_effective_balance = state.validators[validator_index].effective_balance + + run_process_slots_up_to_epoch_boundary(spec, state) + yield "pre_epoch", state + run_epoch_processing_to(spec, state, "process_pending_deposits", enable_slots_processing=False) + + yield "pre", state + spec.process_pending_deposits(state) + yield "post", state + + continue_state = state.copy() + run_epoch_processing_from(spec, continue_state, "process_pending_deposits") + yield "post_epoch", continue_state + + if effective: + if is_top_up: + # Top-ups don't add validators + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + # Top-ups do not change effective balance + assert state.validators[validator_index].effective_balance == pre_effective_balance + else: + # new validator is added + assert len(state.validators) == pre_validator_count + 1 + assert len(state.balances) == pre_validator_count + 1 + # effective balance is set correctly + max_effective_balace = spec.get_max_effective_balance(state.validators[validator_index]) + effective_balance = min(max_effective_balace, pending_deposit.amount) + effective_balance -= effective_balance % spec.EFFECTIVE_BALANCE_INCREMENT + assert state.validators[validator_index].effective_balance == effective_balance + assert get_balance(state, validator_index) == pre_balance + pending_deposit.amount + else: + assert len(state.validators) == pre_validator_count + assert len(state.balances) == pre_validator_count + if is_top_up: + assert get_balance(state, validator_index) == pre_balance + + assert len(state.pending_deposits) == 0 diff --git a/tests/core/pyspec/eth2spec/test/helpers/eip7441.py b/tests/core/pyspec/eth2spec/test/helpers/eip7441.py new file mode 100644 index 0000000000..546bfbd567 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/eip7441.py @@ -0,0 +1,98 @@ +from curdleproofs import GenerateWhiskTrackerProof, WhiskTracker +from eth_typing import BLSPubkey +from py_arkworks_bls12381 import G1Point, Scalar + +from eth2spec.test.helpers.keys import whisk_ks_initial + +# Map of validator index to initial WhiskTracker (r = 1, k = index) +whisk_initial_tracker_cache_by_index = {} +# Map of validator index to k commitment (k = index) +whisk_initial_k_commitment_cache_by_index = {} +# Map of k_r_G to validator index +whisk_initial_tracker_cache_by_k_r_G = {} +INITIAL_R = 1 + +# Generator +G1 = G1Point() + + +def compute_whisk_initial_tracker_cached(i: int) -> WhiskTracker: + if i in whisk_initial_tracker_cache_by_index: + return whisk_initial_tracker_cache_by_index[i] + + tracker = compute_whisk_tracker(whisk_ks_initial(i), INITIAL_R) + whisk_initial_tracker_cache_by_index[i] = tracker + whisk_initial_tracker_cache_by_k_r_G[tracker.k_r_G] = i + return tracker + + +def compute_whisk_initial_k_commitment_cached(i: int) -> BLSPubkey: + if i in whisk_initial_k_commitment_cache_by_index: + return whisk_initial_k_commitment_cache_by_index[i] + + commitment = compute_whisk_k_commitment(whisk_ks_initial(i)) + whisk_initial_k_commitment_cache_by_index[i] = commitment + return commitment + + +def resolve_known_tracker(tracker: WhiskTracker) -> int | None: + if tracker.k_r_G in whisk_initial_tracker_cache_by_k_r_G: + return whisk_initial_tracker_cache_by_k_r_G[tracker.k_r_G] + else: + return None + + +def g1point_to_bytes(point: G1Point) -> bytes: + return bytes(point.to_compressed_bytes()) + + +def compute_whisk_k_commitment(k: int) -> BLSPubkey: + return g1point_to_bytes(G1 * Scalar(k)) + + +def compute_whisk_tracker(k: int, r: int) -> WhiskTracker: + r_G = G1 * Scalar(r) + k_r_G = r_G * Scalar(k) + return WhiskTracker(g1point_to_bytes(r_G), g1point_to_bytes(k_r_G)) + + +def compute_whisk_tracker_and_commitment(k: int, r: int) -> tuple[WhiskTracker, BLSPubkey]: + k_G = G1 * Scalar(k) + r_G = G1 * Scalar(r) + k_r_G = r_G * Scalar(k) + tracker = WhiskTracker(g1point_to_bytes(r_G), g1point_to_bytes(k_r_G)) + commitment = g1point_to_bytes(k_G) + return tracker, commitment + + +# Trigger condition for first proposal +def set_as_first_proposal(spec, state, proposer_index: int): + if state.whisk_trackers[proposer_index].r_G != spec.BLS_G1_GENERATOR: + # Ensure tracker is empty to prevent breaking it + assert state.whisk_trackers[proposer_index].r_G == spec.BLSG1Point() + state.whisk_trackers[proposer_index].r_G = spec.BLS_G1_GENERATOR + + +def is_first_proposal(spec, state, proposer_index: int) -> bool: + return state.whisk_trackers[proposer_index].r_G == spec.BLS_G1_GENERATOR + + +def register_tracker(state, proposer_index: int, k: int, r: int): + tracker, k_commitment = compute_whisk_tracker_and_commitment(k, r) + state.whisk_trackers[proposer_index] = tracker + state.whisk_k_commitments[proposer_index] = k_commitment + + +def set_registration(body, k: int, r: int): + tracker, k_commitment = compute_whisk_tracker_and_commitment(k, r) + body.whisk_registration_proof = GenerateWhiskTrackerProof(tracker, Scalar(k)) + body.whisk_tracker = tracker + body.whisk_k_commitment = k_commitment + + +def set_opening_proof(spec, state, block, proposer_index: int, k: int, r: int): + tracker, k_commitment = compute_whisk_tracker_and_commitment(k, r) + state.whisk_proposer_trackers[state.slot % spec.PROPOSER_TRACKERS_COUNT] = tracker + state.whisk_k_commitments[proposer_index] = k_commitment + block.proposer_index = proposer_index + block.body.whisk_opening_proof = GenerateWhiskTrackerProof(tracker, Scalar(k)) diff --git a/tests/core/pyspec/eth2spec/test/helpers/electra/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/electra/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py new file mode 100644 index 0000000000..b7274cd441 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/electra/fork.py @@ -0,0 +1,82 @@ +from eth2spec.test.helpers.constants import ( + ELECTRA, +) + +ELECTRA_FORK_TEST_META_TAGS = { + "fork": ELECTRA, +} + + +def run_fork_test(post_spec, pre_state): + yield "pre", pre_state + + post_state = post_spec.upgrade_to_electra(pre_state) + + # Stable fields + stable_fields = [ + "genesis_time", + "genesis_validators_root", + "slot", + # History + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", + # Eth1 + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", + # Registry + # NOTE: 'validators', 'balances' could be changed. + # Randomness + "randao_mixes", + # Slashings + "slashings", + # Participation + "previous_epoch_participation", + "current_epoch_participation", + # Finality + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", + # Inactivity + "inactivity_scores", + # Sync + "current_sync_committee", + "next_sync_committee", + # Withdrawals + "next_withdrawal_index", + "next_withdrawal_validator_index", + # Deep history valid from Capella onwards + "historical_summaries", + "latest_execution_payload_header", + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ["fork"] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + "pubkey", + "withdrawal_credentials", + "slashed", + "activation_epoch", + "exit_epoch", + "withdrawable_epoch", + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.ELECTRA_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield "post", post_state + + return post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py index eed259e819..8c6ef54479 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/epoch_processing.py @@ -1,5 +1,7 @@ - -from eth2spec.test.context import is_post_altair +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_capella, +) def get_process_calls(spec): @@ -8,34 +10,54 @@ def get_process_calls(spec): # Note: make sure to explicitly remove/override a processing function in later phases, # or the old function will stick around. return [ - 'process_justification_and_finalization', - 'process_inactivity_updates', # altair - 'process_rewards_and_penalties', - 'process_registry_updates', - 'process_reveal_deadlines', # custody game - 'process_challenge_deadlines', # custody game - 'process_slashings', - 'process_pending_header.', # sharding - 'charge_confirmed_header_fees', # sharding - 'reset_pending_headers', # sharding - 'process_eth1_data_reset', - 'process_effective_balance_updates', - 'process_slashings_reset', - 'process_randao_mixes_reset', - 'process_historical_roots_update', + "process_justification_and_finalization", + "process_inactivity_updates", # altair + "process_rewards_and_penalties", + "process_registry_updates", + "process_slashings", + "process_eth1_data_reset", + "process_pending_deposits", # electra + "process_pending_consolidations", # electra + "process_effective_balance_updates", + "process_slashings_reset", + "process_randao_mixes_reset", + # Capella replaced `process_historical_roots_update` with `process_historical_summaries_update` + ( + "process_historical_summaries_update" + if is_post_capella(spec) + else ("process_historical_roots_update") + ), # Altair replaced `process_participation_record_updates` with `process_participation_flag_updates` - 'process_participation_flag_updates' if is_post_altair(spec) else ( - 'process_participation_record_updates' + ( + "process_participation_flag_updates" + if is_post_altair(spec) + else ("process_participation_record_updates") ), - 'process_sync_committee_updates', # altair - # TODO: add sharding processing functions when spec stabilizes. + "process_sync_committee_updates", # altair + "process_proposer_lookahead", # fulu ] -def run_epoch_processing_to(spec, state, process_name: str): +def run_epoch_processing_to(spec, state, process_name: str, enable_slots_processing=True): """ Processes to the next epoch transition, up to, but not including, the sub-transition named ``process_name`` """ + if enable_slots_processing: + run_process_slots_up_to_epoch_boundary(spec, state) + + # process components of epoch transition before final-updates + for name in get_process_calls(spec): + if name == process_name: + break + # only run when present. Later phases introduce more to the epoch-processing. + if hasattr(spec, name): + getattr(spec, name)(state) + + +def run_process_slots_up_to_epoch_boundary(spec, state): + """ + Processes slots until the next epoch transition + """ slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) # transition state to slot before epoch state transition @@ -45,12 +67,20 @@ def run_epoch_processing_to(spec, state, process_name: str): # start transitioning, do one slot update before the epoch itself. spec.process_slot(state) - # process components of epoch transition before final-updates + +def run_epoch_processing_from(spec, state, process_name: str): + """ + Processes to the next epoch transition, from, but not including, the sub-transition named ``process_name`` + """ + assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0 + + processing = False for name in get_process_calls(spec): if name == process_name: - break + processing = True + continue # only run when present. Later phases introduce more to the epoch-processing. - if hasattr(spec, name): + if processing and hasattr(spec, name): getattr(spec, name)(state) @@ -59,8 +89,16 @@ def run_epoch_processing_with(spec, state, process_name: str): Processes to the next epoch transition, up to and including the sub-transition named ``process_name`` - pre-state ('pre'), state before calling ``process_name`` - post-state ('post'), state after calling ``process_name`` + - pre-epoch-state ('pre_epoch'), state before epoch transition + - post-epoch-state ('post_epoch'), state after epoch transition + The state passed by reference will be modified to be the ``process_name``post state. """ - run_epoch_processing_to(spec, state, process_name) - yield 'pre', state + run_process_slots_up_to_epoch_boundary(spec, state) + yield "pre_epoch", state + run_epoch_processing_to(spec, state, process_name, enable_slots_processing=False) + yield "pre", state getattr(spec, process_name)(state) - yield 'post', state + yield "post", state + continue_state = state.copy() + run_epoch_processing_from(spec, continue_state, process_name) + yield "post_epoch", continue_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py index 43be965a58..94b6faf25b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py +++ b/tests/core/pyspec/eth2spec/test/helpers/execution_payload.py @@ -1,3 +1,309 @@ +from hashlib import sha256 + +from eth_hash.auto import keccak +from rlp import encode +from rlp.sedes import big_endian_int, Binary, List +from trie import HexaryTrie + +from eth2spec.debug.random_value import get_random_bytes_list +from eth2spec.test.helpers.forks import ( + is_post_capella, + is_post_deneb, + is_post_eip7732, + is_post_electra, +) +from eth2spec.test.helpers.keys import privkeys +from eth2spec.test.helpers.withdrawals import get_expected_withdrawals +from eth2spec.utils.ssz.ssz_impl import hash_tree_root + + +def get_execution_payload_header(spec, state, execution_payload): + if is_post_eip7732(spec): + return spec.ExecutionPayloadHeader( + parent_block_hash=execution_payload.parent_hash, + parent_block_root=state.latest_block_header.hash_tree_root(), + block_hash=execution_payload.block_hash, + gas_limit=execution_payload.gas_limit, + slot=state.slot, + blob_kzg_commitments_root=state.latest_execution_payload_header.blob_kzg_commitments_root, + ) + + payload_header = spec.ExecutionPayloadHeader( + parent_hash=execution_payload.parent_hash, + fee_recipient=execution_payload.fee_recipient, + state_root=execution_payload.state_root, + receipts_root=execution_payload.receipts_root, + logs_bloom=execution_payload.logs_bloom, + prev_randao=execution_payload.prev_randao, + block_number=execution_payload.block_number, + gas_limit=execution_payload.gas_limit, + gas_used=execution_payload.gas_used, + timestamp=execution_payload.timestamp, + extra_data=execution_payload.extra_data, + base_fee_per_gas=execution_payload.base_fee_per_gas, + block_hash=execution_payload.block_hash, + transactions_root=spec.hash_tree_root(execution_payload.transactions), + ) + if is_post_capella(spec): + payload_header.withdrawals_root = spec.hash_tree_root(execution_payload.withdrawals) + if is_post_deneb(spec): + payload_header.blob_gas_used = execution_payload.blob_gas_used + payload_header.excess_blob_gas = execution_payload.excess_blob_gas + return payload_header + + +# https://eips.ethereum.org/EIPS/eip-2718 +def compute_trie_root_from_indexed_data(data): + """ + Computes the root hash of `patriciaTrie(rlp(Index) => Data)` for a data array. + """ + t = HexaryTrie(db={}) + for i, obj in enumerate(data): + k = encode(i, big_endian_int) + t.set(k, obj) # Implicitly skipped if `obj == b''` (invalid RLP) + return t.root_hash + + +# https://eips.ethereum.org/EIPS/eip-7685 +def compute_requests_hash(block_requests): + m = sha256() + for r in block_requests: + if len(r) > 1: + m.update(sha256(r).digest()) + return m.digest() + + +# https://eips.ethereum.org/EIPS/eip-4895 +# https://eips.ethereum.org/EIPS/eip-4844 +def compute_el_header_block_hash( + spec, + payload_header, + transactions_trie_root, + withdrawals_trie_root=None, + parent_beacon_block_root=None, + requests_hash=None, +): + """ + Computes the RLP execution block hash described by an `ExecutionPayloadHeader`. + """ + if is_post_eip7732(spec): + return spec.Hash32() + + execution_payload_header_rlp = [ + # parent_hash + (Binary(32, 32), payload_header.parent_hash), + # ommers_hash + ( + Binary(32, 32), + bytes.fromhex("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"), + ), + # coinbase + (Binary(20, 20), payload_header.fee_recipient), + # state_root + (Binary(32, 32), payload_header.state_root), + # txs_root + (Binary(32, 32), transactions_trie_root), + # receipts_root + (Binary(32, 32), payload_header.receipts_root), + # logs_bloom + (Binary(256, 256), payload_header.logs_bloom), + # difficulty + (big_endian_int, 0), + # number + (big_endian_int, payload_header.block_number), + # gas_limit + (big_endian_int, payload_header.gas_limit), + # gas_used + (big_endian_int, payload_header.gas_used), + # timestamp + (big_endian_int, payload_header.timestamp), + # extradata + (Binary(0, 32), payload_header.extra_data), + # prev_randao + (Binary(32, 32), payload_header.prev_randao), + # nonce + (Binary(8, 8), bytes.fromhex("0000000000000000")), + # base_fee_per_gas + (big_endian_int, payload_header.base_fee_per_gas), + ] + if is_post_capella(spec): + # withdrawals_root + execution_payload_header_rlp.append((Binary(32, 32), withdrawals_trie_root)) + if is_post_deneb(spec): + # blob_gas_used + execution_payload_header_rlp.append((big_endian_int, payload_header.blob_gas_used)) + # excess_blob_gas + execution_payload_header_rlp.append((big_endian_int, payload_header.excess_blob_gas)) + # parent_beacon_root + execution_payload_header_rlp.append((Binary(32, 32), parent_beacon_block_root)) + if is_post_electra(spec): + # requests_hash + execution_payload_header_rlp.append((Binary(32, 32), requests_hash)) + + sedes = List([schema for schema, _ in execution_payload_header_rlp]) + values = [value for _, value in execution_payload_header_rlp] + encoded = encode(values, sedes) + + return spec.Hash32(keccak(encoded)) + + +# https://eips.ethereum.org/EIPS/eip-4895 +def get_withdrawal_rlp(withdrawal): + withdrawal_rlp = [ + # index + (big_endian_int, withdrawal.index), + # validator_index + (big_endian_int, withdrawal.validator_index), + # address + (Binary(20, 20), withdrawal.address), + # amount + (big_endian_int, withdrawal.amount), + ] + + sedes = List([schema for schema, _ in withdrawal_rlp]) + values = [value for _, value in withdrawal_rlp] + return encode(values, sedes) + + +def get_deposit_request_rlp_bytes(deposit_request): + deposit_request_rlp = [ + # pubkey + (Binary(48, 48), deposit_request.pubkey), + # withdrawal_credentials + (Binary(32, 32), deposit_request.withdrawal_credentials), + # amount + (big_endian_int, deposit_request.amount), + # pubkey + (Binary(96, 96), deposit_request.signature), + # index + (big_endian_int, deposit_request.index), + ] + + sedes = List([schema for schema, _ in deposit_request_rlp]) + values = [value for _, value in deposit_request_rlp] + return b"\x00" + encode(values, sedes) + + +# https://eips.ethereum.org/EIPS/eip-7002 +def get_withdrawal_request_rlp_bytes(withdrawal_request): + withdrawal_request_rlp = [ + # source_address + (Binary(20, 20), withdrawal_request.source_address), + # validator_pubkey + (Binary(48, 48), withdrawal_request.validator_pubkey), + ] + + sedes = List([schema for schema, _ in withdrawal_request_rlp]) + values = [value for _, value in withdrawal_request_rlp] + return b"\x01" + encode(values, sedes) + + +# https://eips.ethereum.org/EIPS/eip-7251 +def get_consolidation_request_rlp_bytes(consolidation_request): + consolidation_request_rlp = [ + # source_address + (Binary(20, 20), consolidation_request.source_address), + # source_pubkey + (Binary(48, 48), consolidation_request.source_pubkey), + # target_pubkey + (Binary(48, 48), consolidation_request.target_pubkey), + ] + + sedes = List([schema for schema, _ in consolidation_request_rlp]) + values = [value for _, value in consolidation_request_rlp] + return b"\x02" + encode(values, sedes) + + +def compute_el_block_hash_with_new_fields(spec, payload, parent_beacon_block_root, requests_hash): + if payload == spec.ExecutionPayload(): + return spec.Hash32() + + transactions_trie_root = compute_trie_root_from_indexed_data(payload.transactions) + + withdrawals_trie_root = None + + if is_post_capella(spec): + withdrawals_encoded = [get_withdrawal_rlp(withdrawal) for withdrawal in payload.withdrawals] + withdrawals_trie_root = compute_trie_root_from_indexed_data(withdrawals_encoded) + if not is_post_deneb(spec): + parent_beacon_block_root = None + + payload_header = get_execution_payload_header(spec, spec.BeaconState(), payload) + + return compute_el_header_block_hash( + spec, + payload_header, + transactions_trie_root, + withdrawals_trie_root, + parent_beacon_block_root, + requests_hash, + ) + + +def compute_el_block_hash(spec, payload, pre_state): + parent_beacon_block_root = None + requests_hash = None + + if is_post_deneb(spec): + previous_block_header = pre_state.latest_block_header.copy() + if previous_block_header.state_root == spec.Root(): + previous_block_header.state_root = pre_state.hash_tree_root() + parent_beacon_block_root = previous_block_header.hash_tree_root() + if is_post_electra(spec): + requests_hash = compute_requests_hash([]) + + return compute_el_block_hash_with_new_fields( + spec, payload, parent_beacon_block_root, requests_hash + ) + + +def compute_el_block_hash_for_block(spec, block): + requests_hash = None + + if is_post_electra(spec): + requests_list = spec.get_execution_requests_list(block.body.execution_requests) + requests_hash = compute_requests_hash(requests_list) + + return compute_el_block_hash_with_new_fields( + spec, block.body.execution_payload, block.parent_root, requests_hash + ) + + +def build_empty_post_eip7732_execution_payload_header(spec, state): + if not is_post_eip7732(spec): + return + parent_block_root = hash_tree_root(state.latest_block_header) + kzg_list = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + epoch = spec.get_current_epoch(state) + builder_index = None + for index in spec.get_active_validator_indices(state, epoch): + if not state.validators[index].slashed: + builder_index = index + assert builder_index is not None + return spec.ExecutionPayloadHeader( + parent_block_hash=state.latest_block_hash, + parent_block_root=parent_block_root, + block_hash=spec.Hash32(), + gas_limit=spec.uint64(0), + builder_index=builder_index, + slot=state.slot, + value=spec.Gwei(0), + blob_kzg_commitments_root=kzg_list.hash_tree_root(), + ) + + +def build_empty_signed_execution_payload_header(spec, state): + if not is_post_eip7732(spec): + return + message = build_empty_post_eip7732_execution_payload_header(spec, state) + privkey = privkeys[message.builder_index] + signature = spec.get_execution_payload_header_signature(state, message, privkey) + return spec.SignedExecutionPayloadHeader( + message=message, + signature=signature, + ) + + def build_empty_execution_payload(spec, state, randao_mix=None): """ Assuming a pre-state of the same slot, build a valid ExecutionPayload without any transactions. @@ -11,56 +317,88 @@ def build_empty_execution_payload(spec, state, randao_mix=None): payload = spec.ExecutionPayload( parent_hash=latest.block_hash, - coinbase=spec.Bytes20(), - state_root=latest.state_root, # no changes to the state - receipt_root=b"no receipts here" + b"\x00" * 16, # TODO: root of empty MPT may be better. - logs_bloom=spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM](), # TODO: zeroed logs bloom for empty logs ok? - block_number=latest.block_number + 1, - random=randao_mix, - gas_limit=latest.gas_limit, # retain same limit + fee_recipient=spec.ExecutionAddress(), + receipts_root=spec.Bytes32( + bytes.fromhex("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347") + ), + logs_bloom=spec.ByteVector[ + spec.BYTES_PER_LOGS_BLOOM + ](), # TODO: zeroed logs bloom for empty logs ok? + prev_randao=randao_mix, gas_used=0, # empty block, 0 gas + gas_limit=latest.gas_limit, timestamp=timestamp, - base_fee_per_gas=latest.base_fee_per_gas, # retain same base_fee - block_hash=spec.Hash32(), + extra_data=spec.ByteList[spec.MAX_EXTRA_DATA_BYTES](), transactions=empty_txs, ) - # TODO: real RLP + block hash logic would be nice, requires RLP and keccak256 dependency however. - payload.block_hash = spec.Hash32(spec.hash(payload.hash_tree_root() + b"FAKE RLP HASH")) + if not is_post_eip7732(spec): + payload.state_root = latest.state_root # no changes to the state + payload.block_number = latest.block_number + 1 + payload.gas_limit = latest.gas_limit # retain same limit + payload.base_fee_per_gas = latest.base_fee_per_gas # retain same base_fee + if is_post_capella(spec): + payload.withdrawals = get_expected_withdrawals(spec, state) + if is_post_deneb(spec): + payload.blob_gas_used = 0 + payload.excess_blob_gas = 0 + + payload.block_hash = compute_el_block_hash(spec, payload, state) return payload -def get_execution_payload_header(spec, execution_payload): - return spec.ExecutionPayloadHeader( - parent_hash=execution_payload.parent_hash, - coinbase=execution_payload.coinbase, - state_root=execution_payload.state_root, - receipt_root=execution_payload.receipt_root, - logs_bloom=execution_payload.logs_bloom, - random=execution_payload.random, - block_number=execution_payload.block_number, - gas_limit=execution_payload.gas_limit, - gas_used=execution_payload.gas_used, - timestamp=execution_payload.timestamp, - base_fee_per_gas=execution_payload.base_fee_per_gas, - block_hash=execution_payload.block_hash, - transactions_root=spec.hash_tree_root(execution_payload.transactions) +def build_randomized_execution_payload(spec, state, rng): + execution_payload = build_empty_execution_payload(spec, state) + execution_payload.fee_recipient = spec.ExecutionAddress(get_random_bytes_list(rng, 20)) + execution_payload.state_root = spec.Bytes32(get_random_bytes_list(rng, 32)) + execution_payload.receipts_root = spec.Bytes32(get_random_bytes_list(rng, 32)) + execution_payload.logs_bloom = spec.ByteVector[spec.BYTES_PER_LOGS_BLOOM]( + get_random_bytes_list(rng, spec.BYTES_PER_LOGS_BLOOM) + ) + execution_payload.block_number = rng.randint(0, int(10e10)) + execution_payload.gas_limit = rng.randint(0, int(10e10)) + execution_payload.gas_used = rng.randint(0, int(10e10)) + extra_data_length = rng.randint(0, spec.MAX_EXTRA_DATA_BYTES) + execution_payload.extra_data = spec.ByteList[spec.MAX_EXTRA_DATA_BYTES]( + get_random_bytes_list(rng, extra_data_length) ) + execution_payload.base_fee_per_gas = rng.randint(0, 2**256 - 1) + + num_transactions = rng.randint(0, 100) + execution_payload.transactions = [get_random_tx(rng) for _ in range(num_transactions)] + + execution_payload.block_hash = compute_el_block_hash(spec, execution_payload, state) + + return execution_payload def build_state_with_incomplete_transition(spec, state): - return build_state_with_execution_payload_header(spec, state, spec.ExecutionPayloadHeader()) + header = spec.ExecutionPayloadHeader() + if is_post_eip7732(spec): + kzgs = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + header.blob_kzg_commitments_root = kzgs.hash_tree_root() + + state = build_state_with_execution_payload_header(spec, state, header) + assert not spec.is_merge_transition_complete(state) + + return state def build_state_with_complete_transition(spec, state): pre_state_payload = build_empty_execution_payload(spec, state) - payload_header = get_execution_payload_header(spec, pre_state_payload) + payload_header = get_execution_payload_header(spec, state, pre_state_payload) + + state = build_state_with_execution_payload_header(spec, state, payload_header) + assert spec.is_merge_transition_complete(state) - return build_state_with_execution_payload_header(spec, state, payload_header) + return state def build_state_with_execution_payload_header(spec, state, execution_payload_header): pre_state = state.copy() pre_state.latest_execution_payload_header = execution_payload_header - return pre_state + + +def get_random_tx(rng): + return get_random_bytes_list(rng, rng.randint(1, 1000)) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py index 65d6975f23..cc2dec819a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_choice.py @@ -1,8 +1,115 @@ +from collections.abc import Sequence +from typing import Any, NamedTuple + from eth_utils import encode_hex + +from eth2spec.fulu.mainnet import DataColumnSidecar +from eth2spec.test.exceptions import BlockNotFoundException from eth2spec.test.helpers.attestations import ( next_epoch_with_attestations, next_slots_with_attestations, + state_transition_with_full_block, ) +from eth2spec.test.helpers.forks import is_post_eip7732, is_post_fulu +from eth2spec.test.helpers.state import ( + payload_state_transition, + payload_state_transition_no_store, +) + + +def check_head_against_root(spec, store, root): + head = spec.get_head(store) + if is_post_eip7732(spec): + assert head.root == root + else: + assert head == root + + +class BlobData(NamedTuple): + """ + The return values of ``retrieve_blobs_and_proofs`` helper. + """ + + blobs: Sequence[Any] | None = None + proofs: Sequence[bytes] | None = None + sidecars: Sequence[DataColumnSidecar] | None = None + + def is_post_fulu(self): + return self.sidecars is not None and self.blobs is None and self.proofs is None + + def is_pre_fulu(self): + return self.blobs is not None and self.proofs is not None and self.sidecars is None + + +def with_blob_data(spec, blob_data: BlobData, func): + if not is_post_fulu(spec): + if blob_data.proofs is None: + raise ValueError("blob_data.proofs must be provided when pre FULU fork") + yield from with_blob_data_deneb(spec, blob_data, func) + else: + if blob_data.sidecars is None: + raise ValueError("blob_data.sidecars must be provided when post FULU fork") + yield from with_blob_data_fulu(spec, blob_data, func) + + +def with_blob_data_deneb(spec, blob_data: BlobData, func): + """ + This helper runs the given ``func`` with monkeypatched ``retrieve_blobs_and_proofs`` + that returns ``blob_data.blobs, blob_data.proofs``. + """ + + def retrieve_blobs_and_proofs(_): + assert blob_data.proofs is not None, "blob_data.proofs must be provided" + return blob_data.blobs, blob_data.proofs + + retrieve_blobs_and_proofs_backup = spec.retrieve_blobs_and_proofs + spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs + + class AtomicBoolean: + value = False + + is_called = AtomicBoolean() + + def wrap(flag: AtomicBoolean): + yield from func() + flag.value = True + + try: + yield from wrap(is_called) + finally: + spec.retrieve_blobs_and_proofs = retrieve_blobs_and_proofs_backup + assert is_called.value + + +def with_blob_data_fulu(spec, blob_data: BlobData, func): + """ + This helper runs the given ``func`` with monkeypatched ``retrieve_column_sidecars`` + that returns ``blob_data``. + """ + + def retrieve_column_sidecars(_): + assert blob_data.sidecars is not None, "blob_data.sidecars must be provided" + if len(blob_data.sidecars) == 0: + assert False, "Simulation: not all required columns have been sampled" + return blob_data.sidecars + + retrieve_column_sidecars_backup = spec.retrieve_column_sidecars + spec.retrieve_column_sidecars = retrieve_column_sidecars + + class AtomicBoolean: + value = False + + is_called = AtomicBoolean() + + def wrap(flag: AtomicBoolean): + yield from func() + flag.value = True + + try: + yield from wrap(is_called) + finally: + spec.retrieve_column_sidecars = retrieve_column_sidecars_backup + assert is_called.value def get_anchor_root(spec, state): @@ -12,73 +119,88 @@ def get_anchor_root(spec, state): return spec.hash_tree_root(anchor_block_header) -def add_block_to_store(spec, store, signed_block): - pre_state = store.block_states[signed_block.message.parent_root] - block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT - - if store.time < block_time: - spec.on_tick(store, block_time) +def tick_and_add_block( + spec, + store, + signed_block, + test_steps, + valid=True, + merge_block=False, + block_not_found=False, + is_optimistic=False, + blob_data: BlobData | None = None, +): + pre_state = get_store_full_state(spec, store, signed_block.message.parent_root) + if merge_block: + assert spec.is_merge_transition_block(pre_state, signed_block.message.body) - spec.on_block(store, signed_block) - - -def tick_and_add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): - pre_state = store.block_states[signed_block.message.parent_root] block_time = pre_state.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT - - if store.time < block_time: - on_tick_and_append_step(spec, store, block_time, test_steps) + while store.time < block_time: + time = ( + pre_state.genesis_time + + (spec.get_current_slot(store) + 1) * spec.config.SECONDS_PER_SLOT + ) + on_tick_and_append_step(spec, store, time, test_steps) post_state = yield from add_block( - spec, store, signed_block, test_steps, valid=valid, allow_invalid_attestations=allow_invalid_attestations) + spec, + store, + signed_block, + test_steps, + valid=valid, + block_not_found=block_not_found, + is_optimistic=is_optimistic, + blob_data=blob_data, + ) return post_state -def tick_and_run_on_attestation(spec, store, attestation, test_steps): - parent_block = store.blocks[attestation.data.beacon_block_root] - pre_state = store.block_states[spec.hash_tree_root(parent_block)] - block_time = pre_state.genesis_time + parent_block.slot * spec.config.SECONDS_PER_SLOT - next_epoch_time = block_time + spec.SLOTS_PER_EPOCH * spec.config.SECONDS_PER_SLOT +def tick_and_add_block_with_data(spec, store, signed_block, test_steps, blob_data, valid=True): + def run_func(): + yield from tick_and_add_block( + spec, store, signed_block, test_steps, blob_data=blob_data, valid=valid + ) - if store.time < next_epoch_time: - spec.on_tick(store, next_epoch_time) - test_steps.append({'tick': int(next_epoch_time)}) + yield from with_blob_data(spec, blob_data, run_func) - spec.on_attestation(store, attestation) + +def add_attestation(spec, store, attestation, test_steps, is_from_block=False, valid=True): + run_on_attestation(spec, store, attestation, is_from_block=is_from_block, valid=valid) yield get_attestation_file_name(attestation), attestation - test_steps.append({'attestation': get_attestation_file_name(attestation)}) + if valid: + test_steps.append({"attestation": get_attestation_file_name(attestation)}) + else: + test_steps.append({"attestation": get_attestation_file_name(attestation), "valid": False}) -def add_attestation(spec, store, attestation, test_steps, valid=True): - yield get_attestation_file_name(attestation), attestation +def add_attestations(spec, store, attestations, test_steps, is_from_block=False): + for attestation in attestations: + yield from add_attestation( + spec, store, attestation, test_steps, is_from_block=is_from_block + ) - if not valid: - try: - run_on_attestation(spec, store, attestation, valid=True) - except AssertionError: - test_steps.append({ - 'attestation': get_attestation_file_name(attestation), - 'valid': False, - }) - return - else: - assert False - run_on_attestation(spec, store, attestation, valid=True) - test_steps.append({'attestation': get_attestation_file_name(attestation)}) +def tick_and_run_on_attestation(spec, store, attestation, test_steps, is_from_block=False): + # Make get_current_slot(store) >= attestation.data.slot + 1 + min_time_to_include = (attestation.data.slot + 1) * spec.config.SECONDS_PER_SLOT + if store.time < min_time_to_include: + spec.on_tick(store, min_time_to_include) + test_steps.append({"tick": int(min_time_to_include)}) + + yield from add_attestation(spec, store, attestation, test_steps, is_from_block) -def run_on_attestation(spec, store, attestation, valid=True): +def run_on_attestation(spec, store, attestation, is_from_block=False, valid=True): if not valid: try: - spec.on_attestation(store, attestation) + spec.on_attestation(store, attestation, is_from_block=is_from_block) except AssertionError: return else: assert False - spec.on_attestation(store, attestation) + spec.on_attestation(store, attestation, is_from_block=is_from_block) def get_genesis_forkchoice_store(spec, genesis_state): @@ -89,7 +211,14 @@ def get_genesis_forkchoice_store(spec, genesis_state): def get_genesis_forkchoice_store_and_block(spec, genesis_state): assert genesis_state.slot == spec.GENESIS_SLOT genesis_block = spec.BeaconBlock(state_root=genesis_state.hash_tree_root()) - return spec.get_forkchoice_store(genesis_state, genesis_block), genesis_block + if is_post_eip7732(spec): + genesis_block.body.signed_execution_payload_header.message.block_hash = ( + genesis_state.latest_block_hash + ) + store = spec.get_forkchoice_store(genesis_state, genesis_block) + if is_post_eip7732(spec): + store.execution_payload_states = store.block_states.copy() + return store, genesis_block def get_block_file_name(block): @@ -100,9 +229,36 @@ def get_attestation_file_name(attestation): return f"attestation_{encode_hex(attestation.hash_tree_root())}" +def get_attester_slashing_file_name(attester_slashing): + return f"attester_slashing_{encode_hex(attester_slashing.hash_tree_root())}" + + +def get_blobs_file_name(blobs=None, blobs_root=None): + if blobs: + return f"blobs_{encode_hex(blobs.hash_tree_root())}" + else: + return f"blobs_{encode_hex(blobs_root)}" + + +def get_sidecars_file_names(sidecars: Sequence[DataColumnSidecar]) -> Sequence[str]: + """ + Returns the file names for sidecars. + """ + return [get_sidecar_file_name(sidecar) for sidecar in sidecars] + + +def get_sidecar_file_name(sidecar: DataColumnSidecar) -> str: + """ + Returns the file name for a single sidecar. + """ + return f"column_{encode_hex(sidecar.hash_tree_root())}" + + def on_tick_and_append_step(spec, store, time, test_steps): + assert time >= store.time spec.on_tick(store, time) - test_steps.append({'tick': int(time)}) + test_steps.append({"tick": int(time)}) + output_store_checks(spec, store, test_steps) def run_on_block(spec, store, signed_block, valid=True): @@ -115,115 +271,286 @@ def run_on_block(spec, store, signed_block, valid=True): assert False spec.on_block(store, signed_block) - assert store.blocks[signed_block.message.hash_tree_root()] == signed_block.message - - -def add_block(spec, store, signed_block, test_steps, valid=True, allow_invalid_attestations=False): + root = signed_block.message.hash_tree_root() + assert store.blocks[root] == signed_block.message + + +def get_store_full_state(spec, store, root): + if is_post_eip7732(spec): + return store.execution_payload_states[root] + return store.block_states[root] + + +def add_block( + spec, + store, + signed_block, + test_steps, + valid=True, + block_not_found=False, + is_optimistic=False, + blob_data=None, +): """ Run on_block and on_attestation """ yield get_block_file_name(signed_block), signed_block + # Check blob_data + if blob_data is not None: + assert blob_data.is_pre_fulu() or blob_data.is_post_fulu(), "Integrity fail blob_data" + + if blob_data.is_pre_fulu(): + blobs = spec.List[spec.Blob, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK](blob_data.blobs) + blobs_root = blobs.hash_tree_root() + yield get_blobs_file_name(blobs_root=blobs_root), blobs + + if blob_data.is_post_fulu(): + for sidecar in blob_data.sidecars: + yield get_sidecar_file_name(sidecar), sidecar + + def _append_step(valid=True): + if blob_data is not None: + if blob_data.is_post_fulu(): + step = { + "block": get_block_file_name(signed_block), + "columns": get_sidecars_file_names(blob_data.sidecars), + "valid": valid, + } + if blob_data.is_pre_fulu(): + step = { + "block": get_block_file_name(signed_block), + "blobs": get_blobs_file_name(blobs_root=blobs_root), + "proofs": [encode_hex(proof) for proof in blob_data.proofs], + "valid": valid, + } + + test_steps.append(step) + else: + test_steps.append( + { + "block": get_block_file_name(signed_block), + "valid": valid, + } + ) + if not valid: - try: + if is_optimistic: run_on_block(spec, store, signed_block, valid=True) - except AssertionError: - test_steps.append({ - 'block': get_block_file_name(signed_block), - 'valid': False, - }) - return + _append_step(valid=False) else: - assert False - - run_on_block(spec, store, signed_block, valid=True) - test_steps.append({'block': get_block_file_name(signed_block)}) + try: + run_on_block(spec, store, signed_block, valid=True) + except (AssertionError, BlockNotFoundException) as e: + if isinstance(e, BlockNotFoundException) and not block_not_found: + assert False + _append_step(valid=False) + return + else: + assert False + else: + run_on_block(spec, store, signed_block, valid=True) + _append_step() # An on_block step implies receiving block's attestations - try: - for attestation in signed_block.message.body.attestations: - run_on_attestation(spec, store, attestation, valid=True) - except AssertionError: - if allow_invalid_attestations: - pass - else: - raise + for attestation in signed_block.message.body.attestations: + run_on_attestation(spec, store, attestation, is_from_block=True, valid=True) + + # An on_block step implies receiving block's attester slashings + for attester_slashing in signed_block.message.body.attester_slashings: + run_on_attester_slashing(spec, store, attester_slashing, valid=True) block_root = signed_block.message.hash_tree_root() assert store.blocks[block_root] == signed_block.message assert store.block_states[block_root].hash_tree_root() == signed_block.message.state_root - test_steps.append({ - 'checks': { - 'time': int(store.time), - 'head': get_formatted_head_output(spec, store), - 'justified_checkpoint': { - 'epoch': int(store.justified_checkpoint.epoch), - 'root': encode_hex(store.justified_checkpoint.root), - }, - 'finalized_checkpoint': { - 'epoch': int(store.finalized_checkpoint.epoch), - 'root': encode_hex(store.finalized_checkpoint.root), - }, - 'best_justified_checkpoint': { - 'epoch': int(store.best_justified_checkpoint.epoch), - 'root': encode_hex(store.best_justified_checkpoint.root), - }, - } - }) + if not is_optimistic: + output_store_checks(spec, store, test_steps) return store.block_states[signed_block.message.hash_tree_root()] +def run_on_attester_slashing(spec, store, attester_slashing, valid=True): + if not valid: + try: + spec.on_attester_slashing(store, attester_slashing) + except AssertionError: + return + else: + assert False + + spec.on_attester_slashing(store, attester_slashing) + + +def add_attester_slashing(spec, store, attester_slashing, test_steps, valid=True): + slashing_file_name = get_attester_slashing_file_name(attester_slashing) + yield get_attester_slashing_file_name(attester_slashing), attester_slashing + + if not valid: + try: + run_on_attester_slashing(spec, store, attester_slashing) + except AssertionError: + test_steps.append( + { + "attester_slashing": slashing_file_name, + "valid": False, + } + ) + return + else: + assert False + + run_on_attester_slashing(spec, store, attester_slashing) + test_steps.append({"attester_slashing": slashing_file_name}) + + def get_formatted_head_output(spec, store): head = spec.get_head(store) + if is_post_eip7732(spec): + return { + "slot": int(head.slot), + "root": encode_hex(head.root), + } + slot = store.blocks[head].slot return { - 'slot': int(slot), - 'root': encode_hex(head), + "slot": int(slot), + "root": encode_hex(head), } -def apply_next_epoch_with_attestations(spec, - state, - store, - fill_cur_epoch, - fill_prev_epoch, - participation_fn=None, - test_steps=None): +def output_head_check(spec, store, test_steps): + test_steps.append( + { + "checks": { + "head": get_formatted_head_output(spec, store), + } + } + ) + + +def output_store_checks(spec, store, test_steps, with_viable_for_head_weights=False): + checks = { + "time": int(store.time), + "head": get_formatted_head_output(spec, store), + "justified_checkpoint": { + "epoch": int(store.justified_checkpoint.epoch), + "root": encode_hex(store.justified_checkpoint.root), + }, + "finalized_checkpoint": { + "epoch": int(store.finalized_checkpoint.epoch), + "root": encode_hex(store.finalized_checkpoint.root), + }, + "proposer_boost_root": encode_hex(store.proposer_boost_root), + } + + if with_viable_for_head_weights: + filtered_block_roots = spec.get_filtered_block_tree(store).keys() + leaves_viable_for_head = [ + root + for root in filtered_block_roots + if not any(c for c in filtered_block_roots if store.blocks[c].parent_root == root) + ] + + viable_for_head_roots_and_weights = [ + { + "root": encode_hex(viable_for_head_root), + "weight": int(spec.get_weight(store, viable_for_head_root)), + } + for viable_for_head_root in leaves_viable_for_head + ] + checks["viable_for_head_roots_and_weights"] = viable_for_head_roots_and_weights + + test_steps.append({"checks": checks}) + + +def apply_next_epoch_with_attestations( + spec, state, store, fill_cur_epoch, fill_prev_epoch, participation_fn=None, test_steps=None +): if test_steps is None: test_steps = [] _, new_signed_blocks, post_state = next_epoch_with_attestations( - spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) + spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn + ) for signed_block in new_signed_blocks: block = signed_block.message yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) block_root = block.hash_tree_root() assert store.blocks[block_root] == block last_signed_block = signed_block - assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + if is_post_eip7732(spec): + assert ( + store.execution_payload_states[block_root].hash_tree_root() + == post_state.hash_tree_root() + ) + else: + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() return post_state, store, last_signed_block -def apply_next_slots_with_attestations(spec, - state, - store, - slots, - fill_cur_epoch, - fill_prev_epoch, - test_steps, - participation_fn=None): +def apply_next_slots_with_attestations( + spec, state, store, slots, fill_cur_epoch, fill_prev_epoch, test_steps, participation_fn=None +): _, new_signed_blocks, post_state = next_slots_with_attestations( - spec, state, slots, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn) + spec, state, slots, fill_cur_epoch, fill_prev_epoch, participation_fn=participation_fn + ) for signed_block in new_signed_blocks: block = signed_block.message yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) block_root = block.hash_tree_root() assert store.blocks[block_root] == block last_signed_block = signed_block - assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() + if is_post_eip7732(spec): + assert ( + store.execution_payload_states[block_root].hash_tree_root() + == post_state.hash_tree_root() + ) + else: + assert store.block_states[block_root].hash_tree_root() == post_state.hash_tree_root() return post_state, store, last_signed_block + + +def is_ready_to_justify(spec, state): + """ + Check if the given ``state`` will trigger justification updates at epoch boundary. + """ + temp_state = state.copy() + spec.process_justification_and_finalization(temp_state) + return temp_state.current_justified_checkpoint.epoch > state.current_justified_checkpoint.epoch + + +def find_next_justifying_slot(spec, state, fill_cur_epoch, fill_prev_epoch, participation_fn=None): + temp_state = state.copy() + + signed_blocks = [] + justifying_slot = None + while justifying_slot is None: + signed_block = state_transition_with_full_block( + spec, + temp_state, + fill_cur_epoch, + fill_prev_epoch, + participation_fn, + ) + signed_blocks.append(signed_block) + payload_state_transition_no_store(spec, temp_state, signed_block.message) + + if is_ready_to_justify(spec, temp_state): + justifying_slot = temp_state.slot + + return signed_blocks, justifying_slot + + +def get_pow_block_file_name(pow_block): + return f"pow_block_{encode_hex(pow_block.block_hash)}" + + +def add_pow_block(spec, store, pow_block, test_steps): + yield get_pow_block_file_name(pow_block), pow_block + test_steps.append({"pow_block": get_pow_block_file_name(pow_block)}) diff --git a/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py new file mode 100644 index 0000000000..856d655deb --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fork_transition.py @@ -0,0 +1,552 @@ +from enum import auto, Enum + +from eth2spec.test.helpers.attestations import ( + next_slots_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing_by_indices, +) +from eth2spec.test.helpers.block import ( + build_empty_block, + build_empty_block_for_next_slot, + sign_block, +) +from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change +from eth2spec.test.helpers.consolidations import ( + prepare_switch_to_compounding_request, +) +from eth2spec.test.helpers.constants import ( + DENEB, + PHASE0, + POST_FORK_OF, + PREVIOUS_FORK_OF, +) +from eth2spec.test.helpers.deposits import ( + prepare_deposit_request, + prepare_state_and_deposit, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.forks import ( + get_next_fork_transition, + is_post_bellatrix, + is_post_eip7732, + is_post_electra, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_valid_proposer_slashing, +) +from eth2spec.test.helpers.state import ( + next_slot, + state_transition_and_sign_block, + transition_to, +) +from eth2spec.test.helpers.voluntary_exits import ( + prepare_signed_exits, +) +from eth2spec.test.helpers.withdrawals import ( + prepare_withdrawal_request, +) + + +class OperationType(Enum): + PROPOSER_SLASHING = auto() + ATTESTER_SLASHING = auto() + DEPOSIT = auto() + VOLUNTARY_EXIT = auto() + BLS_TO_EXECUTION_CHANGE = auto() + DEPOSIT_REQUEST = auto() + WITHDRAWAL_REQUEST = auto() + CONSOLIDATION_REQUEST = auto() + + +# TODO(jtraglia): Pretty sure this doesn't play well with eip7732. Needs some work. +def _set_operations_by_dict(spec, block, operation_dict, state): + 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_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + elif 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, state, sync_aggregate=None, operation_dict=None): + """ + Cribbed from ``transition_unsigned_block`` helper + where the early parts of the state transition have already + been applied to ``state``. + + Used to produce a block during an irregular state transition. + + The optional `operation_dict` is a dict of {'': }. + This is used for assigning the block operations. + p.s. we can't just pass `body` and assign it because randao_reveal and eth1_data was set in `build_empty_block` + Thus use dict to pass operations. + """ + block = build_empty_block(spec, state) + if sync_aggregate is not None: + block.body.sync_aggregate = sync_aggregate + + if operation_dict: + _set_operations_by_dict(spec, block, operation_dict, state) + + assert state.latest_block_header.slot < block.slot + assert state.slot == block.slot + spec.process_block(state, block) + block.state_root = state.hash_tree_root() + return sign_block(spec, state, block) + + +def _all_blocks(_): + return True + + +def skip_slots(*slots): + """ + Skip making a block if its slot is + passed as an argument to this filter + """ + + def f(state_at_prior_slot): + return state_at_prior_slot.slot + 1 not in slots + + return f + + +def no_blocks(_): + return False + + +def only_at(slot): + """ + Only produce a block if its slot is ``slot``. + """ + + def f(state_at_prior_slot): + return state_at_prior_slot.slot + 1 == slot + + return f + + +def state_transition_across_slots(spec, state, to_slot, block_filter=_all_blocks): + assert state.slot < to_slot + while state.slot < to_slot: + should_make_block = block_filter(state) + if should_make_block: + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield signed_block + else: + next_slot(spec, state) + + +def state_transition_across_slots_with_ignoring_proposers( + spec, state, to_slot, ignoring_proposers, only_last_block=False +): + """ + The slashed validators can't be proposers. Here we ignore the given `ignoring_proposers` + and ensure that the result state was computed with a block with slot >= to_slot. + """ + assert state.slot < to_slot + + found_valid = False + while state.slot < to_slot or not found_valid: + if state.slot + 1 < to_slot and only_last_block: + next_slot(spec, state) + continue + + future_state = state.copy() + next_slot(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + if proposer_index not in ignoring_proposers: + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield signed_block + if state.slot >= to_slot: + found_valid = True + else: + next_slot(spec, state) + + +def get_upgrade_fn(spec, fork): + # pylint: disable=unused-argument + # NOTE: `spec` is used for the `eval` call + assert fork in POST_FORK_OF.values() + try: + # TODO: make all upgrade_to_* function names consistent? + fn = eval(f"spec.upgrade_to_{fork}") + return fn + except Exception: + raise ValueError(f"Unknown fork: {fork}") + + +def do_fork( + state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate=None, operation_dict=None +): + spec.process_slots(state, state.slot + 1) + + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + assert spec.get_current_epoch(state) == fork_epoch + + state = get_upgrade_fn(post_spec, post_spec.fork)(state) + + assert state.fork.epoch == fork_epoch + + previous_fork = PREVIOUS_FORK_OF[post_spec.fork] + if previous_fork == PHASE0: + previous_version = spec.config.GENESIS_FORK_VERSION + else: + previous_version = getattr(post_spec.config, f"{previous_fork.upper()}_FORK_VERSION") + current_version = getattr(post_spec.config, f"{post_spec.fork.upper()}_FORK_VERSION") + + assert state.fork.previous_version == previous_version + assert state.fork.current_version == current_version + + if with_block: + return state, _state_transition_and_sign_block_at_slot( + post_spec, + state, + sync_aggregate=sync_aggregate, + operation_dict=operation_dict, + ) + else: + return state, None + + +def do_fork_generate( + state, spec, post_spec, fork_epoch, with_block=True, sync_aggregate=None, operation_dict=None +): + spec.process_slots(state, state.slot + 1) + + assert state.slot % spec.SLOTS_PER_EPOCH == 0 + assert spec.get_current_epoch(state) == fork_epoch + + yield "pre", state + + state = get_upgrade_fn(post_spec, post_spec.fork)(state) + + yield "post", state + + assert state.fork.epoch == fork_epoch + + previous_fork = PREVIOUS_FORK_OF[post_spec.fork] + if previous_fork == PHASE0: + previous_version = spec.config.GENESIS_FORK_VERSION + else: + previous_version = getattr(post_spec.config, f"{previous_fork.upper()}_FORK_VERSION") + current_version = getattr(post_spec.config, f"{post_spec.fork.upper()}_FORK_VERSION") + + assert state.fork.previous_version == previous_version + assert state.fork.current_version == current_version + + if with_block: + return state, _state_transition_and_sign_block_at_slot( + post_spec, + state, + sync_aggregate=sync_aggregate, + operation_dict=operation_dict, + ) + else: + return state, None + + +def transition_until_fork(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 1 + transition_to(spec, state, to_slot) + + +def _transition_until_fork_minus_one(spec, state, fork_epoch): + to_slot = fork_epoch * spec.SLOTS_PER_EPOCH - 2 + transition_to(spec, state, to_slot) + + +def transition_across_forks( + spec, state, to_slot, phases=None, with_block=False, sync_aggregate=None +): + assert to_slot > state.slot + state = state.copy() + block = None + to_epoch = spec.compute_epoch_at_slot(to_slot) + while state.slot < to_slot: + assert block is None + epoch = spec.compute_epoch_at_slot(state.slot) + post_spec, fork_epoch = get_next_fork_transition(spec, epoch, phases) + if fork_epoch is None or to_epoch < fork_epoch: + if with_block and (to_slot == state.slot + 1): + transition_to(spec, state, to_slot - 1) + block = state_transition_with_full_block( + spec, state, True, True, sync_aggregate=sync_aggregate + ) + else: + transition_to(spec, state, to_slot) + else: + transition_until_fork(spec, state, fork_epoch) + state, block = do_fork( + state, + spec, + post_spec, + fork_epoch, + with_block=with_block and (to_slot == state.slot + 1), + sync_aggregate=sync_aggregate, + ) + spec = post_spec + return spec, state, block + + +def transition_to_next_epoch_and_append_blocks( + spec, state, post_tag, blocks, only_last_block=False, ignoring_proposers=None +): + to_slot = spec.SLOTS_PER_EPOCH + state.slot + + if only_last_block: + block_filter = only_at(to_slot) + else: + block_filter = _all_blocks + + if ignoring_proposers is None: + result_blocks = state_transition_across_slots( + spec, state, to_slot, block_filter=block_filter + ) + else: + result_blocks = state_transition_across_slots_with_ignoring_proposers( + spec, + state, + to_slot, + ignoring_proposers, + only_last_block=only_last_block, + ) + + blocks.extend([post_tag(block) for block in result_blocks]) + + +def run_transition_with_operation( + state, fork_epoch, spec, post_spec, pre_tag, post_tag, operation_type, operation_at_slot +): + """ + Generate `operation_type` operation with the spec before fork. + The operation would be included into the block at `operation_at_slot`. + """ + is_at_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH + is_right_before_fork = operation_at_slot == fork_epoch * spec.SLOTS_PER_EPOCH - 1 + assert is_at_fork or is_right_before_fork + + if is_at_fork: + transition_until_fork(spec, state, fork_epoch) + elif is_right_before_fork: + _transition_until_fork_minus_one(spec, state, fork_epoch) + + is_slashing_operation = operation_type in ( + OperationType.PROPOSER_SLASHING, + OperationType.ATTESTER_SLASHING, + ) + # prepare operation + selected_validator_index = None + if is_slashing_operation: + # avoid slashing the next proposer + future_state = state.copy() + next_slot(spec, future_state) + proposer_index = spec.get_beacon_proposer_index(future_state) + selected_validator_index = (proposer_index + 1) % len(state.validators) + if operation_type == OperationType.PROPOSER_SLASHING: + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=selected_validator_index, signed_1=True, signed_2=True + ) + operation_dict = {"proposer_slashings": [proposer_slashing]} + else: + # operation_type == OperationType.ATTESTER_SLASHING: + if is_at_fork and spec.fork == DENEB: + # NOTE: attestation format changes between Deneb and Electra + # so attester slashing must be made with the `post_spec` + target_spec = post_spec + target_state = post_spec.upgrade_to_electra(state.copy()) + target_state.fork = state.fork + else: + target_spec = spec + target_state = state + + attester_slashing = get_valid_attester_slashing_by_indices( + target_spec, + target_state, + [selected_validator_index], + signed_1=True, + signed_2=True, + ) + operation_dict = {"attester_slashings": [attester_slashing]} + elif operation_type == OperationType.DEPOSIT: + # create a new deposit + selected_validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit( + spec, state, selected_validator_index, amount, signed=True + ) + operation_dict = {"deposits": [deposit]} + elif operation_type == OperationType.VOLUNTARY_EXIT: + selected_validator_index = 0 + signed_exits = prepare_signed_exits(spec, state, [selected_validator_index]) + operation_dict = {"voluntary_exits": signed_exits} + elif operation_type == OperationType.BLS_TO_EXECUTION_CHANGE: + selected_validator_index = 0 + bls_to_execution_changes = [ + get_signed_address_change(spec, state, selected_validator_index) + ] + operation_dict = {"bls_to_execution_changes": bls_to_execution_changes} + elif operation_type == OperationType.DEPOSIT_REQUEST: + # create a new deposit request + selected_validator_index = len(state.validators) + amount = post_spec.MIN_ACTIVATION_BALANCE + deposit_request = prepare_deposit_request( + post_spec, selected_validator_index, amount, signed=True + ) + operation_dict = {"execution_requests.deposits": [deposit_request]} + elif operation_type == OperationType.WITHDRAWAL_REQUEST: + selected_validator_index = 0 + withdrawal_request = prepare_withdrawal_request( + post_spec, state, selected_validator_index, amount=post_spec.FULL_EXIT_REQUEST_AMOUNT + ) + operation_dict = {"execution_requests.withdrawals": [withdrawal_request]} + elif operation_type == OperationType.CONSOLIDATION_REQUEST: + selected_validator_index = 0 + consolidation_request = prepare_switch_to_compounding_request( + post_spec, state, selected_validator_index + ) + operation_dict = {"execution_requests.consolidations": [consolidation_request]} + + def _check_state(): + if operation_type == OperationType.PROPOSER_SLASHING: + slashed_proposer = state.validators[ + proposer_slashing.signed_header_1.message.proposer_index + ] + assert slashed_proposer.slashed + elif operation_type == OperationType.ATTESTER_SLASHING: + indices = set(attester_slashing.attestation_1.attesting_indices).intersection( + attester_slashing.attestation_2.attesting_indices + ) + assert selected_validator_index in indices + assert len(indices) > 0 + for validator_index in indices: + assert state.validators[validator_index].slashed + elif operation_type == OperationType.DEPOSIT: + assert not post_spec.is_active_validator( + state.validators[selected_validator_index], post_spec.get_current_epoch(state) + ) + elif operation_type == OperationType.VOLUNTARY_EXIT: + validator = state.validators[selected_validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + elif operation_type == OperationType.BLS_TO_EXECUTION_CHANGE: + validator = state.validators[selected_validator_index] + assert validator.withdrawal_credentials[:1] == spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + elif operation_type == OperationType.DEPOSIT_REQUEST: + assert state.pending_deposits == [ + post_spec.PendingDeposit( + pubkey=deposit_request.pubkey, + withdrawal_credentials=deposit_request.withdrawal_credentials, + amount=deposit_request.amount, + signature=deposit_request.signature, + slot=state.slot, + ) + ] + elif operation_type == OperationType.WITHDRAWAL_REQUEST: + validator = state.validators[selected_validator_index] + assert validator.exit_epoch < post_spec.FAR_FUTURE_EPOCH + elif operation_type == OperationType.CONSOLIDATION_REQUEST: + validator = state.validators[selected_validator_index] + assert validator.withdrawal_credentials[:1] == post_spec.COMPOUNDING_WITHDRAWAL_PREFIX + + yield "pre", state + + blocks = [] + + if is_right_before_fork: + # add a block with operation. + block = build_empty_block_for_next_slot(spec, state) + _set_operations_by_dict(spec, block, operation_dict, state) + signed_block = state_transition_and_sign_block(spec, state, block) + blocks.append(pre_tag(signed_block)) + + _check_state() + + # irregular state transition to handle fork: + _operation_at_slot = operation_dict if is_at_fork else None + state, block = do_fork(state, spec, post_spec, fork_epoch, operation_dict=_operation_at_slot) + blocks.append(post_tag(block)) + + if is_at_fork: + _check_state() + + # after the fork + if operation_type == OperationType.DEPOSIT: + state = _transition_until_active( + post_spec, state, post_tag, blocks, selected_validator_index + ) + else: + # avoid using the slashed validators as block proposers + ignoring_proposers = [selected_validator_index] if is_slashing_operation else None + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, + state, + post_tag, + blocks, + only_last_block=True, + ignoring_proposers=ignoring_proposers, + ) + + yield "blocks", blocks + yield "post", state + + +def _transition_until_active(post_spec, state, post_tag, blocks, validator_index): + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks(post_spec, state, post_tag, blocks) + epochs_required_to_activate = 2 + if is_post_electra(post_spec): + # NOTE: an extra epoch is required given the way balance updates + # changed with electra + epochs_required_to_activate = 3 + # finalize activation_eligibility_epoch + _, blocks_in_epoch, state = next_slots_with_attestations( + post_spec, + state, + post_spec.SLOTS_PER_EPOCH * epochs_required_to_activate, + fill_cur_epoch=True, + fill_prev_epoch=True, + ) + blocks.extend([post_tag(block) for block in blocks_in_epoch]) + assert ( + state.finalized_checkpoint.epoch + >= state.validators[validator_index].activation_eligibility_epoch + ) + + # continue regular state transition with new spec into next epoch + transition_to_next_epoch_and_append_blocks( + post_spec, state, post_tag, blocks, only_last_block=True + ) + + assert state.validators[validator_index].activation_epoch < post_spec.FAR_FUTURE_EPOCH + + to_slot = state.validators[validator_index].activation_epoch * post_spec.SLOTS_PER_EPOCH + blocks.extend( + [ + post_tag(block) + for block in state_transition_across_slots( + post_spec, state, to_slot, block_filter=only_at(to_slot) + ) + ] + ) + assert post_spec.is_active_validator( + state.validators[validator_index], post_spec.get_current_epoch(state) + ) + + return state diff --git a/tests/core/pyspec/eth2spec/test/helpers/forks.py b/tests/core/pyspec/eth2spec/test/helpers/forks.py new file mode 100644 index 0000000000..59e5852ae1 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/forks.py @@ -0,0 +1,84 @@ +from .constants import ( + ALTAIR, + BELLATRIX, + CAPELLA, + DENEB, + EIP7441, + EIP7732, + ELECTRA, + FULU, + PHASE0, + PREVIOUS_FORK_OF, +) + + +def is_post_fork(a, b) -> bool: + """ + Returns true if fork a is after b, or if a == b + """ + if a == b: + return True + + prev_fork = PREVIOUS_FORK_OF[a] + if prev_fork == b: + return True + elif prev_fork is None: + return False + else: + return is_post_fork(prev_fork, b) + + +def is_post_altair(spec): + return is_post_fork(spec.fork, ALTAIR) + + +def is_post_bellatrix(spec): + return is_post_fork(spec.fork, BELLATRIX) + + +def is_post_capella(spec): + return is_post_fork(spec.fork, CAPELLA) + + +def is_post_deneb(spec): + return is_post_fork(spec.fork, DENEB) + + +def is_post_electra(spec): + return is_post_fork(spec.fork, ELECTRA) + + +def is_post_fulu(spec): + return is_post_fork(spec.fork, FULU) + + +def is_post_eip7441(spec): + return is_post_fork(spec.fork, EIP7441) + + +def is_post_eip7732(spec): + return is_post_fork(spec.fork, EIP7732) + + +def get_spec_for_fork_version(spec, fork_version, phases): + if phases is None: + return spec + for fork in [fork for fork in phases if is_post_fork(spec.fork, fork)]: + if fork == PHASE0: + fork_version_field = "GENESIS_FORK_VERSION" + else: + fork_version_field = fork.upper() + "_FORK_VERSION" + if fork_version == getattr(spec.config, fork_version_field): + return phases[fork] + raise ValueError(f"Unknown fork version {fork_version}") + + +def get_next_fork_transition(spec, epoch, phases): + if phases is None: + return None, None + for fork in [fork for fork in phases if PREVIOUS_FORK_OF[fork] == spec.fork]: + assert fork != PHASE0 # PHASE0 does not have previous fork + fork_epoch = getattr(phases[fork].config, fork.upper() + "_FORK_EPOCH") + assert fork_epoch > epoch # Forks through given epoch already applied + return phases[fork], fork_epoch + return None, None # Already at latest fork diff --git a/tests/core/pyspec/eth2spec/test/helpers/fulu/__init__.py b/tests/core/pyspec/eth2spec/test/helpers/fulu/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py b/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py new file mode 100644 index 0000000000..0f2ad6c344 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/fulu/fork.py @@ -0,0 +1,82 @@ +from eth2spec.test.helpers.constants import ( + FULU, +) + +FULU_FORK_TEST_META_TAGS = { + "fork": FULU, +} + + +def run_fork_test(post_spec, pre_state): + yield "pre", pre_state + + post_state = post_spec.upgrade_to_fulu(pre_state) + + # Stable fields + stable_fields = [ + "genesis_time", + "genesis_validators_root", + "slot", + # History + "latest_block_header", + "block_roots", + "state_roots", + "historical_roots", + # Eth1 + "eth1_data", + "eth1_data_votes", + "eth1_deposit_index", + # Registry + # NOTE: 'validators', 'balances' could be changed. + # Randomness + "randao_mixes", + # Slashings + "slashings", + # Participation + "previous_epoch_participation", + "current_epoch_participation", + # Finality + "justification_bits", + "previous_justified_checkpoint", + "current_justified_checkpoint", + "finalized_checkpoint", + # Inactivity + "inactivity_scores", + # Sync + "current_sync_committee", + "next_sync_committee", + # Withdrawals + "next_withdrawal_index", + "next_withdrawal_validator_index", + # Deep history valid from Capella onwards + "historical_summaries", + "latest_execution_payload_header", + ] + for field in stable_fields: + assert getattr(pre_state, field) == getattr(post_state, field) + + # Modified fields + modified_fields = ["fork"] + for field in modified_fields: + assert getattr(pre_state, field) != getattr(post_state, field) + + assert len(pre_state.validators) == len(post_state.validators) + for pre_validator, post_validator in zip(pre_state.validators, post_state.validators): + stable_validator_fields = [ + "pubkey", + "withdrawal_credentials", + "slashed", + "activation_epoch", + "exit_epoch", + "withdrawable_epoch", + ] + for field in stable_validator_fields: + assert getattr(pre_validator, field) == getattr(post_validator, field) + + assert pre_state.fork.current_version == post_state.fork.previous_version + assert post_state.fork.current_version == post_spec.config.FULU_FORK_VERSION + assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) + + yield "post", post_state + + return post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/genesis.py b/tests/core/pyspec/eth2spec/test/helpers/genesis.py index 0e9af4cff9..67061c560a 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/genesis.py +++ b/tests/core/pyspec/eth2spec/test/helpers/genesis.py @@ -1,37 +1,143 @@ +from hashlib import sha256 + from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, - FORKS_BEFORE_ALTAIR, FORKS_BEFORE_MERGE, + PHASE0, + PREVIOUS_FORK_OF, +) +from eth2spec.test.helpers.eip7441 import ( + compute_whisk_initial_k_commitment_cached, + compute_whisk_initial_tracker_cached, +) +from eth2spec.test.helpers.execution_payload import ( + compute_el_header_block_hash, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_bellatrix, + is_post_capella, + is_post_deneb, + is_post_eip7441, + is_post_eip7732, + is_post_electra, + is_post_fulu, ) from eth2spec.test.helpers.keys import pubkeys def build_mock_validator(spec, i: int, balance: int): - pubkey = pubkeys[i] - # insecurely use pubkey as withdrawal key as well - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] - return spec.Validator( - pubkey=pubkeys[i], + active_pubkey = pubkeys[i] + withdrawal_pubkey = pubkeys[-1 - i] + if is_post_electra(spec): + if balance > spec.MIN_ACTIVATION_BALANCE: + # use compounding withdrawal credentials if the balance is higher than MIN_ACTIVATION_BALANCE + withdrawal_credentials = ( + spec.COMPOUNDING_WITHDRAWAL_PREFIX + + b"\x00" * 11 + + spec.hash(withdrawal_pubkey)[12:] + ) + else: + # insecurely use pubkey as withdrawal key as well + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] + max_effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + else: + # insecurely use pubkey as withdrawal key as well + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] + max_effective_balance = spec.MAX_EFFECTIVE_BALANCE + + validator = spec.Validator( + pubkey=active_pubkey, withdrawal_credentials=withdrawal_credentials, activation_eligibility_epoch=spec.FAR_FUTURE_EPOCH, activation_epoch=spec.FAR_FUTURE_EPOCH, exit_epoch=spec.FAR_FUTURE_EPOCH, withdrawable_epoch=spec.FAR_FUTURE_EPOCH, - effective_balance=min(balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE) + effective_balance=min( + balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, max_effective_balance + ), + ) + + return validator + + +def get_post_eip7732_genesis_execution_payload_header(spec, slot, eth1_block_hash): + kzgs = spec.List[spec.KZGCommitment, spec.MAX_BLOB_COMMITMENTS_PER_BLOCK]() + header = spec.ExecutionPayloadHeader( + parent_block_hash=b"\x30" * 32, + parent_block_root=b"\x00" * 32, + block_hash=eth1_block_hash, + gas_limit=30000000, + slot=slot, + blob_kzg_commitments_root=kzgs.hash_tree_root(), ) + return header + + +def get_sample_genesis_execution_payload_header(spec, slot, eth1_block_hash=None): + if eth1_block_hash is None: + eth1_block_hash = b"\x55" * 32 + if is_post_eip7732(spec): + return get_post_eip7732_genesis_execution_payload_header(spec, slot, eth1_block_hash) + payload_header = spec.ExecutionPayloadHeader( + parent_hash=b"\x30" * 32, + fee_recipient=b"\x42" * 20, + state_root=b"\x20" * 32, + receipts_root=b"\x20" * 32, + logs_bloom=b"\x35" * spec.BYTES_PER_LOGS_BLOOM, + prev_randao=eth1_block_hash, + block_number=0, + gas_limit=30000000, + base_fee_per_gas=1000000000, + block_hash=eth1_block_hash, + transactions_root=spec.Root(b"\x56" * 32), + ) + + transactions_trie_root = bytes.fromhex( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) + withdrawals_trie_root = None + parent_beacon_block_root = None + requests_hash = None + + if is_post_capella(spec): + withdrawals_trie_root = bytes.fromhex( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + ) + if is_post_deneb(spec): + parent_beacon_block_root = bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + if is_post_electra(spec): + requests_hash = sha256(b"").digest() + + payload_header.block_hash = compute_el_header_block_hash( + spec, + payload_header, + transactions_trie_root, + withdrawals_trie_root, + parent_beacon_block_root, + requests_hash, + ) + return payload_header def create_genesis_state(spec, validator_balances, activation_threshold): - deposit_root = b'\x42' * 32 + deposit_root = b"\x42" * 32 - eth1_block_hash = b'\xda' * 32 + eth1_block_hash = b"\xda" * 32 previous_version = spec.config.GENESIS_FORK_VERSION current_version = spec.config.GENESIS_FORK_VERSION - if spec.fork == ALTAIR: - current_version = spec.config.ALTAIR_FORK_VERSION - elif spec.fork == MERGE: - previous_version = spec.config.ALTAIR_FORK_VERSION - current_version = spec.config.MERGE_FORK_VERSION + if spec.fork != PHASE0: + previous_fork = PREVIOUS_FORK_OF[spec.fork] + if previous_fork == PHASE0: + previous_version = spec.config.GENESIS_FORK_VERSION + else: + previous_version = getattr(spec.config, f"{previous_fork.upper()}_FORK_VERSION") + current_version = getattr(spec.config, f"{spec.fork.upper()}_FORK_VERSION") + + genesis_block_body = spec.BeaconBlockBody() + if is_post_eip7732(spec): + genesis_block_body.signed_execution_payload_header.message.block_hash = eth1_block_hash state = spec.BeaconState( genesis_time=0, @@ -46,21 +152,25 @@ def create_genesis_state(spec, validator_balances, activation_threshold): current_version=current_version, epoch=spec.GENESIS_EPOCH, ), - latest_block_header=spec.BeaconBlockHeader(body_root=spec.hash_tree_root(spec.BeaconBlockBody())), + latest_block_header=spec.BeaconBlockHeader( + body_root=spec.hash_tree_root(genesis_block_body) + ), randao_mixes=[eth1_block_hash] * spec.EPOCHS_PER_HISTORICAL_VECTOR, ) # We "hack" in the initial validators, # as it is much faster than creating and processing genesis deposits for every single test case. state.balances = validator_balances - state.validators = [build_mock_validator(spec, i, state.balances[i]) for i in range(len(validator_balances))] + state.validators = [ + build_mock_validator(spec, i, state.balances[i]) for i in range(len(validator_balances)) + ] # Process genesis activations for validator in state.validators: if validator.effective_balance >= activation_threshold: validator.activation_eligibility_epoch = spec.GENESIS_EPOCH validator.activation_epoch = spec.GENESIS_EPOCH - if spec.fork not in FORKS_BEFORE_ALTAIR: + if is_post_altair(spec): state.previous_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) state.current_epoch_participation.append(spec.ParticipationFlags(0b0000_0000)) state.inactivity_scores.append(spec.uint64(0)) @@ -68,17 +178,54 @@ def create_genesis_state(spec, validator_balances, activation_threshold): # Set genesis validators root for domain separation and chain versioning state.genesis_validators_root = spec.hash_tree_root(state.validators) - if spec.fork not in FORKS_BEFORE_ALTAIR: + if is_post_altair(spec): # Fill in sync committees # Note: A duplicate committee is assigned for the current and next committee at genesis state.current_sync_committee = spec.get_next_sync_committee(state) state.next_sync_committee = spec.get_next_sync_committee(state) - if spec.fork not in FORKS_BEFORE_MERGE: + if is_post_bellatrix(spec): # Initialize the execution payload header (with block number and genesis time set to 0) - state.latest_execution_payload_header.block_hash = eth1_block_hash - state.latest_execution_payload_header.random = eth1_block_hash - state.latest_execution_payload_header.gas_limit = spec.GENESIS_GAS_LIMIT - state.latest_execution_payload_header.base_fee_per_gas = spec.GENESIS_BASE_FEE_PER_GAS + state.latest_execution_payload_header = get_sample_genesis_execution_payload_header( + spec, + spec.compute_start_slot_at_epoch(spec.GENESIS_EPOCH), + eth1_block_hash=eth1_block_hash, + ) + + if is_post_electra(spec): + state.deposit_requests_start_index = spec.UNSET_DEPOSIT_REQUESTS_START_INDEX + + if is_post_eip7441(spec): + vc = len(state.validators) + for i in range(vc): + state.whisk_k_commitments.append(compute_whisk_initial_k_commitment_cached(i)) + state.whisk_trackers.append(compute_whisk_initial_tracker_cached(i)) + + for i in range(spec.CANDIDATE_TRACKERS_COUNT): + state.whisk_candidate_trackers[i] = compute_whisk_initial_tracker_cached(i % vc) + + for i in range(spec.PROPOSER_TRACKERS_COUNT): + state.whisk_proposer_trackers[i] = compute_whisk_initial_tracker_cached(i % vc) + + if is_post_electra(spec): + state.deposit_balance_to_consume = 0 + state.exit_balance_to_consume = 0 + state.earliest_exit_epoch = spec.GENESIS_EPOCH + state.consolidation_balance_to_consume = 0 + state.earliest_consolidation_epoch = 0 + state.pending_deposits = [] + state.pending_partial_withdrawals = [] + state.pending_consolidations = [] + + if is_post_eip7732(spec): + withdrawals = spec.List[spec.Withdrawal, spec.MAX_WITHDRAWALS_PER_PAYLOAD]() + state.latest_withdrawals_root = withdrawals.hash_tree_root() + state.latest_block_hash = ( + state.latest_execution_payload_header.block_hash + ) # last block is full + + if is_post_fulu(spec): + # Initialize proposer lookahead list + state.proposer_lookahead = spec.initialize_proposer_lookahead(state) return state diff --git a/tests/core/pyspec/eth2spec/test/helpers/keys.py b/tests/core/pyspec/eth2spec/test/helpers/keys.py index 5e36e90df6..a849f11320 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/keys.py +++ b/tests/core/pyspec/eth2spec/test/helpers/keys.py @@ -4,3 +4,18 @@ privkeys = [i + 1 for i in range(32 * 256)] pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] pubkey_to_privkey = {pubkey: privkey for privkey, pubkey in zip(privkeys, pubkeys)} + +known_whisk_trackers = {} + + +def register_known_whisk_tracker(k_r_G: bytes, index: int): + known_whisk_trackers[k_r_G] = index + + +def whisk_ks_initial(i: int): + return i + + +# Must be unique among the set `whisk_ks_initial + whisk_ks_final` +def whisk_ks_final(i: int): + return i + 10000000 diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client.py b/tests/core/pyspec/eth2spec/test/helpers/light_client.py new file mode 100644 index 0000000000..c15c45e06b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client.py @@ -0,0 +1,302 @@ +from math import floor + +from eth2spec.test.helpers.constants import ( + CAPELLA, + DENEB, + ELECTRA, +) +from eth2spec.test.helpers.fork_transition import ( + transition_across_forks, +) +from eth2spec.test.helpers.forks import is_post_capella, is_post_deneb, is_post_electra +from eth2spec.test.helpers.sync_committee import ( + compute_aggregate_sync_committee_signature, + compute_committee_indices, +) + + +def latest_finalized_root_gindex(spec): + if hasattr(spec, "FINALIZED_ROOT_GINDEX_ELECTRA"): + return spec.FINALIZED_ROOT_GINDEX_ELECTRA + return spec.FINALIZED_ROOT_GINDEX + + +def latest_current_sync_committee_gindex(spec): + if hasattr(spec, "CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA"): + return spec.CURRENT_SYNC_COMMITTEE_GINDEX_ELECTRA + return spec.CURRENT_SYNC_COMMITTEE_GINDEX + + +def latest_next_sync_committee_gindex(spec): + if hasattr(spec, "NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA"): + return spec.NEXT_SYNC_COMMITTEE_GINDEX_ELECTRA + return spec.NEXT_SYNC_COMMITTEE_GINDEX + + +def latest_normalize_merkle_branch(spec, branch, gindex): + if hasattr(spec, "normalize_merkle_branch"): + return spec.normalize_merkle_branch(branch, gindex) + return branch + + +def compute_start_slot_at_sync_committee_period(spec, sync_committee_period): + return spec.compute_start_slot_at_epoch( + sync_committee_period * spec.EPOCHS_PER_SYNC_COMMITTEE_PERIOD + ) + + +def compute_start_slot_at_next_sync_committee_period(spec, state): + sync_committee_period = spec.compute_sync_committee_period_at_slot(state.slot) + return compute_start_slot_at_sync_committee_period(spec, sync_committee_period + 1) + + +def get_sync_aggregate(spec, state, num_participants=None, signature_slot=None, phases=None): + # By default, the sync committee signs the previous slot + if signature_slot is None: + signature_slot = state.slot + 1 + assert signature_slot > state.slot + + # Ensure correct sync committee and fork version are selected + signature_spec, signature_state, _ = transition_across_forks( + spec, state, signature_slot, phases + ) + + # Fetch sync committee + committee_indices = compute_committee_indices(signature_state) + committee_size = len(committee_indices) + + # By default, use full participation + if num_participants is None: + num_participants = committee_size + assert committee_size >= num_participants >= 0 + + # Compute sync aggregate + sync_committee_bits = [True] * num_participants + [False] * (committee_size - num_participants) + sync_committee_signature = compute_aggregate_sync_committee_signature( + signature_spec, + signature_state, + max(signature_slot, 1) - 1, + committee_indices[:num_participants], + ) + sync_aggregate = signature_spec.SyncAggregate( + sync_committee_bits=sync_committee_bits, + sync_committee_signature=sync_committee_signature, + ) + return sync_aggregate, signature_slot + + +def create_update( + spec, + attested_state, + attested_block, + finalized_block, + with_next, + with_finality, + participation_rate, + signature_slot=None, +): + num_participants = floor(spec.SYNC_COMMITTEE_SIZE * participation_rate) + + update = spec.LightClientUpdate() + + update.attested_header = spec.block_to_light_client_header(attested_block) + + if with_next: + update.next_sync_committee = attested_state.next_sync_committee + update.next_sync_committee_branch = spec.compute_merkle_proof( + attested_state, latest_next_sync_committee_gindex(spec) + ) + + if with_finality: + update.finalized_header = spec.block_to_light_client_header(finalized_block) + update.finality_branch = spec.compute_merkle_proof( + attested_state, latest_finalized_root_gindex(spec) + ) + + update.sync_aggregate, update.signature_slot = get_sync_aggregate( + spec, attested_state, num_participants, signature_slot=signature_slot + ) + + return update + + +def needs_upgrade_to_capella(spec, new_spec): + return is_post_capella(new_spec) and not is_post_capella(spec) + + +def needs_upgrade_to_deneb(spec, new_spec): + return is_post_deneb(new_spec) and not is_post_deneb(spec) + + +def needs_upgrade_to_electra(spec, new_spec): + return is_post_electra(new_spec) and not is_post_electra(spec) + + +def check_merkle_branch_equal(spec, new_spec, data, upgraded, gindex): + if is_post_electra(new_spec): + assert new_spec.normalize_merkle_branch( + upgraded, gindex + ) == new_spec.normalize_merkle_branch(data, gindex) + else: + assert upgraded == data + + +def check_lc_header_equal(spec, new_spec, data, upgraded): + assert upgraded.beacon.slot == data.beacon.slot + assert upgraded.beacon.hash_tree_root() == data.beacon.hash_tree_root() + if is_post_capella(new_spec): + if is_post_capella(spec): + assert new_spec.get_lc_execution_root(upgraded) == spec.get_lc_execution_root(data) + else: + assert new_spec.get_lc_execution_root(upgraded) == new_spec.Root() + + +def upgrade_lc_header_to_new_spec(spec, new_spec, data, phases): + upgraded = data + + if needs_upgrade_to_capella(spec, new_spec): + upgraded = phases[CAPELLA].upgrade_lc_header_to_capella(upgraded) + check_lc_header_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_deneb(spec, new_spec): + upgraded = phases[DENEB].upgrade_lc_header_to_deneb(upgraded) + check_lc_header_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_header_to_electra(upgraded) + check_lc_header_equal(spec, new_spec, data, upgraded) + + return upgraded + + +def check_lc_bootstrap_equal(spec, new_spec, data, upgraded): + check_lc_header_equal(spec, new_spec, data.header, upgraded.header) + assert upgraded.current_sync_committee == data.current_sync_committee + check_merkle_branch_equal( + spec, + new_spec, + data.current_sync_committee_branch, + upgraded.current_sync_committee_branch, + latest_current_sync_committee_gindex(new_spec), + ) + + +def upgrade_lc_bootstrap_to_new_spec(spec, new_spec, data, phases): + upgraded = data + + if needs_upgrade_to_capella(spec, new_spec): + upgraded = phases[CAPELLA].upgrade_lc_bootstrap_to_capella(upgraded) + check_lc_bootstrap_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_deneb(spec, new_spec): + upgraded = phases[DENEB].upgrade_lc_bootstrap_to_deneb(upgraded) + check_lc_bootstrap_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_bootstrap_to_electra(upgraded) + check_lc_bootstrap_equal(spec, new_spec, data, upgraded) + + return upgraded + + +def check_lc_update_equal(spec, new_spec, data, upgraded): + check_lc_header_equal(spec, new_spec, data.attested_header, upgraded.attested_header) + assert upgraded.next_sync_committee == data.next_sync_committee + check_merkle_branch_equal( + spec, + new_spec, + data.next_sync_committee_branch, + upgraded.next_sync_committee_branch, + latest_next_sync_committee_gindex(new_spec), + ) + check_lc_header_equal(spec, new_spec, data.finalized_header, upgraded.finalized_header) + check_merkle_branch_equal( + spec, + new_spec, + data.finality_branch, + upgraded.finality_branch, + latest_finalized_root_gindex(new_spec), + ) + assert upgraded.sync_aggregate == data.sync_aggregate + assert upgraded.signature_slot == data.signature_slot + + +def upgrade_lc_update_to_new_spec(spec, new_spec, data, phases): + upgraded = data + + if needs_upgrade_to_capella(spec, new_spec): + upgraded = phases[CAPELLA].upgrade_lc_update_to_capella(upgraded) + check_lc_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_deneb(spec, new_spec): + upgraded = phases[DENEB].upgrade_lc_update_to_deneb(upgraded) + check_lc_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_update_to_electra(upgraded) + check_lc_update_equal(spec, new_spec, data, upgraded) + + return upgraded + + +def check_lc_finality_update_equal(spec, new_spec, data, upgraded): + check_lc_header_equal(spec, new_spec, data.attested_header, upgraded.attested_header) + check_lc_header_equal(spec, new_spec, data.finalized_header, upgraded.finalized_header) + check_merkle_branch_equal( + spec, + new_spec, + data.finality_branch, + upgraded.finality_branch, + latest_finalized_root_gindex(new_spec), + ) + assert upgraded.sync_aggregate == data.sync_aggregate + assert upgraded.signature_slot == data.signature_slot + + +def upgrade_lc_finality_update_to_new_spec(spec, new_spec, data, phases): + upgraded = data + + if needs_upgrade_to_capella(spec, new_spec): + upgraded = phases[CAPELLA].upgrade_lc_finality_update_to_capella(upgraded) + check_lc_finality_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_deneb(spec, new_spec): + upgraded = phases[DENEB].upgrade_lc_finality_update_to_deneb(upgraded) + check_lc_finality_update_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_finality_update_to_electra(upgraded) + check_lc_finality_update_equal(spec, new_spec, data, upgraded) + + return upgraded + + +def check_lc_store_equal(spec, new_spec, data, upgraded): + check_lc_header_equal(spec, new_spec, data.finalized_header, upgraded.finalized_header) + assert upgraded.current_sync_committee == data.current_sync_committee + assert upgraded.next_sync_committee == data.next_sync_committee + if upgraded.best_valid_update is None: + assert data.best_valid_update is None + else: + check_lc_update_equal(spec, new_spec, data.best_valid_update, upgraded.best_valid_update) + check_lc_header_equal(spec, new_spec, data.optimistic_header, upgraded.optimistic_header) + assert upgraded.previous_max_active_participants == data.previous_max_active_participants + assert upgraded.current_max_active_participants == data.current_max_active_participants + + +def upgrade_lc_store_to_new_spec(spec, new_spec, data, phases): + upgraded = data + + if needs_upgrade_to_capella(spec, new_spec): + upgraded = phases[CAPELLA].upgrade_lc_store_to_capella(upgraded) + check_lc_store_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_deneb(spec, new_spec): + upgraded = phases[DENEB].upgrade_lc_store_to_deneb(upgraded) + check_lc_store_equal(spec, new_spec, data, upgraded) + + if needs_upgrade_to_electra(spec, new_spec): + upgraded = phases[ELECTRA].upgrade_lc_store_to_electra(upgraded) + check_lc_store_equal(spec, new_spec, data, upgraded) + + return upgraded diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client_data_collection.py b/tests/core/pyspec/eth2spec/test/helpers/light_client_data_collection.py new file mode 100644 index 0000000000..712d551034 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client_data_collection.py @@ -0,0 +1,998 @@ +from dataclasses import dataclass +from typing import Any + +from eth_utils import encode_hex + +from eth2spec.test.helpers.constants import ( + ALTAIR, +) +from eth2spec.test.helpers.fork_transition import ( + transition_across_forks, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, +) +from eth2spec.test.helpers.light_client import ( + compute_start_slot_at_sync_committee_period, + get_sync_aggregate, + latest_current_sync_committee_gindex, + latest_finalized_root_gindex, + latest_next_sync_committee_gindex, + latest_normalize_merkle_branch, + upgrade_lc_header_to_new_spec, + upgrade_lc_update_to_new_spec, +) + + +def _next_epoch_boundary_slot(spec, slot): + # Compute the first possible epoch boundary state slot of a `Checkpoint` + # referring to a block at given slot. + epoch = spec.compute_epoch_at_slot(slot + spec.SLOTS_PER_EPOCH - 1) + return spec.compute_start_slot_at_epoch(epoch) + + +@dataclass(frozen=True) +class BlockID: + slot: Any + root: Any + + +def _block_to_block_id(block): + return BlockID( + slot=block.message.slot, + root=block.message.hash_tree_root(), + ) + + +def _state_to_block_id(state): + parent_header = state.latest_block_header.copy() + parent_header.state_root = state.hash_tree_root() + return BlockID(slot=parent_header.slot, root=parent_header.hash_tree_root()) + + +def get_lc_bootstrap_block_id(bootstrap): + return BlockID( + slot=bootstrap.header.beacon.slot, + root=bootstrap.header.beacon.hash_tree_root(), + ) + + +def get_lc_update_attested_block_id(update): + return BlockID( + slot=update.attested_header.beacon.slot, + root=update.attested_header.beacon.hash_tree_root(), + ) + + +@dataclass +class ForkedBeaconState: + spec: Any + data: Any + + +@dataclass +class ForkedSignedBeaconBlock: + spec: Any + data: Any + + +@dataclass +class ForkedLightClientHeader: + spec: Any + data: Any + + +@dataclass +class ForkedLightClientBootstrap: + spec: Any + data: Any + + +@dataclass +class ForkedLightClientUpdate: + spec: Any + data: Any + + +@dataclass +class ForkedLightClientFinalityUpdate: + spec: Any + data: Any + + +@dataclass +class ForkedLightClientOptimisticUpdate: + spec: Any + data: Any + + +@dataclass +class CachedLightClientData: + # Sync committee branches at block's post-state + current_sync_committee_branch: Any # CurrentSyncCommitteeBranch + next_sync_committee_branch: Any # NextSyncCommitteeBranch + + # Finality information at block's post-state + finalized_slot: Any # Slot + finality_branch: Any # FinalityBranch + + # Best / latest light client data + current_period_best_update: ForkedLightClientUpdate + latest_signature_slot: Any # Slot + + +@dataclass +class LightClientDataCache: + # Cached data for creating future `LightClientUpdate` instances. + # Key is the block ID of which the post state was used to get the data. + # Data stored for the finalized head block and all non-finalized blocks. + data: dict[BlockID, CachedLightClientData] + + # Light client data for the latest slot that was signed by at least + # `MIN_SYNC_COMMITTEE_PARTICIPANTS`. May be older than head + latest: ForkedLightClientFinalityUpdate + + # The earliest slot for which light client data is imported + tail_slot: Any # Slot + + +@dataclass +class LightClientDataDB: + headers: dict[Any, ForkedLightClientHeader] # Root -> ForkedLightClientHeader + current_branches: dict[Any, Any] # Slot -> CurrentSyncCommitteeBranch + sync_committees: dict[Any, Any] # SyncCommitteePeriod -> SyncCommittee + best_updates: dict[ + Any, ForkedLightClientUpdate + ] # SyncCommitteePeriod -> ForkedLightClientUpdate + + +@dataclass +class LightClientDataStore: + spec: Any + + # Cached data to accelerate creating light client data + cache: LightClientDataCache + + # Persistent light client data + db: LightClientDataDB + + +@dataclass +class LightClientDataCollectionTest: + steps: list[dict[str, Any]] + files: set[str] + + # Fork schedule + phases: Any + + # History access + blocks: dict[Any, ForkedSignedBeaconBlock] # Block root -> ForkedSignedBeaconBlock + finalized_block_roots: dict[Any, Any] # Slot -> Root + states: dict[Any, ForkedBeaconState] # State root -> ForkedBeaconState + finalized_checkpoint_states: dict[Any, ForkedBeaconState] # State root -> ForkedBeaconState + latest_finalized_epoch: Any # Epoch + latest_finalized_bid: BlockID + historical_tail_slot: Any # Slot + + # Light client data + lc_data_store: LightClientDataStore + + +def get_ancestor_of_block_id(test, bid, slot): # -> Optional[BlockID] + try: + block = test.blocks[bid.root] + while True: + if block.data.message.slot <= slot: + return _block_to_block_id(block.data) + + block = test.blocks[block.data.message.parent_root] + except KeyError: + return None + + +def _block_id_at_finalized_slot(test, slot): # -> Optional[BlockID] + while slot >= test.historical_tail_slot: + try: + return BlockID(slot=slot, root=test.finalized_block_roots[slot]) + except KeyError: + slot = slot - 1 + return None + + +def _get_current_sync_committee_for_finalized_period(test, period): # -> Optional[SyncCommittee] + low_slot = max( + test.historical_tail_slot, + test.lc_data_store.spec.compute_start_slot_at_epoch( + test.lc_data_store.spec.config.ALTAIR_FORK_EPOCH + ), + ) + if period < test.lc_data_store.spec.compute_sync_committee_period_at_slot(low_slot): + return None + period_start_slot = compute_start_slot_at_sync_committee_period(test.lc_data_store.spec, period) + sync_committee_slot = max(period_start_slot, low_slot) + bid = _block_id_at_finalized_slot(test, sync_committee_slot) + if bid is None: + return None + block = test.blocks[bid.root] + state = test.finalized_checkpoint_states[block.data.message.state_root] + if sync_committee_slot > state.data.slot: + state.spec, state.data, _ = transition_across_forks( + state.spec, state.data, sync_committee_slot, phases=test.phases + ) + assert is_post_altair(state.spec) + return state.data.current_sync_committee + + +def _light_client_header_for_block(test, block): # -> ForkedLightClientHeader + if not is_post_altair(block.spec): + spec = test.phases[ALTAIR] + else: + spec = block.spec + return ForkedLightClientHeader(spec=spec, data=spec.block_to_light_client_header(block.data)) + + +def _light_client_header_for_block_id(test, bid): # -> ForkedLightClientHeader + block = test.blocks[bid.root] + if not is_post_altair(block.spec): + spec = test.phases[ALTAIR] + else: + spec = block.spec + return ForkedLightClientHeader(spec=spec, data=spec.block_to_light_client_header(block.data)) + + +def _sync_aggregate_for_block_id(test, bid): # -> Optional[SyncAggregate] + block = test.blocks[bid.root] + if not is_post_altair(block.spec): + return None + return block.data.message.body.sync_aggregate + + +def _get_light_client_data(lc_data_store, bid): # -> CachedLightClientData + # Fetch cached light client data about a given block. + # Data must be cached (`_cache_lc_data`) before calling this function. + try: + return lc_data_store.cache.data[bid] + except KeyError: + raise ValueError("Trying to get light client data that was not cached") + + +def _cache_lc_data( + lc_data_store, spec, state, bid, current_period_best_update, latest_signature_slot +): + # Cache data for a given block and its post-state to speed up creating future + # `LightClientUpdate` and `LightClientBootstrap` instances that refer to this + # block and state. + cached_data = CachedLightClientData( + current_sync_committee_branch=latest_normalize_merkle_branch( + lc_data_store.spec, + spec.compute_merkle_proof( + state, spec.current_sync_committee_gindex_at_slot(state.slot) + ), + latest_current_sync_committee_gindex(lc_data_store.spec), + ), + next_sync_committee_branch=latest_normalize_merkle_branch( + lc_data_store.spec, + spec.compute_merkle_proof(state, spec.next_sync_committee_gindex_at_slot(state.slot)), + latest_next_sync_committee_gindex(lc_data_store.spec), + ), + finalized_slot=spec.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch), + finality_branch=latest_normalize_merkle_branch( + lc_data_store.spec, + spec.compute_merkle_proof(state, spec.finalized_root_gindex_at_slot(state.slot)), + latest_finalized_root_gindex(lc_data_store.spec), + ), + current_period_best_update=current_period_best_update, + latest_signature_slot=latest_signature_slot, + ) + if bid in lc_data_store.cache.data: + raise ValueError("Redundant `_cache_lc_data` call") + lc_data_store.cache.data[bid] = cached_data + + +def _delete_light_client_data(lc_data_store, bid): + # Delete cached light client data for a given block. This needs to be called + # when a block becomes unreachable due to finalization of a different fork. + del lc_data_store.cache.data[bid] + + +def _create_lc_finality_update_from_lc_data( + test, attested_bid, signature_slot, sync_aggregate +): # -> ForkedLightClientFinalityUpdate + attested_header = _light_client_header_for_block_id(test, attested_bid) + attested_data = _get_light_client_data(test.lc_data_store, attested_bid) + finalized_bid = _block_id_at_finalized_slot(test, attested_data.finalized_slot) + if finalized_bid is not None: + if finalized_bid.slot != attested_data.finalized_slot: + # Empty slots at end of epoch, update cache for latest block slot + attested_data.finalized_slot = finalized_bid.slot + if finalized_bid.slot == attested_header.spec.GENESIS_SLOT: + finalized_header = ForkedLightClientHeader( + spec=attested_header.spec, + data=attested_header.spec.LightClientHeader(), + ) + else: + finalized_header = _light_client_header_for_block_id(test, finalized_bid) + finalized_header = ForkedLightClientHeader( + spec=attested_header.spec, + data=upgrade_lc_header_to_new_spec( + finalized_header.spec, + attested_header.spec, + finalized_header.data, + ), + ) + finality_branch = attested_data.finality_branch + return ForkedLightClientFinalityUpdate( + spec=attested_header.spec, + data=attested_header.spec.LightClientFinalityUpdate( + attested_header=attested_header.data, + finalized_header=finalized_header.data, + finality_branch=finality_branch, + sync_aggregate=sync_aggregate, + signature_slot=signature_slot, + ), + ) + + +def _create_lc_update_from_lc_data( + test, attested_bid, signature_slot, sync_aggregate, next_sync_committee +): # -> ForkedLightClientUpdate + finality_update = _create_lc_finality_update_from_lc_data( + test, attested_bid, signature_slot, sync_aggregate + ) + attested_data = _get_light_client_data(test.lc_data_store, attested_bid) + return ForkedLightClientUpdate( + spec=finality_update.spec, + data=finality_update.spec.LightClientUpdate( + attested_header=finality_update.data.attested_header, + next_sync_committee=next_sync_committee, + next_sync_committee_branch=attested_data.next_sync_committee_branch, + finalized_header=finality_update.data.finalized_header, + finality_branch=finality_update.data.finality_branch, + sync_aggregate=finality_update.data.sync_aggregate, + signature_slot=finality_update.data.signature_slot, + ), + ) + + +def _create_lc_update(test, spec, state, block, parent_bid): + # Create `LightClientUpdate` instances for a given block and its post-state, + # and keep track of best / latest ones. Data about the parent block's + # post-state must be cached (`_cache_lc_data`) before calling this. + + # Verify attested block (parent) is recent enough and that state is available + attested_bid = parent_bid + attested_slot = attested_bid.slot + if attested_slot < test.lc_data_store.cache.tail_slot: + _cache_lc_data( + test.lc_data_store, + spec, + state, + _block_to_block_id(block), + current_period_best_update=ForkedLightClientUpdate(spec=None, data=None), + latest_signature_slot=spec.GENESIS_SLOT, + ) + return + + # If sync committee period changed, reset `best` + attested_period = spec.compute_sync_committee_period_at_slot(attested_slot) + signature_slot = block.message.slot + signature_period = spec.compute_sync_committee_period_at_slot(signature_slot) + attested_data = _get_light_client_data(test.lc_data_store, attested_bid) + if attested_period != signature_period: + best = ForkedLightClientUpdate(spec=None, data=None) + else: + best = attested_data.current_period_best_update + + # If sync committee does not have sufficient participants, do not bump latest + sync_aggregate = block.message.body.sync_aggregate + num_active_participants = sum(sync_aggregate.sync_committee_bits) + if num_active_participants < spec.MIN_SYNC_COMMITTEE_PARTICIPANTS: + latest_signature_slot = attested_data.latest_signature_slot + else: + latest_signature_slot = signature_slot + + # To update `best`, sync committee must have sufficient participants, and + # `signature_slot` must be in `attested_slot`'s sync committee period + if ( + num_active_participants < spec.MIN_SYNC_COMMITTEE_PARTICIPANTS + or attested_period != signature_period + ): + _cache_lc_data( + test.lc_data_store, + spec, + state, + _block_to_block_id(block), + current_period_best_update=best, + latest_signature_slot=latest_signature_slot, + ) + return + + # Check if light client data improved + update = _create_lc_update_from_lc_data( + test, attested_bid, signature_slot, sync_aggregate, state.next_sync_committee + ) + is_better = best.spec is None or spec.is_better_update( + update.data, upgrade_lc_update_to_new_spec(best.spec, update.spec, best.data, test.phases) + ) + + # Update best light client data for current sync committee period + if is_better: + best = update + _cache_lc_data( + test.lc_data_store, + spec, + state, + _block_to_block_id(block), + current_period_best_update=best, + latest_signature_slot=latest_signature_slot, + ) + + +def _create_lc_bootstrap(test, spec, bid): + block = test.blocks[bid.root] + period = spec.compute_sync_committee_period_at_slot(bid.slot) + if period not in test.lc_data_store.db.sync_committees: + test.lc_data_store.db.sync_committees[period] = ( + _get_current_sync_committee_for_finalized_period(test, period) + ) + test.lc_data_store.db.headers[bid.root] = ForkedLightClientHeader( + spec=block.spec, data=block.spec.block_to_light_client_header(block.data) + ) + test.lc_data_store.db.current_branches[bid.slot] = _get_light_client_data( + test.lc_data_store, bid + ).current_sync_committee_branch + + +def _process_new_block_for_light_client(test, spec, state, block, parent_bid): + # Update light client data with information from a new block. + if block.message.slot < test.lc_data_store.cache.tail_slot: + return + + if is_post_altair(spec): + _create_lc_update(test, spec, state, block, parent_bid) + else: + raise ValueError("`tail_slot` cannot be before Altair") + + +def _process_head_change_for_light_client(test, spec, head_bid, old_finalized_bid): + # Update light client data to account for a new head block. + # Note that `old_finalized_bid` is not yet updated when this is called. + if head_bid.slot < test.lc_data_store.cache.tail_slot: + return + + # Commit best light client data for non-finalized periods + head_period = spec.compute_sync_committee_period_at_slot(head_bid.slot) + low_slot = max(test.lc_data_store.cache.tail_slot, old_finalized_bid.slot) + low_period = spec.compute_sync_committee_period_at_slot(low_slot) + bid = head_bid + for period in reversed(range(low_period, head_period + 1)): + period_end_slot = compute_start_slot_at_sync_committee_period(spec, period + 1) - 1 + bid = get_ancestor_of_block_id(test, bid, period_end_slot) + if bid is None or bid.slot < low_slot: + break + best = _get_light_client_data(test.lc_data_store, bid).current_period_best_update + if ( + best.spec is None + or sum(best.data.sync_aggregate.sync_committee_bits) + < spec.MIN_SYNC_COMMITTEE_PARTICIPANTS + ): + test.lc_data_store.db.best_updates.pop(period, None) + else: + test.lc_data_store.db.best_updates[period] = best + + # Update latest light client data + head_data = _get_light_client_data(test.lc_data_store, head_bid) + signature_slot = head_data.latest_signature_slot + if signature_slot <= low_slot: + test.lc_data_store.cache.latest = ForkedLightClientFinalityUpdate(spec=None, data=None) + return + signature_bid = get_ancestor_of_block_id(test, head_bid, signature_slot) + if signature_bid is None or signature_bid.slot <= low_slot: + test.lc_data_store.cache.latest = ForkedLightClientFinalityUpdate(spec=None, data=None) + return + attested_bid = get_ancestor_of_block_id(test, signature_bid, signature_bid.slot - 1) + if attested_bid is None or attested_bid.slot < low_slot: + test.lc_data_store.cache.latest = ForkedLightClientFinalityUpdate(spec=None, data=None) + return + sync_aggregate = _sync_aggregate_for_block_id(test, signature_bid) + assert sync_aggregate is not None + test.lc_data_store.cache.latest = _create_lc_finality_update_from_lc_data( + test, attested_bid, signature_slot, sync_aggregate + ) + + +def _process_finalization_for_light_client(test, spec, finalized_bid, old_finalized_bid): + # Prune cached data that is no longer useful for creating future + # `LightClientUpdate` and `LightClientBootstrap` instances. + # This needs to be called whenever `finalized_checkpoint` changes. + finalized_slot = finalized_bid.slot + if finalized_slot < test.lc_data_store.cache.tail_slot: + return + + # Cache `LightClientBootstrap` for newly finalized epoch boundary blocks + first_new_slot = old_finalized_bid.slot + 1 + low_slot = max(first_new_slot, test.lc_data_store.cache.tail_slot) + boundary_slot = finalized_slot + while boundary_slot >= low_slot: + bid = _block_id_at_finalized_slot(test, boundary_slot) + if bid is None: + break + if bid.slot >= low_slot: + _create_lc_bootstrap(test, spec, bid) + boundary_slot = _next_epoch_boundary_slot(spec, bid.slot) + if boundary_slot < spec.SLOTS_PER_EPOCH: + break + boundary_slot = boundary_slot - spec.SLOTS_PER_EPOCH + + # Prune light client data that is no longer referable by future updates + bids_to_delete = [] + for bid in test.lc_data_store.cache.data: + if bid.slot >= finalized_bid.slot: + continue + bids_to_delete.append(bid) + for bid in bids_to_delete: + _delete_light_client_data(test.lc_data_store, bid) + + +def get_light_client_bootstrap(test, block_root): # -> ForkedLightClientBootstrap + try: + header = test.lc_data_store.db.headers[block_root] + except KeyError: + return ForkedLightClientBootstrap(spec=None, data=None) + + slot = header.data.beacon.slot + period = header.spec.compute_sync_committee_period_at_slot(slot) + return ForkedLightClientBootstrap( + spec=header.spec, + data=header.spec.LightClientBootstrap( + header=header.data, + current_sync_committee=test.lc_data_store.db.sync_committees[period], + current_sync_committee_branch=test.lc_data_store.db.current_branches[slot], + ), + ) + + +def get_light_client_update_for_period(test, period): # -> ForkedLightClientUpdate + try: + return test.lc_data_store.db.best_updates[period] + except KeyError: + return ForkedLightClientUpdate(spec=None, data=None) + + +def get_light_client_finality_update(test): # -> ForkedLightClientFinalityUpdate + return test.lc_data_store.cache.latest + + +def get_light_client_optimistic_update(test): # -> ForkedLightClientOptimisticUpdate + finality_update = get_light_client_finality_update(test) + if finality_update.spec is None: + return ForkedLightClientOptimisticUpdate(spec=None, data=None) + return ForkedLightClientOptimisticUpdate( + spec=finality_update.spec, + data=finality_update.spec.LightClientOptimisticUpdate( + attested_header=finality_update.data.attested_header, + sync_aggregate=finality_update.data.sync_aggregate, + signature_slot=finality_update.data.signature_slot, + ), + ) + + +def setup_lc_data_collection_test(spec, state, phases=None): + assert spec.compute_slots_since_epoch_start(state.slot) == 0 + + test = LightClientDataCollectionTest( + steps=[], + files=set(), + phases=phases, + blocks={}, + finalized_block_roots={}, + states={}, + finalized_checkpoint_states={}, + latest_finalized_epoch=state.finalized_checkpoint.epoch, + latest_finalized_bid=BlockID( + slot=spec.compute_start_slot_at_epoch(state.finalized_checkpoint.epoch), + root=state.finalized_checkpoint.root, + ), + historical_tail_slot=state.slot, + lc_data_store=LightClientDataStore( + spec=spec, + cache=LightClientDataCache( + data={}, + latest=ForkedLightClientFinalityUpdate(spec=None, data=None), + tail_slot=max( + state.slot, spec.compute_start_slot_at_epoch(spec.config.ALTAIR_FORK_EPOCH) + ), + ), + db=LightClientDataDB( + headers={}, + current_branches={}, + sync_committees={}, + best_updates={}, + ), + ), + ) + bid = _state_to_block_id(state) + yield "initial_state", state + test.blocks[bid.root] = ForkedSignedBeaconBlock( + spec=spec, + data=spec.SignedBeaconBlock( + message=spec.BeaconBlock(state_root=state.hash_tree_root()), + ), + ) + test.finalized_block_roots[bid.slot] = bid.root + test.states[state.hash_tree_root()] = ForkedBeaconState(spec=spec, data=state) + test.finalized_checkpoint_states[state.hash_tree_root()] = ForkedBeaconState( + spec=spec, data=state + ) + _cache_lc_data( + test.lc_data_store, + spec, + state, + bid, + current_period_best_update=ForkedLightClientUpdate(spec=None, data=None), + latest_signature_slot=spec.GENESIS_SLOT, + ) + _create_lc_bootstrap(test, spec, bid) + + return test + + +def finish_lc_data_collection_test(test): + yield "steps", test.steps + + +def _encode_lc_object(test, prefix, obj, slot, genesis_validators_root): + yield from [] # Consistently enable `yield from` syntax in calling tests + + file_name = f"{prefix}_{slot}_{encode_hex(obj.data.hash_tree_root())}" + if file_name not in test.files: + test.files.add(file_name) + yield file_name, obj.data + return { + "fork_digest": encode_hex( + obj.spec.compute_fork_digest( + obj.spec.compute_fork_version(obj.spec.compute_epoch_at_slot(slot)), + genesis_validators_root, + ) + ), + "data": file_name, + } + + +def add_new_block(test, spec, state, slot=None, num_sync_participants=0): + if slot is None: + slot = state.slot + 1 + assert slot > state.slot + parent_bid = _state_to_block_id(state) + + # Advance to target slot - 1 to ensure sync aggregate can be efficiently computed + if state.slot < slot - 1: + spec, state, _ = transition_across_forks(spec, state, slot - 1, phases=test.phases) + + # Compute sync aggregate, using: + # - sync committee based on target slot + # - fork digest based on target slot - 1 + # - signed data based on parent_bid.slot + # All three slots may be from different forks + sync_aggregate, signature_slot = get_sync_aggregate( + spec, state, num_participants=num_sync_participants, phases=test.phases + ) + assert signature_slot == slot + + # Apply final block with computed sync aggregate + spec, state, block = transition_across_forks( + spec, state, slot, phases=test.phases, with_block=True, sync_aggregate=sync_aggregate + ) + bid = _block_to_block_id(block) + test.blocks[bid.root] = ForkedSignedBeaconBlock(spec=spec, data=block) + test.states[block.message.state_root] = ForkedBeaconState(spec=spec, data=state) + _process_new_block_for_light_client(test, spec, state, block, parent_bid) + block_obj = yield from _encode_lc_object( + test, + "block", + ForkedSignedBeaconBlock(spec=spec, data=block), + block.message.slot, + state.genesis_validators_root, + ) + test.steps.append({"new_block": block_obj}) + return spec, state, bid + + +def select_new_head(test, spec, head_bid): + old_finalized_bid = test.latest_finalized_bid + _process_head_change_for_light_client(test, spec, head_bid, old_finalized_bid) + + # Process finalization + block = test.blocks[head_bid.root] + state = test.states[block.data.message.state_root] + if state.data.finalized_checkpoint.epoch != spec.GENESIS_EPOCH: + block = test.blocks[state.data.finalized_checkpoint.root] + bid = _block_to_block_id(block.data) + new_finalized_bid = bid + if new_finalized_bid.slot > old_finalized_bid.slot: + old_finalized_epoch = None + new_finalized_epoch = state.data.finalized_checkpoint.epoch + while bid.slot > test.latest_finalized_bid.slot: + test.finalized_block_roots[bid.slot] = bid.root + finalized_epoch = spec.compute_epoch_at_slot(bid.slot + spec.SLOTS_PER_EPOCH - 1) + if finalized_epoch != old_finalized_epoch: + state = test.states[block.data.message.state_root] + test.finalized_checkpoint_states[block.data.message.state_root] = state + old_finalized_epoch = finalized_epoch + block = test.blocks[block.data.message.parent_root] + bid = _block_to_block_id(block.data) + test.latest_finalized_epoch = new_finalized_epoch + test.latest_finalized_bid = new_finalized_bid + _process_finalization_for_light_client(test, spec, new_finalized_bid, old_finalized_bid) + + blocks_to_delete = [] + for block_root, block in test.blocks.items(): + if block.data.message.slot < new_finalized_bid.slot: + blocks_to_delete.append(block_root) + for block_root in blocks_to_delete: + del test.blocks[block_root] + states_to_delete = [] + for state_root, state in test.states.items(): + if state.data.slot < new_finalized_bid.slot: + states_to_delete.append(state_root) + for state_root in states_to_delete: + del test.states[state_root] + + yield from [] # Consistently enable `yield from` syntax in calling tests + + bootstraps = [] + for state in test.finalized_checkpoint_states.values(): + bid = _state_to_block_id(state.data) + entry = { + "block_root": encode_hex(bid.root), + } + bootstrap = get_light_client_bootstrap(test, bid.root) + if bootstrap.spec is not None: + bootstrap_obj = yield from _encode_lc_object( + test, + "bootstrap", + bootstrap, + bootstrap.data.header.beacon.slot, + state.data.genesis_validators_root, + ) + entry["bootstrap"] = bootstrap_obj + bootstraps.append(entry) + + best_updates = [] + low_period = spec.compute_sync_committee_period_at_slot(test.lc_data_store.cache.tail_slot) + head_period = spec.compute_sync_committee_period_at_slot(head_bid.slot) + for period in range(low_period, head_period + 1): + entry = { + "period": int(period), + } + update = get_light_client_update_for_period(test, period) + if update.spec is not None: + update_obj = yield from _encode_lc_object( + test, + "update", + update, + update.data.attested_header.beacon.slot, + state.data.genesis_validators_root, + ) + entry["update"] = update_obj + best_updates.append(entry) + + checks = { + "latest_finalized_checkpoint": { + "epoch": int(test.latest_finalized_epoch), + "root": encode_hex(test.latest_finalized_bid.root), + }, + "bootstraps": bootstraps, + "best_updates": best_updates, + } + finality_update = get_light_client_finality_update(test) + if finality_update.spec is not None: + finality_update_obj = yield from _encode_lc_object( + test, + "finality_update", + finality_update, + finality_update.data.attested_header.beacon.slot, + state.data.genesis_validators_root, + ) + checks["latest_finality_update"] = finality_update_obj + optimistic_update = get_light_client_optimistic_update(test) + if optimistic_update.spec is not None: + optimistic_update_obj = yield from _encode_lc_object( + test, + "optimistic_update", + optimistic_update, + optimistic_update.data.attested_header.beacon.slot, + state.data.genesis_validators_root, + ) + checks["latest_optimistic_update"] = optimistic_update_obj + + test.steps.append( + { + "new_head": { + "head_block_root": encode_hex(head_bid.root), + "checks": checks, + } + } + ) + + +def run_lc_data_collection_test_multi_fork(spec, phases, state, fork_1, fork_2): + # Start test + test = yield from setup_lc_data_collection_test(spec, state, phases=phases) + + # Genesis block is post Altair and is finalized, so can be used as bootstrap + genesis_bid = BlockID( + slot=state.slot, root=spec.BeaconBlock(state_root=state.hash_tree_root()).hash_tree_root() + ) + assert ( + get_lc_bootstrap_block_id(get_light_client_bootstrap(test, genesis_bid.root).data) + == genesis_bid + ) + + # Shared history up to final epoch of period before `fork_1` + fork_1_epoch = getattr(phases[fork_1].config, fork_1.upper() + "_FORK_EPOCH") + fork_1_period = spec.compute_sync_committee_period(fork_1_epoch) + slot = compute_start_slot_at_sync_committee_period(spec, fork_1_period) - spec.SLOTS_PER_EPOCH + spec, state, bid = yield from add_new_block( + test, spec, state, slot=slot, num_sync_participants=1 + ) + yield from select_new_head(test, spec, bid) + assert get_light_client_bootstrap(test, bid.root).spec is None + slot_period = spec.compute_sync_committee_period_at_slot(slot) + if slot_period == 0: + assert ( + get_lc_update_attested_block_id(get_light_client_update_for_period(test, 0).data) + == genesis_bid + ) + else: + for period in range(0, slot_period): + assert ( + get_light_client_update_for_period(test, period).spec is None + ) # attested period != signature period + state_period = spec.compute_sync_committee_period_at_slot(state.slot) + + # Branch A: Advance past `fork_2`, having blocks at slots 0 and 4 of each epoch + spec_a = spec + state_a = state + slot_a = state_a.slot + bids_a = [bid] + num_sync_participants_a = 1 + fork_2_epoch = getattr(phases[fork_2].config, fork_2.upper() + "_FORK_EPOCH") + while spec_a.get_current_epoch(state_a) <= fork_2_epoch: + attested_period = spec_a.compute_sync_committee_period_at_slot(slot_a) + slot_a += 4 + signature_period = spec_a.compute_sync_committee_period_at_slot(slot_a) + if signature_period != attested_period: + num_sync_participants_a = 0 + num_sync_participants_a += 1 + spec_a, state_a, bid_a = yield from add_new_block( + test, spec_a, state_a, slot=slot_a, num_sync_participants=num_sync_participants_a + ) + yield from select_new_head(test, spec_a, bid_a) + for bid in bids_a: + assert get_light_client_bootstrap(test, bid.root).spec is None + if attested_period == signature_period: + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_a[-1] + ) + else: + assert signature_period == attested_period + 1 + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_a[-2] + ) + assert get_light_client_update_for_period(test, signature_period).spec is None + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) + == bids_a[-1] + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) + == bids_a[-1] + ) + bids_a.append(bid_a) + + # Branch B: Advance past `fork_2`, having blocks at slots 1 and 5 of each epoch but no sync participation + spec_b = spec + state_b = state + slot_b = state_b.slot + bids_b = [bid] + while spec_b.get_current_epoch(state_b) <= fork_2_epoch: + slot_b += 4 + signature_period = spec_b.compute_sync_committee_period_at_slot(slot_b) + spec_b, state_b, bid_b = yield from add_new_block(test, spec_b, state_b, slot=slot_b) + # Simulate that this does not become head yet, e.g., this branch was withheld + for bid in bids_b: + assert get_light_client_bootstrap(test, bid.root).spec is None + bids_b.append(bid_b) + + # Branch B: Another block that becomes head + attested_period = spec_b.compute_sync_committee_period_at_slot(slot_b) + slot_b += 1 + signature_period = spec_b.compute_sync_committee_period_at_slot(slot_b) + num_sync_participants_b = 1 + spec_b, state_b, bid_b = yield from add_new_block( + test, spec_b, state_b, slot=slot_b, num_sync_participants=num_sync_participants_b + ) + yield from select_new_head(test, spec_b, bid_b) + for bid in bids_b: + assert get_light_client_bootstrap(test, bid.root).spec is None + if attested_period == signature_period: + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_b[-1] + ) + else: + assert signature_period == attested_period + 1 + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_b[-2] + ) + assert get_light_client_update_for_period(test, signature_period).spec is None + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bids_b[-1] + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bids_b[-1] + ) + bids_b.append(bid_b) + + # All data for periods between the common ancestor of the two branches should have reorged. + # As there was no sync participation on branch B, that means it is deleted. + state_b_period = spec_b.compute_sync_committee_period_at_slot(state_b.slot) + for period in range(state_period + 1, state_b_period): + assert get_light_client_update_for_period(test, period).spec is None + + # Branch A: Another block, reorging branch B once more + attested_period = spec_a.compute_sync_committee_period_at_slot(slot_a) + slot_a = slot_b + 1 + signature_period = spec_a.compute_sync_committee_period_at_slot(slot_a) + if signature_period != attested_period: + num_sync_participants_a = 0 + num_sync_participants_a += 1 + spec_a, state_a, bid_a = yield from add_new_block( + test, spec_a, state_a, slot=slot_a, num_sync_participants=num_sync_participants_a + ) + yield from select_new_head(test, spec_a, bid_a) + for bid in bids_a: + assert get_light_client_bootstrap(test, bid.root).spec is None + if attested_period == signature_period: + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_a[-1] + ) + else: + assert signature_period == attested_period + 1 + assert ( + get_lc_update_attested_block_id( + get_light_client_update_for_period(test, attested_period).data, + ) + == bids_a[-2] + ) + assert get_light_client_update_for_period(test, signature_period).spec is None + assert ( + get_lc_update_attested_block_id(get_light_client_finality_update(test).data) == bids_a[-1] + ) + assert ( + get_lc_update_attested_block_id(get_light_client_optimistic_update(test).data) == bids_a[-1] + ) + bids_a.append(bid_a) + + # Data has been restored + state_a_period = spec_a.compute_sync_committee_period_at_slot(state_a.slot) + for period in range(state_period + 1, state_a_period): + assert get_light_client_update_for_period(test, period).spec is not None + + # Finish test + yield from finish_lc_data_collection_test(test) diff --git a/tests/core/pyspec/eth2spec/test/helpers/light_client_sync.py b/tests/core/pyspec/eth2spec/test/helpers/light_client_sync.py new file mode 100644 index 0000000000..7ef53d9190 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/light_client_sync.py @@ -0,0 +1,369 @@ +from typing import Any + +from eth_utils import encode_hex + +from eth2spec.test.helpers.attestations import ( + next_slots_with_attestations, + state_transition_with_full_block, +) +from eth2spec.test.helpers.fork_transition import ( + do_fork, + transition_across_forks, +) +from eth2spec.test.helpers.forks import ( + get_spec_for_fork_version, + is_post_capella, + is_post_deneb, + is_post_electra, +) +from eth2spec.test.helpers.light_client import ( + get_sync_aggregate, + upgrade_lc_bootstrap_to_new_spec, + upgrade_lc_store_to_new_spec, + upgrade_lc_update_to_new_spec, +) +from eth2spec.test.helpers.state import ( + next_slots, + transition_to, +) + + +class LightClientSyncTest: + steps: list[dict[str, Any]] + genesis_validators_root: Any + s_spec: Any + store: Any + + +def _get_store_fork_version(s_spec): + if is_post_electra(s_spec): + return s_spec.config.ELECTRA_FORK_VERSION + if is_post_deneb(s_spec): + return s_spec.config.DENEB_FORK_VERSION + if is_post_capella(s_spec): + return s_spec.config.CAPELLA_FORK_VERSION + return s_spec.config.ALTAIR_FORK_VERSION + + +def setup_lc_sync_test(spec, state, s_spec=None, phases=None): + test = LightClientSyncTest() + test.steps = [] + + if s_spec is None: + s_spec = spec + if phases is None: + phases = { + spec.fork: spec, + s_spec.fork: s_spec, + } + test.s_spec = s_spec + + yield "genesis_validators_root", "meta", "0x" + state.genesis_validators_root.hex() + test.genesis_validators_root = state.genesis_validators_root + + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2 - 1) + trusted_block = state_transition_with_full_block(spec, state, True, True) + trusted_block_root = trusted_block.message.hash_tree_root() + yield "trusted_block_root", "meta", "0x" + trusted_block_root.hex() + + data_fork_version = spec.compute_fork_version( + spec.compute_epoch_at_slot(trusted_block.message.slot) + ) + data_fork_digest = spec.compute_fork_digest(data_fork_version, test.genesis_validators_root) + d_spec = get_spec_for_fork_version(spec, data_fork_version, phases) + data = d_spec.create_light_client_bootstrap(state, trusted_block) + yield "bootstrap_fork_digest", "meta", encode_hex(data_fork_digest) + yield "bootstrap", data + + upgraded = upgrade_lc_bootstrap_to_new_spec(d_spec, test.s_spec, data, phases) + test.store = test.s_spec.initialize_light_client_store(trusted_block_root, upgraded) + store_fork_version = _get_store_fork_version(test.s_spec) + store_fork_digest = test.s_spec.compute_fork_digest( + store_fork_version, test.genesis_validators_root + ) + yield "store_fork_digest", "meta", encode_hex(store_fork_digest) + + return test + + +def finish_lc_sync_test(test): + yield "steps", test.steps + + +def _get_update_file_name(d_spec, update): + if d_spec.is_sync_committee_update(update): + suffix1 = "s" + else: + suffix1 = "x" + if d_spec.is_finality_update(update): + suffix2 = "f" + else: + suffix2 = "x" + return f"update_{encode_hex(update.attested_header.beacon.hash_tree_root())}_{suffix1}{suffix2}" + + +def _get_checks(s_spec, store): + if is_post_capella(s_spec): + return { + "finalized_header": { + "slot": int(store.finalized_header.beacon.slot), + "beacon_root": encode_hex(store.finalized_header.beacon.hash_tree_root()), + "execution_root": encode_hex(s_spec.get_lc_execution_root(store.finalized_header)), + }, + "optimistic_header": { + "slot": int(store.optimistic_header.beacon.slot), + "beacon_root": encode_hex(store.optimistic_header.beacon.hash_tree_root()), + "execution_root": encode_hex(s_spec.get_lc_execution_root(store.optimistic_header)), + }, + } + + return { + "finalized_header": { + "slot": int(store.finalized_header.beacon.slot), + "beacon_root": encode_hex(store.finalized_header.beacon.hash_tree_root()), + }, + "optimistic_header": { + "slot": int(store.optimistic_header.beacon.slot), + "beacon_root": encode_hex(store.optimistic_header.beacon.hash_tree_root()), + }, + } + + +def emit_force_update(test, spec, state): + current_slot = state.slot + test.s_spec.process_light_client_store_force_update(test.store, current_slot) + + yield from [] # Consistently enable `yield from` syntax in calling tests + test.steps.append( + { + "force_update": { + "current_slot": int(current_slot), + "checks": _get_checks(test.s_spec, test.store), + } + } + ) + + +def emit_update( + test, + spec, + state, + block, + attested_state, + attested_block, + finalized_block, + with_next=True, + phases=None, +): + data_fork_version = spec.compute_fork_version( + spec.compute_epoch_at_slot(attested_block.message.slot) + ) + data_fork_digest = spec.compute_fork_digest(data_fork_version, test.genesis_validators_root) + d_spec = get_spec_for_fork_version(spec, data_fork_version, phases) + data = d_spec.create_light_client_update( + state, block, attested_state, attested_block, finalized_block + ) + if not with_next: + data.next_sync_committee = spec.SyncCommittee() + data.next_sync_committee_branch = spec.NextSyncCommitteeBranch() + current_slot = state.slot + + upgraded = upgrade_lc_update_to_new_spec(d_spec, test.s_spec, data, phases) + test.s_spec.process_light_client_update( + test.store, upgraded, current_slot, test.genesis_validators_root + ) + + yield _get_update_file_name(d_spec, data), data + test.steps.append( + { + "process_update": { + "update_fork_digest": encode_hex(data_fork_digest), + "update": _get_update_file_name(d_spec, data), + "current_slot": int(current_slot), + "checks": _get_checks(test.s_spec, test.store), + } + } + ) + return upgraded + + +def _emit_upgrade_store(test, new_s_spec, phases=None): + test.store = upgrade_lc_store_to_new_spec(test.s_spec, new_s_spec, test.store, phases) + test.s_spec = new_s_spec + store_fork_version = _get_store_fork_version(test.s_spec) + store_fork_digest = test.s_spec.compute_fork_digest( + store_fork_version, test.genesis_validators_root + ) + + yield from [] # Consistently enable `yield from` syntax in calling tests + test.steps.append( + { + "upgrade_store": { + "store_fork_digest": encode_hex(store_fork_digest), + "checks": _get_checks(test.s_spec, test.store), + } + } + ) + + +def run_lc_sync_test_single_fork(spec, phases, state, fork): + # Start test + test = yield from setup_lc_sync_test(spec, state, phases=phases) + + # Initial `LightClientUpdate` + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Jump to two slots before fork + fork_epoch = getattr(phases[fork].config, fork.upper() + "_FORK_EPOCH") + transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_epoch) - 4) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + update = yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Perform `LightClientStore` upgrade + yield from _emit_upgrade_store(test, phases[fork], phases=phases) + update = test.store.best_valid_update + + # Final slot before fork, check that importing the pre-fork format still works + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Upgrade to post-fork spec, attested block is still before the fork + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + state, block = do_fork(state, spec, phases[fork], fork_epoch, sync_aggregate=sync_aggregate) + spec = phases[fork] + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Another block after the fork, this time attested block is after the fork + attested_block = block.copy() + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Jump to next epoch + transition_to(spec, state, spec.compute_start_slot_at_epoch(fork_epoch + 1) - 2) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update == update + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finalize the fork + finalized_block = block.copy() + finalized_state = state.copy() + _, _, state = next_slots_with_attestations( + spec, state, 2 * spec.SLOTS_PER_EPOCH - 1, True, True + ) + attested_block = state_transition_with_full_block(spec, state, True, True) + attested_state = state.copy() + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + block = state_transition_with_full_block(spec, state, True, True, sync_aggregate=sync_aggregate) + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finish test + yield from finish_lc_sync_test(test) + + +def run_lc_sync_test_multi_fork(spec, phases, state, fork_1, fork_2): + # Start test + test = yield from setup_lc_sync_test(spec, state, phases[fork_2], phases) + + # Set up so that finalized is from `spec`, ... + finalized_block = spec.SignedBeaconBlock() + finalized_block.message.state_root = state.hash_tree_root() + finalized_state = state.copy() + + # ..., attested is from `fork_1`, ... + fork_1_epoch = getattr(phases[fork_1].config, fork_1.upper() + "_FORK_EPOCH") + spec, state, attested_block = transition_across_forks( + spec, + state, + spec.compute_start_slot_at_epoch(fork_1_epoch), + phases, + with_block=True, + ) + attested_state = state.copy() + + # ..., and signature is from `fork_2` + fork_2_epoch = getattr(phases[fork_2].config, fork_2.upper() + "_FORK_EPOCH") + spec, state, _ = transition_across_forks( + spec, state, spec.compute_start_slot_at_epoch(fork_2_epoch) - 1, phases + ) + sync_aggregate, _ = get_sync_aggregate(spec, state, phases=phases) + spec, state, block = transition_across_forks( + spec, + state, + spec.compute_start_slot_at_epoch(fork_2_epoch), + phases, + with_block=True, + sync_aggregate=sync_aggregate, + ) + + # Check that update applies + yield from emit_update( + test, spec, state, block, attested_state, attested_block, finalized_block, phases=phases + ) + assert test.store.finalized_header.beacon.slot == finalized_state.slot + assert test.store.next_sync_committee == finalized_state.next_sync_committee + assert test.store.best_valid_update is None + assert test.store.optimistic_header.beacon.slot == attested_state.slot + + # Finish test + yield from finish_lc_sync_test(test) diff --git a/tests/core/pyspec/eth2spec/test/helpers/merge/fork.py b/tests/core/pyspec/eth2spec/test/helpers/merge/fork.py deleted file mode 100644 index 5a45e8565b..0000000000 --- a/tests/core/pyspec/eth2spec/test/helpers/merge/fork.py +++ /dev/null @@ -1,46 +0,0 @@ -MERGE_FORK_TEST_META_TAGS = { - 'fork': 'merge', -} - - -def run_fork_test(post_spec, pre_state): - yield 'pre', pre_state - - post_state = post_spec.upgrade_to_merge(pre_state) - - # Stable fields - stable_fields = [ - 'genesis_time', 'genesis_validators_root', 'slot', - # History - 'latest_block_header', 'block_roots', 'state_roots', 'historical_roots', - # Eth1 - 'eth1_data', 'eth1_data_votes', 'eth1_deposit_index', - # Registry - 'validators', 'balances', - # Randomness - 'randao_mixes', - # Slashings - 'slashings', - # Participation - 'previous_epoch_participation', 'current_epoch_participation', - # Finality - 'justification_bits', 'previous_justified_checkpoint', 'current_justified_checkpoint', 'finalized_checkpoint', - # Inactivity - 'inactivity_scores', - # Sync - 'current_sync_committee', 'next_sync_committee' - ] - for field in stable_fields: - assert getattr(pre_state, field) == getattr(post_state, field) - - # Modified fields - modified_fields = ['fork'] - for field in modified_fields: - assert getattr(pre_state, field) != getattr(post_state, field) - - assert pre_state.fork.current_version == post_state.fork.previous_version - assert post_state.fork.current_version == post_spec.config.MERGE_FORK_VERSION - assert post_state.fork.epoch == post_spec.get_current_epoch(post_state) - assert post_state.latest_execution_payload_header == post_spec.ExecutionPayloadHeader() - - yield 'post', post_state diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py index c2cc6d98a4..314ac6a3be 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -1,20 +1,21 @@ from random import Random +from eth2spec.test.helpers.attestations import get_max_attestations, get_valid_attestation +from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.bls_to_execution_changes import get_signed_address_change +from eth2spec.test.helpers.deposits import build_deposit, deposit_from_context from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing from eth2spec.test.helpers.state import ( state_transition_and_sign_block, ) -from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, -) from eth2spec.test.helpers.sync_committee import ( - compute_committee_indices, compute_aggregate_sync_committee_signature, + compute_committee_indices, ) -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing -from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices -from eth2spec.test.helpers.attestations import get_valid_attestation -from eth2spec.test.helpers.deposits import build_deposit, deposit_from_context from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits @@ -25,12 +26,13 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) + spec, state, slashed_index=slash_index, signed_1=True, signed_2=True + ) signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] block.body.proposer_slashings.append(proposer_slashing) @@ -38,26 +40,26 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) - yield 'blocks', [signed_block] + yield "blocks", [signed_block] if not valid: - yield 'post', None + yield "post", None return - yield 'post', state + yield "post", state def get_random_proposer_slashings(spec, state, rng): num_slashings = rng.randrange(1, spec.MAX_PROPOSER_SLASHINGS) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() - indices = [ - index for index in active_indices - if not state.validators[index].slashed - ] + indices = [index for index in active_indices if not state.validators[index].slashed] slashings = [ get_valid_proposer_slashing( - spec, state, - slashed_index=indices.pop(rng.randrange(len(indices))), signed_1=True, signed_2=True, + spec, + state, + slashed_index=indices.pop(rng.randrange(len(indices))), + signed_1=True, + signed_2=True, ) for _ in range(num_slashings) ] @@ -75,11 +77,9 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): num_slashings = rng.randrange(1, spec.MAX_ATTESTER_SLASHINGS) active_indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() indices = [ - index for index in active_indices - if ( - not state.validators[index].slashed - and index not in slashed_indices - ) + index + for index in active_indices + if (not state.validators[index].slashed and index not in slashed_indices) ] sample_upper_bound = 4 max_slashed_count = num_slashings * sample_upper_bound - 1 @@ -89,10 +89,17 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot)) slashings = [ get_valid_attester_slashing_by_indices( - spec, state, - sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, sample_upper_bound))]), + spec, + state, + sorted( + [ + indices.pop(rng.randrange(len(indices))) + for _ in range(rng.randrange(1, sample_upper_bound)) + ] + ), slot=slot_range.pop(rng.randrange(len(slot_range))), - signed_1=True, signed_2=True, + signed_1=True, + signed_2=True, ) for _ in range(num_slashings) ] @@ -100,11 +107,12 @@ def get_random_attester_slashings(spec, state, rng, slashed_indices=[]): def get_random_attestations(spec, state, rng): - num_attestations = rng.randrange(1, spec.MAX_ATTESTATIONS) + num_attestations = rng.randrange(1, get_max_attestations(spec)) attestations = [ get_valid_attestation( - spec, state, + spec, + state, slot=rng.randrange(state.slot - spec.SLOTS_PER_EPOCH + 1, state.slot), signed=True, ) @@ -126,13 +134,15 @@ def get_random_deposits(spec, state, rng, num_deposits=None): # First build deposit data leaves for i in range(num_deposits): index = len(state.validators) + i + withdrawal_pubkey = pubkeys[-1 - index] + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(withdrawal_pubkey)[1:] _, root, deposit_data_leaves = build_deposit( spec, deposit_data_leaves, pubkeys[index], privkeys[index], spec.MAX_EFFECTIVE_BALANCE, - withdrawal_credentials=b'\x00' * 32, + withdrawal_credentials=withdrawal_credentials, signed=True, ) @@ -168,25 +178,23 @@ def _eligible_for_exit(spec, state, index): def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): num_exits = rng.randrange(1, spec.MAX_VOLUNTARY_EXITS) - active_indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) - indices = set( - index for index in active_indices - if _eligible_for_exit(spec, state, index) + active_indices = set( + spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() ) + indices = set(index for index in active_indices if _eligible_for_exit(spec, state, index)) eligible_indices = indices - to_be_slashed_indices indices_count = min(num_exits, len(eligible_indices)) exit_indices = [eligible_indices.pop() for _ in range(indices_count)] return prepare_signed_exits(spec, state, exit_indices) -def get_random_sync_aggregate(spec, state, slot, block_root=None, fraction_participated=1.0, rng=Random(2099)): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) +def get_random_sync_aggregate( + spec, state, slot, block_root=None, fraction_participated=1.0, rng=Random(2099) +): + committee_indices = compute_committee_indices(state, state.current_sync_committee) participant_count = int(len(committee_indices) * fraction_participated) participant_indices = rng.sample(range(len(committee_indices)), participant_count) - participants = [ - committee_indices[index] - for index in participant_indices - ] + participants = [committee_indices[index] for index in participant_indices] signature = compute_aggregate_sync_committee_signature( spec, state, @@ -195,18 +203,33 @@ def get_random_sync_aggregate(spec, state, slot, block_root=None, fraction_parti block_root=block_root, ) return spec.SyncAggregate( - sync_committee_bits=[index in participant_indices for index in range(len(committee_indices))], + sync_committee_bits=[ + index in participant_indices for index in range(len(committee_indices)) + ], sync_committee_signature=signature, ) +def get_random_bls_to_execution_changes(spec, state, rng=Random(2188), num_address_changes=0): + bls_indices = [ + index + for index, validator in enumerate(state.validators) + if validator.withdrawal_credentials[:1] == spec.BLS_WITHDRAWAL_PREFIX + ] + assert len(bls_indices) > 0 + + return [ + get_signed_address_change(spec, state, validator_index=validator_index) + for validator_index in rng.sample(bls_indices, min(num_address_changes, len(bls_indices))) + ] + + def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188), deposits=None): block = build_empty_block_for_next_slot(spec, state) proposer_slashings = get_random_proposer_slashings(spec, state, rng) block.body.proposer_slashings = proposer_slashings slashed_indices = [ - slashing.signed_header_1.message.proposer_index - for slashing in proposer_slashings + slashing.signed_header_1.message.proposer_index for slashing in proposer_slashings ] block.body.attester_slashings = get_random_attester_slashings(spec, state, rng, slashed_indices) block.body.attestations = get_random_attestations(spec, state, rng) @@ -214,10 +237,12 @@ def build_random_block_from_state_for_next_slot(spec, state, rng=Random(2188), d block.body.deposits = deposits # cannot include to be slashed indices as exits - slashed_indices = set([ - slashing.signed_header_1.message.proposer_index - for slashing in block.body.proposer_slashings - ]) + slashed_indices = set( + [ + slashing.signed_header_1.message.proposer_index + for slashing in block.body.proposer_slashings + ] + ) for attester_slashing in block.body.attester_slashings: slashed_indices = slashed_indices.union(attester_slashing.attestation_1.attesting_indices) slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices) @@ -234,9 +259,106 @@ def run_test_full_random_operations(spec, state, rng=Random(2080)): deposits = prepare_state_and_get_random_deposits(spec, state, rng) block = build_random_block_from_state_for_next_slot(spec, state, rng, deposits=deposits) - yield 'pre', state + yield "pre", state signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + 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/optimistic_sync.py b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py new file mode 100644 index 0000000000..53fbff267a --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/optimistic_sync.py @@ -0,0 +1,225 @@ +from dataclasses import dataclass +from enum import Enum + +from eth_utils import encode_hex + +from eth2spec.test.helpers.fork_choice import ( + add_block, +) +from eth2spec.utils.ssz.ssz_typing import Bytes32 + + +class PayloadStatusV1StatusAlias(Enum): + NOT_VALIDATED = "NOT_VALIDATED" + INVALIDATED = "INVALIDATED" + + +class PayloadStatusV1Status(Enum): + VALID = "VALID" + INVALID = "INVALID" + SYNCING = "SYNCING" + ACCEPTED = "ACCEPTED" + INVALID_BLOCK_HASH = "INVALID_BLOCK_HASH" + + @property + def alias(self) -> PayloadStatusV1StatusAlias: + if self.value in (self.SYNCING.value, self.ACCEPTED.value): + return PayloadStatusV1StatusAlias.NOT_VALIDATED + elif self.value in (self.INVALID.value, self.INVALID_BLOCK_HASH.value): + return PayloadStatusV1StatusAlias.INVALIDATED + + +@dataclass +class PayloadStatusV1: + status: PayloadStatusV1Status = PayloadStatusV1Status.VALID + latest_valid_hash: Bytes32 | None = None + validation_error: str | None = None + + @property + def formatted_output(self): + return { + "status": str(self.status.value), + "latest_valid_hash": ( + encode_hex(self.latest_valid_hash) if self.latest_valid_hash is not None else None + ), + "validation_error": ( + str(self.validation_error) if self.validation_error is not None else None + ), + } + + +class MegaStore: + spec = None + fc_store = None + opt_store = None + block_payload_statuses: dict[Bytes32, PayloadStatusV1] = dict() + + def __init__(self, spec, fc_store, opt_store): + self.spec = spec + self.fc_store = fc_store + self.opt_store = opt_store + + +def get_optimistic_store(spec, anchor_state, anchor_block): + assert anchor_block.state_root == anchor_state.hash_tree_root() + + opt_store = spec.OptimisticStore( + optimistic_roots=set(), + head_block_root=anchor_block.hash_tree_root(), + ) + anchor_block_root = anchor_block.hash_tree_root() + opt_store.blocks[anchor_block_root] = anchor_block.copy() + opt_store.block_states[anchor_block_root] = anchor_state.copy() + + return opt_store + + +def get_valid_flag_value(status: PayloadStatusV1Status) -> bool: + if status == PayloadStatusV1Status.VALID: + return True + elif status.alias == PayloadStatusV1StatusAlias.NOT_VALIDATED: + return True + else: + # status.alias == PayloadStatusV1StatusAlias.INVALIDATED or other cases + return False + + +def add_optimistic_block( + spec, + mega_store, + signed_block, + test_steps, + payload_status=None, + status=PayloadStatusV1Status.SYNCING, +): + """ + Add a block with optimistic sync logic + + ``valid`` indicates if the given ``signed_block.message.body.execution_payload`` is valid/invalid + from ``verify_and_notify_new_payload`` method response. + """ + block = signed_block.message + block_root = block.hash_tree_root() + el_block_hash = block.body.execution_payload.block_hash + + if payload_status is None: + payload_status = PayloadStatusV1(status=status) + if payload_status.status == PayloadStatusV1Status.VALID: + payload_status.latest_valid_hash = el_block_hash + + mega_store.block_payload_statuses[block_root] = payload_status + test_steps.append( + { + "block_hash": encode_hex(el_block_hash), + "payload_status": payload_status.formatted_output, + } + ) + + # Set `valid` flag + valid = get_valid_flag_value(payload_status.status) + + # Optimistic sync + + # Case: INVALID + if payload_status.status == PayloadStatusV1Status.INVALID: + # Update parent status to INVALID + assert payload_status.latest_valid_hash is not None + current_block = block + while el_block_hash != payload_status.latest_valid_hash and el_block_hash != spec.Bytes32(): + current_block_root = current_block.hash_tree_root() + assert current_block_root in mega_store.block_payload_statuses + mega_store.block_payload_statuses[ + current_block_root + ].status = PayloadStatusV1Status.INVALID + # Get parent + current_block = mega_store.fc_store.blocks[current_block.parent_root] + el_block_hash = current_block.body.execution_payload.block_hash + + yield from add_block( + spec, + mega_store.fc_store, + signed_block, + valid=valid, + test_steps=test_steps, + is_optimistic=True, + ) + + # Update stores + is_optimistic_candidate = spec.is_optimistic_candidate_block( + mega_store.opt_store, + current_slot=spec.get_current_slot(mega_store.fc_store), + block=signed_block.message, + ) + if is_optimistic_candidate: + mega_store.opt_store.optimistic_roots.add(block_root) + mega_store.opt_store.blocks[block_root] = signed_block.message.copy() + if not is_invalidated(mega_store, block_root): + mega_store.opt_store.block_states[block_root] = mega_store.fc_store.block_states[ + block_root + ].copy() + + # Clean up the invalidated blocks + clean_up_store(mega_store) + + # Update head + mega_store.opt_store.head_block_root = get_opt_head_block_root(spec, mega_store) + test_steps.append( + { + "checks": { + "head": get_formatted_optimistic_head_output(mega_store), + } + } + ) + + +def get_opt_head_block_root(spec, mega_store): + """ + Copied and modified from fork-choice spec `get_head` function. + """ + store = mega_store.fc_store + + # Get filtered block tree that only includes viable branches + blocks = spec.get_filtered_block_tree(store) + # Execute the LMD-GHOST fork choice + head = store.justified_checkpoint.root + while True: + children = [ + root + for root in blocks.keys() + if ( + blocks[root].parent_root == head + and not is_invalidated(mega_store, root) # For optimistic sync + ) + ] + if len(children) == 0: + return head + # Sort by latest attesting balance with ties broken lexicographically + # Ties broken by favoring block with lexicographically higher root + head = max(children, key=lambda root: (spec.get_weight(store, root), root)) + + +def is_invalidated(mega_store, block_root): + if block_root in mega_store.block_payload_statuses: + return ( + mega_store.block_payload_statuses[block_root].status.alias + == PayloadStatusV1StatusAlias.INVALIDATED + ) + else: + return False + + +def get_formatted_optimistic_head_output(mega_store): + head = mega_store.opt_store.head_block_root + slot = mega_store.fc_store.blocks[head].slot + return { + "slot": int(slot), + "root": encode_hex(head), + } + + +def clean_up_store(mega_store): + """ + Remove invalidated blocks + """ + # TODO + ... diff --git a/tests/core/pyspec/eth2spec/test/helpers/pow_block.py b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py new file mode 100644 index 0000000000..6a92c7703c --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/pow_block.py @@ -0,0 +1,37 @@ +from random import Random + +from eth2spec.utils.ssz.ssz_typing import uint256 + + +class PowChain: + blocks = [] + + def __init__(self, blocks): + self.blocks = blocks + + def __iter__(self): + return iter(self.blocks) + + def head(self, offset=0): + assert offset <= 0 + return self.blocks[offset - 1] + + def to_dict(self): + return {block.block_hash: block for block in self.blocks} + + +def prepare_random_pow_block(spec, rng=Random(3131)): + return spec.PowBlock( + block_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + parent_hash=spec.Hash32(spec.hash(bytearray(rng.getrandbits(8) for _ in range(32)))), + total_difficulty=uint256(0), + ) + + +def prepare_random_pow_chain(spec, length, rng=Random(3131)) -> PowChain: + assert length > 0 + chain = [prepare_random_pow_block(spec, rng)] + for i in range(1, length): + chain.append(prepare_random_pow_block(spec, rng)) + chain[i].parent_hash = chain[i - 1].block_hash + return PowChain(chain) diff --git a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py index ac0a1cce2d..fa6d4c5248 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/proposer_slashings.py @@ -1,5 +1,5 @@ -from eth2spec.test.context import is_post_altair from eth2spec.test.helpers.block_header import sign_block_header +from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix, is_post_electra from eth2spec.test.helpers.keys import pubkey_to_privkey from eth2spec.test.helpers.state import get_balance from eth2spec.test.helpers.sync_committee import ( @@ -9,12 +9,23 @@ def get_min_slashing_penalty_quotient(spec): - if is_post_altair(spec): + if is_post_electra(spec): + return spec.MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA + elif is_post_bellatrix(spec): + return spec.MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX + elif is_post_altair(spec): return spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR else: return spec.MIN_SLASHING_PENALTY_QUOTIENT +def get_whistleblower_reward_quotient(spec): + if is_post_electra(spec): + return spec.WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA + else: + return spec.WHISTLEBLOWER_REWARD_QUOTIENT + + def check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block=None): slashed_validator = state.validators[slashed_index] assert slashed_validator.slashed @@ -22,58 +33,77 @@ def check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block= assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH proposer_index = spec.get_beacon_proposer_index(state) - slash_penalty = state.validators[slashed_index].effective_balance // get_min_slashing_penalty_quotient(spec) - whistleblower_reward = state.validators[slashed_index].effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT + slash_penalty = state.validators[ + slashed_index + ].effective_balance // get_min_slashing_penalty_quotient(spec) + whistleblower_reward = state.validators[ + slashed_index + ].effective_balance // get_whistleblower_reward_quotient(spec) # Altair introduces sync committee (SC) reward and penalty - sc_reward_for_slashed = sc_penalty_for_slashed = sc_reward_for_proposer = sc_penalty_for_proposer = 0 + sc_reward_for_slashed = sc_penalty_for_slashed = sc_reward_for_proposer = ( + sc_penalty_for_proposer + ) = 0 if is_post_altair(spec) and block is not None: - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(state, state.current_sync_committee) committee_bits = block.body.sync_aggregate.sync_committee_bits - sc_reward_for_slashed, sc_penalty_for_slashed = compute_sync_committee_participant_reward_and_penalty( - spec, - pre_state, - slashed_index, - committee_indices, - committee_bits, + sc_reward_for_slashed, sc_penalty_for_slashed = ( + compute_sync_committee_participant_reward_and_penalty( + spec, + pre_state, + slashed_index, + committee_indices, + committee_bits, + ) ) - sc_reward_for_proposer, sc_penalty_for_proposer = compute_sync_committee_participant_reward_and_penalty( - spec, - pre_state, - proposer_index, - committee_indices, - committee_bits, + sc_reward_for_proposer, sc_penalty_for_proposer = ( + compute_sync_committee_participant_reward_and_penalty( + spec, + pre_state, + proposer_index, + committee_indices, + committee_bits, + ) ) if proposer_index != slashed_index: # slashed validator lost initial slash penalty assert ( get_balance(state, slashed_index) - == get_balance(pre_state, slashed_index) - slash_penalty + sc_reward_for_slashed - sc_penalty_for_slashed + == get_balance(pre_state, slashed_index) + - slash_penalty + + sc_reward_for_slashed + - sc_penalty_for_slashed ) # block proposer gained whistleblower reward # >= because proposer could have reported multiple - assert ( - get_balance(state, proposer_index) - >= ( - get_balance(pre_state, proposer_index) + whistleblower_reward - + sc_reward_for_proposer - sc_penalty_for_proposer - ) + assert get_balance(state, proposer_index) >= ( + get_balance(pre_state, proposer_index) + + whistleblower_reward + + sc_reward_for_proposer + - sc_penalty_for_proposer ) else: # proposer reported themself so get penalty and reward # >= because proposer could have reported multiple - assert ( - get_balance(state, slashed_index) - >= ( - get_balance(pre_state, slashed_index) - slash_penalty + whistleblower_reward - + sc_reward_for_slashed - sc_penalty_for_slashed - ) + assert get_balance(state, slashed_index) >= ( + get_balance(pre_state, slashed_index) + - slash_penalty + + whistleblower_reward + + sc_reward_for_slashed + - sc_penalty_for_slashed ) -def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, - slashed_index=None, slot=None, signed_1=False, signed_2=False): +def get_valid_proposer_slashing( + spec, + state, + random_root=b"\x99" * 32, + slashed_index=None, + slot=None, + signed_1=False, + signed_2=False, +): if slashed_index is None: current_epoch = spec.get_current_epoch(state) slashed_index = spec.get_active_validator_indices(state, current_epoch)[-1] @@ -84,9 +114,9 @@ def get_valid_proposer_slashing(spec, state, random_root=b'\x99' * 32, header_1 = spec.BeaconBlockHeader( slot=slot, proposer_index=slashed_index, - parent_root=b'\x33' * 32, - state_root=b'\x44' * 32, - body_root=b'\x55' * 32, + parent_root=b"\x33" * 32, + state_root=b"\x44" * 32, + body_root=b"\x55" * 32, ) header_2 = header_1.copy() header_2.parent_root = random_root diff --git a/tests/core/pyspec/eth2spec/test/helpers/random.py b/tests/core/pyspec/eth2spec/test/helpers/random.py index 8448b24248..a1b89c001e 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/random.py +++ b/tests/core/pyspec/eth2spec/test/helpers/random.py @@ -1,12 +1,36 @@ from random import Random from eth2spec.test.helpers.attestations import cached_prepare_state_with_attestations -from eth2spec.test.context import is_post_altair from eth2spec.test.helpers.deposits import mock_deposit +from eth2spec.test.helpers.forks import is_post_altair from eth2spec.test.helpers.state import next_epoch +def set_some_activations(spec, state, rng, activation_epoch=None): + if activation_epoch is None: + activation_epoch = spec.get_current_epoch(state) + num_validators = len(state.validators) + selected_indices = [] + for index in range(num_validators): + # If is slashed or exiting, skip + if ( + state.validators[index].slashed + or state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH + ): + continue + # Set ~1/10 validators' activation_eligibility_epoch and activation_epoch + if rng.randrange(num_validators) < num_validators // 10: + state.validators[index].activation_eligibility_epoch = max( + int(activation_epoch) - int(spec.MAX_SEED_LOOKAHEAD) - 1, + spec.GENESIS_EPOCH, + ) + state.validators[index].activation_epoch = activation_epoch + selected_indices.append(index) + return selected_indices + + def set_some_new_deposits(spec, state, rng): + deposited_indices = [] num_validators = len(state.validators) # Set ~1/10 to just recently deposited for index in range(num_validators): @@ -15,46 +39,70 @@ def set_some_new_deposits(spec, state, rng): continue if rng.randrange(num_validators) < num_validators // 10: mock_deposit(spec, state, index) - # Set ~half of selected to eligible for activation if rng.choice([True, False]): + # Set ~half of selected to eligible for activation state.validators[index].activation_eligibility_epoch = spec.get_current_epoch(state) + else: + # The validators that just made a deposit + deposited_indices.append(index) + return deposited_indices -def exit_random_validators(spec, state, rng, fraction=None): - if fraction is None: - # Exit ~1/2 - fraction = 0.5 +def exit_random_validators( + spec, state, rng, fraction=0.5, exit_epoch=None, withdrawable_epoch=None, from_epoch=None +): + """ + Set some validators' exit_epoch and withdrawable_epoch. - if spec.get_current_epoch(state) < 5: - # Move epochs forward to allow for some validators already exited/withdrawable - for _ in range(5): - next_epoch(spec, state) + If exit_epoch is configured, use the given exit_epoch. Otherwise, randomly set exit_epoch and withdrawable_epoch. + """ + if from_epoch is None: + from_epoch = spec.MAX_SEED_LOOKAHEAD + 1 + epoch_diff = int(from_epoch) - int(spec.get_current_epoch(state)) + for _ in range(epoch_diff): + # NOTE: if `epoch_diff` is negative, then this loop body does not execute. + next_epoch(spec, state) current_epoch = spec.get_current_epoch(state) + exited_indices = [] for index in spec.get_active_validator_indices(state, current_epoch): sampled = rng.random() < fraction if not sampled: continue + exited_indices.append(index) validator = state.validators[index] - validator.exit_epoch = rng.choice([current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3]) - # ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch) - if rng.choice([True, False]): - validator.withdrawable_epoch = current_epoch + if exit_epoch is None: + assert withdrawable_epoch is None + validator.exit_epoch = rng.choice( + [current_epoch, current_epoch - 1, current_epoch - 2, current_epoch - 3] + ) + # ~1/2 are withdrawable (note, unnatural span between exit epoch and withdrawable epoch) + if rng.choice([True, False]): + validator.withdrawable_epoch = current_epoch + else: + validator.withdrawable_epoch = current_epoch + 1 else: - validator.withdrawable_epoch = current_epoch + 1 + validator.exit_epoch = exit_epoch + if withdrawable_epoch is None: + validator.withdrawable_epoch = ( + validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) + else: + validator.withdrawable_epoch = withdrawable_epoch + return exited_indices -def slash_random_validators(spec, state, rng, fraction=None): - if fraction is None: - # Slash ~1/2 of validators - fraction = 0.5 +def slash_random_validators(spec, state, rng, fraction=0.5): + slashed_indices = [] for index in range(len(state.validators)): # slash at least one validator sampled = rng.random() < fraction if index == 0 or sampled: spec.slash_validator(state, index) + slashed_indices.append(index) + return slashed_indices def randomize_epoch_participation(spec, state, epoch, rng): @@ -67,13 +115,14 @@ def randomize_epoch_participation(spec, state, epoch, rng): for pending_attestation in pending_attestations: # ~1/3 have bad target if rng.randint(0, 2) == 0: - pending_attestation.data.target.root = b'\x55' * 32 + pending_attestation.data.target.root = b"\x55" * 32 # ~1/3 have bad head if rng.randint(0, 2) == 0: - pending_attestation.data.beacon_block_root = b'\x66' * 32 + pending_attestation.data.beacon_block_root = b"\x66" * 32 # ~50% participation - pending_attestation.aggregation_bits = [rng.choice([True, False]) - for _ in pending_attestation.aggregation_bits] + pending_attestation.aggregation_bits = [ + rng.choice([True, False]) for _ in pending_attestation.aggregation_bits + ] # Random inclusion delay pending_attestation.inclusion_delay = rng.randint(1, spec.SLOTS_PER_EPOCH) else: @@ -92,7 +141,7 @@ def set_flag(index, value): if value: flags |= flag else: - flags &= 0xff ^ flag + flags &= 0xFF ^ flag set_flag(spec.TIMELY_HEAD_FLAG_INDEX, is_timely_correct_head) if is_timely_correct_head: @@ -114,7 +163,9 @@ def randomize_previous_epoch_participation(spec, state, rng=Random(8020)): if not is_post_altair(spec): state.current_epoch_attestations = [] else: - state.current_epoch_participation = [spec.ParticipationFlags(0b0000_0000) for _ in range(len(state.validators))] + state.current_epoch_participation = [ + spec.ParticipationFlags(0b0000_0000) for _ in range(len(state.validators)) + ] def randomize_attestation_participation(spec, state, rng=Random(8020)): @@ -123,7 +174,7 @@ def randomize_attestation_participation(spec, state, rng=Random(8020)): randomize_epoch_participation(spec, state, spec.get_current_epoch(state), rng) -def randomize_state(spec, state, rng=Random(8020), exit_fraction=None, slash_fraction=None): +def randomize_state(spec, state, rng=Random(8020), exit_fraction=0.5, slash_fraction=0.5): set_some_new_deposits(spec, state, rng) exit_random_validators(spec, state, rng, fraction=exit_fraction) slash_random_validators(spec, state, rng, fraction=slash_fraction) diff --git a/tests/core/pyspec/eth2spec/test/helpers/rewards.py b/tests/core/pyspec/eth2spec/test/helpers/rewards.py index ec617bda9b..f245820acd 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/rewards.py +++ b/tests/core/pyspec/eth2spec/test/helpers/rewards.py @@ -1,19 +1,22 @@ from random import Random + from lru import LRU from eth2spec.phase0.mainnet import VALIDATOR_REGISTRY_LIMIT # equal everywhere, fine to import -from eth2spec.test.context import is_post_altair -from eth2spec.test.helpers.state import ( - next_epoch, +from eth2spec.test.helpers.attestations import ( + cached_prepare_state_with_attestations, ) +from eth2spec.test.helpers.forks import is_post_altair, is_post_bellatrix from eth2spec.test.helpers.random import ( - set_some_new_deposits, exit_random_validators, slash_random_validators, + exit_random_validators, randomize_state, + set_some_new_deposits, + slash_random_validators, ) -from eth2spec.test.helpers.attestations import ( - cached_prepare_state_with_attestations, +from eth2spec.test.helpers.state import ( + next_epoch, ) -from eth2spec.utils.ssz.ssz_typing import Container, uint64, List +from eth2spec.utils.ssz.ssz_typing import Container, List, uint64 class Deltas(Container): @@ -21,6 +24,15 @@ class Deltas(Container): penalties: List[uint64, VALIDATOR_REGISTRY_LIMIT] +def get_inactivity_penalty_quotient(spec): + if is_post_bellatrix(spec): + return spec.INACTIVITY_PENALTY_QUOTIENT_BELLATRIX + elif is_post_altair(spec): + return spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR + else: + return spec.INACTIVITY_PENALTY_QUOTIENT + + def has_enough_for_reward(spec, state, index): """ Check if base_reward will be non-zero. @@ -30,7 +42,8 @@ def has_enough_for_reward(spec, state, index): """ return ( state.validators[index].effective_balance * spec.BASE_REWARD_FACTOR - > spec.integer_squareroot(spec.get_total_active_balance(state)) // spec.BASE_REWARDS_PER_EPOCH + > spec.integer_squareroot(spec.get_total_active_balance(state)) + // spec.BASE_REWARDS_PER_EPOCH ) @@ -43,10 +56,9 @@ def has_enough_for_leak_penalty(spec, state, index): """ if is_post_altair(spec): - return ( - state.validators[index].effective_balance * state.inactivity_scores[index] - > spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR - ) + return state.validators[index].effective_balance * state.inactivity_scores[ + index + ] > spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec) else: return ( state.validators[index].effective_balance * spec.get_finality_delay(state) @@ -65,9 +77,10 @@ def run_deltas(spec, state): - inclusion delay deltas ('inclusion_delay_deltas') - inactivity penalty deltas ('inactivity_penalty_deltas') """ - yield 'pre', state + yield "pre", state if is_post_altair(spec): + def get_source_deltas(state): return spec.get_flag_index_deltas(state, spec.TIMELY_SOURCE_FLAG_INDEX) @@ -82,21 +95,21 @@ def get_target_deltas(state): state, spec.get_source_deltas if not is_post_altair(spec) else get_source_deltas, spec.get_matching_source_attestations, - 'source_deltas', + "source_deltas", ) yield from run_attestation_component_deltas( spec, state, spec.get_target_deltas if not is_post_altair(spec) else get_target_deltas, spec.get_matching_target_attestations, - 'target_deltas', + "target_deltas", ) yield from run_attestation_component_deltas( spec, state, spec.get_head_deltas if not is_post_altair(spec) else get_head_deltas, spec.get_matching_head_attestations, - 'head_deltas', + "head_deltas", ) if not is_post_altair(spec): yield from run_get_inclusion_delay_deltas(spec, state) @@ -104,13 +117,13 @@ def get_target_deltas(state): def deltas_name_to_flag_index(spec, deltas_name): - if 'source' in deltas_name: + if "source" in deltas_name: return spec.TIMELY_SOURCE_FLAG_INDEX - elif 'head' in deltas_name: + elif "head" in deltas_name: return spec.TIMELY_HEAD_FLAG_INDEX - elif 'target' in deltas_name: + elif "target" in deltas_name: return spec.TIMELY_TARGET_FLAG_INDEX - raise ValueError("Wrong deltas_name %s" % deltas_name) + raise ValueError(f"Wrong deltas_name {deltas_name}") def run_attestation_component_deltas(spec, state, component_delta_fn, matching_att_fn, deltas_name): @@ -145,16 +158,15 @@ def run_attestation_component_deltas(spec, state, component_delta_fn, matching_a assert rewards[index] > 0 else: assert rewards[index] == 0 + elif enough_for_reward: + assert rewards[index] > 0 else: - if enough_for_reward: - assert rewards[index] > 0 - else: - assert rewards[index] == 0 + assert rewards[index] == 0 assert penalties[index] == 0 else: assert rewards[index] == 0 - if is_post_altair(spec) and 'head' in deltas_name: + if is_post_altair(spec) and "head" in deltas_name: assert penalties[index] == 0 elif enough_for_reward: assert penalties[index] > 0 @@ -169,15 +181,19 @@ def run_get_inclusion_delay_deltas(spec, state): """ if is_post_altair(spec): # No inclusion_delay_deltas - yield 'inclusion_delay_deltas', Deltas(rewards=[0] * len(state.validators), - penalties=[0] * len(state.validators)) + yield ( + "inclusion_delay_deltas", + Deltas(rewards=[0] * len(state.validators), penalties=[0] * len(state.validators)), + ) return rewards, penalties = spec.get_inclusion_delay_deltas(state) - yield 'inclusion_delay_deltas', Deltas(rewards=rewards, penalties=penalties) + yield "inclusion_delay_deltas", Deltas(rewards=rewards, penalties=penalties) - eligible_attestations = spec.get_matching_source_attestations(state, spec.get_previous_epoch(state)) + eligible_attestations = spec.get_matching_source_attestations( + state, spec.get_previous_epoch(state) + ) attesting_indices = spec.get_unslashed_attesting_indices(state, eligible_attestations) rewarded_indices = set() @@ -190,14 +206,14 @@ def run_get_inclusion_delay_deltas(spec, state): rewarded_indices.add(index) # Track proposer of earliest included attestation for the validator defined by index - earliest_attestation = min([ - a for a in eligible_attestations - if index in spec.get_attesting_indices(state, a.data, a.aggregation_bits) - ], key=lambda a: a.inclusion_delay) + earliest_attestation = min( + [a for a in eligible_attestations if index in spec.get_attesting_indices(state, a)], + key=lambda a: a.inclusion_delay, + ) rewarded_proposer_indices.add(earliest_attestation.proposer_index) # Ensure all expected proposers have been rewarded - # Track rewarde indices + # Track reward indices proposing_indices = [a.proposer_index for a in eligible_attestations] for index in proposing_indices: if index in rewarded_proposer_indices: @@ -218,11 +234,15 @@ def run_get_inactivity_penalty_deltas(spec, state): """ rewards, penalties = spec.get_inactivity_penalty_deltas(state) - yield 'inactivity_penalty_deltas', Deltas(rewards=rewards, penalties=penalties) + yield "inactivity_penalty_deltas", Deltas(rewards=rewards, penalties=penalties) if not is_post_altair(spec): - matching_attestations = spec.get_matching_target_attestations(state, spec.get_previous_epoch(state)) - matching_attesting_indices = spec.get_unslashed_attesting_indices(state, matching_attestations) + matching_attestations = spec.get_matching_target_attestations( + state, spec.get_previous_epoch(state) + ) + matching_attesting_indices = spec.get_unslashed_attesting_indices( + state, matching_attestations + ) else: matching_attesting_indices = spec.get_unslashed_participating_indices( state, spec.TIMELY_TARGET_FLAG_INDEX, spec.get_previous_epoch(state) @@ -240,34 +260,40 @@ def run_get_inactivity_penalty_deltas(spec, state): base_reward = spec.get_base_reward(state, index) if not is_post_altair(spec): cancel_base_rewards_per_epoch = spec.BASE_REWARDS_PER_EPOCH - base_penalty = cancel_base_rewards_per_epoch * base_reward - spec.get_proposer_reward(state, index) + base_penalty = ( + cancel_base_rewards_per_epoch * base_reward + - spec.get_proposer_reward(state, index) + ) if not has_enough_for_reward(spec, state, index): assert penalties[index] == 0 - elif index in matching_attesting_indices or not has_enough_for_leak_penalty(spec, state, index): + elif index in matching_attesting_indices or not has_enough_for_leak_penalty( + spec, state, index + ): if is_post_altair(spec): assert penalties[index] == 0 else: assert penalties[index] == base_penalty + elif is_post_altair(spec): + assert penalties[index] > 0 else: - if is_post_altair(spec): - assert penalties[index] > 0 - else: - assert penalties[index] > base_penalty + assert penalties[index] > base_penalty + elif not is_post_altair(spec): + assert penalties[index] == 0 + continue + # post altair, this penalty is derived from the inactivity score + # regardless if the state is leaking or not... + elif index in matching_attesting_indices: + assert penalties[index] == 0 else: - if not is_post_altair(spec): - assert penalties[index] == 0 - continue - else: - # post altair, this penalty is derived from the inactivity score - # regardless if the state is leaking or not... - if index in matching_attesting_indices: - assert penalties[index] == 0 - else: - # copied from spec: - penalty_numerator = state.validators[index].effective_balance * state.inactivity_scores[index] - penalty_denominator = spec.config.INACTIVITY_SCORE_BIAS * spec.INACTIVITY_PENALTY_QUOTIENT_ALTAIR - assert penalties[index] == penalty_numerator // penalty_denominator + # copied from spec: + penalty_numerator = ( + state.validators[index].effective_balance * state.inactivity_scores[index] + ) + penalty_denominator = ( + spec.config.INACTIVITY_SCORE_BIAS * get_inactivity_penalty_quotient(spec) + ) + assert penalties[index] == penalty_numerator // penalty_denominator def transition_state_to_leak(spec, state, epochs=None): @@ -290,17 +316,25 @@ def entry(*args, spec, state, **kw): # If the pre-state is not already known in the LRU, then take it, # transition it to leak, and put it in the LRU. # The input state is likely already cached, so the hash-tree-root does not affect speed. - key = (state.hash_tree_root(), spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY, spec.SLOTS_PER_EPOCH, epochs) - global _cache_dict + key = ( + state.hash_tree_root(), + spec.MIN_EPOCHS_TO_INACTIVITY_PENALTY, + spec.SLOTS_PER_EPOCH, + epochs, + ) if key not in _cache_dict: transition_state_to_leak(spec, state, epochs=epochs) - _cache_dict[key] = state.get_backing() # cache the tree structure, not the view wrapping it. + _cache_dict[key] = ( + state.get_backing() + ) # cache the tree structure, not the view wrapping it. # Take an entry out of the LRU. # No copy is necessary, as we wrap the immutable backing with a new view. state = spec.BeaconState(backing=_cache_dict[key]) return fn(*args, spec=spec, state=state, **kw) + return entry + return deco @@ -417,9 +451,9 @@ def run_test_full_fraction_incorrect(spec, state, correct_target, correct_head, num_incorrect = int(fraction_incorrect * len(state.previous_epoch_attestations)) for pending_attestation in state.previous_epoch_attestations[:num_incorrect]: if not correct_target: - pending_attestation.data.target.root = b'\x55' * 32 + pending_attestation.data.target.root = b"\x55" * 32 if not correct_head: - pending_attestation.data.beacon_block_root = b'\x66' * 32 + pending_attestation.data.beacon_block_root = b"\x66" * 32 yield from run_deltas(spec, state) @@ -491,7 +525,7 @@ def run_test_duplicate_attestations_at_later_slots(spec, state): state.previous_epoch_attestations = sorted( state.previous_epoch_attestations + later_attestations, - key=lambda a: a.data.slot + a.inclusion_delay + key=lambda a: a.data.slot + a.inclusion_delay, ) yield from run_deltas(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py index c8d60938b7..0436675973 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/shard_block.py +++ b/tests/core/pyspec/eth2spec/test/helpers/shard_block.py @@ -11,18 +11,16 @@ def sign_shard_block(spec, beacon_state, shard, block, proposer_index=None): proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) privkey = privkeys[proposer_index] - domain = spec.get_domain(beacon_state, spec.DOMAIN_SHARD_PROPOSAL, spec.compute_epoch_at_slot(slot)) + domain = spec.get_domain( + beacon_state, spec.DOMAIN_SHARD_PROPOSAL, spec.compute_epoch_at_slot(slot) + ) signing_root = spec.compute_signing_root(block.message, domain) block.signature = bls.Sign(privkey, signing_root) -def build_shard_block(spec, - beacon_state, - shard, - slot=None, - body=None, - shard_parent_state=None, - signed=False): +def build_shard_block( + spec, beacon_state, shard, slot=None, body=None, shard_parent_state=None, signed=False +): if shard_parent_state is None: shard_parent_state = beacon_state.shard_states[shard] @@ -32,7 +30,9 @@ def build_shard_block(spec, if body is None: body = get_sample_shard_block_body(spec) - beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot(spec, beacon_state, slot) + beacon_state, beacon_parent_root = get_state_and_beacon_parent_root_at_slot( + spec, beacon_state, slot + ) proposer_index = spec.get_shard_proposer_index(beacon_state, slot, shard) block = spec.ShardBlock( shard_parent_root=shard_parent_state.latest_block_root, @@ -66,7 +66,10 @@ def get_shard_transitions(spec, parent_beacon_state, shard_block_dict): if len(blocks) > 0: shard_block_root = blocks[-1].message.hash_tree_root() - assert shard_transition.shard_states[len_offset_slots - 1].latest_block_root == shard_block_root + assert ( + shard_transition.shard_states[len_offset_slots - 1].latest_block_root + == shard_block_root + ) assert shard_transition.shard_states[len_offset_slots - 1].slot == offset_slots[-1] shard_transitions[shard] = shard_transition @@ -85,4 +88,4 @@ def get_committee_index_of_shard(spec, state, slot, shard): # Optional[Committe def get_sample_shard_block_body(spec, is_max=False): size = spec.MAX_SHARD_BLOCK_SIZE if is_max else 128 - return b'\x56' * size + return b"\x56" * size diff --git a/tests/core/pyspec/eth2spec/test/helpers/specs.py b/tests/core/pyspec/eth2spec/test/helpers/specs.py new file mode 100644 index 0000000000..73ceea5552 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/specs.py @@ -0,0 +1,26 @@ +from .constants import ( + ALL_PHASES, + EIP7441, + MAINNET, + MINIMAL, +) +from .typing import ( + PresetBaseName, + Spec, + SpecForkName, +) + +# NOTE: special case like `ALLOWED_TEST_RUNNER_FORKS` +ALL_EXECUTABLE_SPEC_NAMES = ALL_PHASES + (EIP7441,) + +# import the spec for each fork and preset +for fork in ALL_EXECUTABLE_SPEC_NAMES: + exec( + f"from eth2spec.{fork} import mainnet as spec_{fork}_mainnet, minimal as spec_{fork}_minimal" + ) + +# this is the only output of this file +spec_targets: dict[PresetBaseName, dict[SpecForkName, Spec]] = { + MINIMAL: {fork: eval(f"spec_{fork}_minimal") for fork in ALL_EXECUTABLE_SPEC_NAMES}, + MAINNET: {fork: eval(f"spec_{fork}_mainnet") for fork in ALL_EXECUTABLE_SPEC_NAMES}, +} diff --git a/tests/core/pyspec/eth2spec/test/helpers/state.py b/tests/core/pyspec/eth2spec/test/helpers/state.py index 6f1923e542..092a6d2538 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/state.py +++ b/tests/core/pyspec/eth2spec/test/helpers/state.py @@ -1,6 +1,14 @@ -from eth2spec.test.context import expect_assertion_error, is_post_altair +from collections.abc import Sequence + +from remerkleable.basic import uint64 +from remerkleable.byte_arrays import Bytes32 + +from eth2spec.test.context import expect_assertion_error from eth2spec.test.helpers.block import apply_empty_block, sign_block, transition_unsigned_block +from eth2spec.test.helpers.forks import is_post_altair, is_post_eip7732 from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators +from eth2spec.utils.hash_function import hash +from eth2spec.utils.ssz.ssz_impl import uint_to_bytes def get_balance(state, index): @@ -41,15 +49,6 @@ def transition_to_slot_via_block(spec, state, slot): assert state.slot == slot -def transition_to_valid_shard_slot(spec, state): - """ - Transition to slot `compute_epoch_at_slot(spec.config.SHARDING_FORK_EPOCH) + 1` - and fork at `compute_epoch_at_slot(spec.config.SHARDING_FORK_EPOCH)`. - """ - transition_to(spec, state, spec.compute_epoch_at_slot(spec.config.SHARDING_FORK_EPOCH)) - next_slot(spec, state) - - def next_epoch(spec, state): """ Transition to the start slot of the next epoch @@ -59,11 +58,21 @@ def next_epoch(spec, state): spec.process_slots(state, slot) +def next_epoch_with_full_participation(spec, state): + """ + Transition to the start slot of the next epoch with full participation + """ + set_full_participation(spec, state) + next_epoch(spec, state) + + def next_epoch_via_block(spec, state, insert_state_root=False): """ Transition to the start slot of the next epoch via a full block transition """ - block = apply_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) + block = apply_empty_block( + spec, state, state.slot + spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH + ) if insert_state_root: block.state_root = state.hash_tree_root() return block @@ -82,6 +91,27 @@ def get_state_root(spec, state, slot) -> bytes: return state.state_roots[slot % spec.SLOTS_PER_HISTORICAL_ROOT] +def payload_state_transition_no_store(spec, state, block): + if is_post_eip7732(spec): + # cache the latest block header + previous_state_root = state.hash_tree_root() + if state.latest_block_header.state_root == spec.Root(): + state.latest_block_header.state_root = previous_state_root + # also perform the state transition as if the payload was revealed + state.latest_block_hash = block.body.signed_execution_payload_header.message.block_hash + state.latest_full_slot = block.slot + return state + + +def payload_state_transition(spec, store, block): + root = block.hash_tree_root() + state = store.block_states[root].copy() + if is_post_eip7732(spec): + payload_state_transition_no_store(spec, state, block) + store.execution_payload_states[root] = state + return state + + def state_transition_and_sign_block(spec, state, block, expect_fail=False): """ State transition via the provided ``block`` @@ -164,4 +194,103 @@ def has_active_balance_differential(spec, state): """ active_balance = spec.get_total_active_balance(state) total_balance = spec.get_total_balance(state, set(range(len(state.validators)))) - return active_balance // spec.EFFECTIVE_BALANCE_INCREMENT != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT + return ( + active_balance // spec.EFFECTIVE_BALANCE_INCREMENT + != total_balance // spec.EFFECTIVE_BALANCE_INCREMENT + ) + + +def get_validator_index_by_pubkey(state, pubkey): + index = next( + (i for i, validator in enumerate(state.validators) if validator.pubkey == pubkey), None + ) + return index + + +def advance_finality_to(spec, state, epoch): + while state.finalized_checkpoint.epoch < epoch: + next_epoch_with_full_participation(spec, state) + + +def simulate_lookahead(spec, state): + """ + Simulate the lookahead by advancing the state forward with empty slots and + calling `get_beacon_proposer_index`. + """ + lookahead = [] + simulation_state = state.copy() + for _ in range(spec.SLOTS_PER_EPOCH * (spec.MIN_SEED_LOOKAHEAD + 1)): + proposer_index = spec.get_beacon_proposer_index(simulation_state) + lookahead.append(proposer_index) + next_slot(spec, simulation_state) + return lookahead + + +def cause_effective_balance_decrease_below_threshold( + spec, state, validator_index: uint64, threshold: uint64 +) -> None: + """ + Cause an effective balance decrease change for the validator at + `validator_index` below a threshold + """ + HYSTERESIS_INCREMENT = uint64(spec.EFFECTIVE_BALANCE_INCREMENT // spec.HYSTERESIS_QUOTIENT) + DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * spec.HYSTERESIS_DOWNWARD_MULTIPLIER + state.balances[validator_index] = ( + min(threshold, state.validators[validator_index].effective_balance - DOWNWARD_THRESHOLD) - 1 + ) + + +def simulate_lookahead_with_thresholds(spec, state) -> Sequence[tuple[uint64, uint64]]: + """ + Simulate the lookahead by advancing the state forward with empty slots and + calling `get_beacon_proposer_index`. Returns along, the lookaheads. + """ + lookahead = [] + simulation_state = state.copy() + for _ in range(spec.SLOTS_PER_EPOCH * (spec.MIN_SEED_LOOKAHEAD + 1)): + proposer_index = get_beacon_proposer_index_and_threshold(spec, simulation_state) + lookahead.append(proposer_index) + next_slot(spec, simulation_state) + return lookahead + + +def get_beacon_proposer_index_and_threshold(spec, state) -> tuple[uint64, uint64]: + """ + Return the beacon proposer index at the current slot, + along with the threshold for that index. + """ + epoch = spec.get_current_epoch(state) + seed = hash( + spec.get_seed(state, epoch, spec.DOMAIN_BEACON_PROPOSER) + uint_to_bytes(state.slot) + ) + indices = spec.get_active_validator_indices(state, epoch) + return electra_compute_proposer_index_and_threshold(spec, state, indices, seed) + + +def electra_compute_proposer_index_and_threshold( + spec, state, indices: Sequence[uint64], seed: Bytes32 +) -> tuple[uint64, uint64]: + """ + Return from ``indices`` a random index sampled by effective balance, + along with the threshold for that index. + """ + assert len(indices) > 0 + MAX_RANDOM_VALUE = 2**16 - 1 # [Modified in Electra] + i = uint64(0) + total = uint64(len(indices)) + while True: + candidate_index = indices[spec.compute_shuffled_index(i % total, total, seed)] + # [Modified in Electra] + random_bytes = hash(seed + uint_to_bytes(i // 16)) + offset = i % 16 * 2 + random_value = spec.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_VALUE + >= spec.MAX_EFFECTIVE_BALANCE_ELECTRA * random_value + ): + return candidate_index, ( + spec.MAX_EFFECTIVE_BALANCE_ELECTRA * random_value + ) // MAX_RANDOM_VALUE + i += 1 diff --git a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py index 417802ece8..8237e0ee39 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py +++ b/tests/core/pyspec/eth2spec/test/helpers/sync_committee.py @@ -3,11 +3,11 @@ from eth2spec.test.context import ( expect_assertion_error, ) -from eth2spec.test.helpers.keys import privkeys from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) from eth2spec.test.helpers.block_processing import run_block_processing_to +from eth2spec.test.helpers.keys import privkeys from eth2spec.utils import bls @@ -24,7 +24,9 @@ def compute_sync_committee_signature(spec, state, slot, privkey, block_root=None return bls.Sign(privkey, signing_root) -def compute_aggregate_sync_committee_signature(spec, state, slot, participants, block_root=None, domain_type=None): +def compute_aggregate_sync_committee_signature( + spec, state, slot, participants, block_root=None, domain_type=None +): if len(participants) == 0: return spec.G2_POINT_AT_INFINITY @@ -45,24 +47,33 @@ def compute_aggregate_sync_committee_signature(spec, state, slot, participants, def compute_sync_committee_inclusion_reward(spec, state): - total_active_increments = spec.get_total_active_balance(state) // spec.EFFECTIVE_BALANCE_INCREMENT + total_active_increments = ( + spec.get_total_active_balance(state) // spec.EFFECTIVE_BALANCE_INCREMENT + ) total_base_rewards = spec.get_base_reward_per_increment(state) * total_active_increments - max_participant_rewards = (total_base_rewards * spec.SYNC_REWARD_WEIGHT - // spec.WEIGHT_DENOMINATOR // spec.SLOTS_PER_EPOCH) + max_participant_rewards = ( + total_base_rewards + * spec.SYNC_REWARD_WEIGHT + // spec.WEIGHT_DENOMINATOR + // spec.SLOTS_PER_EPOCH + ) return max_participant_rewards // spec.SYNC_COMMITTEE_SIZE def compute_sync_committee_participant_reward_and_penalty( - spec, state, participant_index, committee_indices, committee_bits): + spec, state, participant_index, committee_indices, committee_bits +): inclusion_reward = compute_sync_committee_inclusion_reward(spec, state) included_indices = [index for index, bit in zip(committee_indices, committee_bits) if bit] - not_included_indices = [index for index, bit in zip(committee_indices, committee_bits) if not bit] + not_included_indices = [ + index for index, bit in zip(committee_indices, committee_bits) if not bit + ] included_multiplicities = Counter(included_indices) not_included_multiplicities = Counter(not_included_indices) return ( spec.Gwei(inclusion_reward * included_multiplicities[participant_index]), - spec.Gwei(inclusion_reward * not_included_multiplicities[participant_index]) + spec.Gwei(inclusion_reward * not_included_multiplicities[participant_index]), ) @@ -74,7 +85,7 @@ def compute_sync_committee_proposer_reward(spec, state, committee_indices, commi return spec.Gwei(participant_reward * participant_number) -def compute_committee_indices(spec, state, committee=None): +def compute_committee_indices(state, committee=None): """ Given a ``committee``, calculate and return the related indices """ @@ -84,7 +95,9 @@ def compute_committee_indices(spec, state, committee=None): return [all_pubkeys.index(pubkey) for pubkey in committee.pubkeys] -def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indices, committee_bits, proposer_index): +def validate_sync_committee_rewards( + spec, pre_state, post_state, committee_indices, committee_bits, proposer_index +): for index in range(len(post_state.validators)): reward = 0 penalty = 0 @@ -107,45 +120,42 @@ def validate_sync_committee_rewards(spec, pre_state, post_state, committee_indic committee_bits, ) - assert post_state.balances[index] == pre_state.balances[index] + reward - penalty + balance = pre_state.balances[index] + reward + assert post_state.balances[index] == (0 if balance < penalty else balance - penalty) -def run_sync_committee_processing(spec, state, block, expect_exception=False): +def run_sync_committee_processing( + spec, state, block, expect_exception=False, skip_reward_validation=False +): """ Processes everything up to the sync committee work, then runs the sync committee work in isolation, and produces a pre-state and post-state (None if exception) specifically for sync-committee processing changes. """ pre_state = state.copy() # process up to the sync committee work - call = run_block_processing_to(spec, state, block, 'process_sync_aggregate') - yield 'pre', state - yield 'sync_aggregate', block.body.sync_aggregate + call = run_block_processing_to(spec, state, block, "process_sync_aggregate") + yield "pre", state + yield "sync_aggregate", block.body.sync_aggregate if expect_exception: expect_assertion_error(lambda: call(state, block)) - yield 'post', None + yield "post", None else: call(state, block) - yield 'post', state + yield "post", state if expect_exception: assert pre_state.balances == state.balances else: - committee_indices = compute_committee_indices( - spec, - state, - state.current_sync_committee, - ) + committee_indices = compute_committee_indices(state, state.current_sync_committee) committee_bits = block.body.sync_aggregate.sync_committee_bits - validate_sync_committee_rewards( - spec, - pre_state, - state, - committee_indices, - committee_bits, - block.proposer_index - ) + if not skip_reward_validation: + validate_sync_committee_rewards( + spec, pre_state, state, committee_indices, committee_bits, block.proposer_index + ) -def _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits): +def _build_block_for_next_slot_with_sync_participation( + spec, state, committee_indices, committee_bits +): block = build_empty_block_for_next_slot(spec, state) block.body.sync_aggregate = spec.SyncAggregate( sync_committee_bits=committee_bits, @@ -155,11 +165,17 @@ def _build_block_for_next_slot_with_sync_participation(spec, state, committee_in block.slot - 1, [index for index, bit in zip(committee_indices, committee_bits) if bit], block_root=block.parent_root, - ) + ), ) return block -def run_successful_sync_committee_test(spec, state, committee_indices, committee_bits): - block = _build_block_for_next_slot_with_sync_participation(spec, state, committee_indices, committee_bits) - yield from run_sync_committee_processing(spec, state, block) +def run_successful_sync_committee_test( + spec, state, committee_indices, committee_bits, skip_reward_validation=False +): + block = _build_block_for_next_slot_with_sync_participation( + spec, state, committee_indices, committee_bits + ) + yield from run_sync_committee_processing( + spec, state, block, skip_reward_validation=skip_reward_validation + ) diff --git a/tests/core/pyspec/eth2spec/test/helpers/typing.py b/tests/core/pyspec/eth2spec/test/helpers/typing.py index 19657a8f72..321431ed7b 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/typing.py +++ b/tests/core/pyspec/eth2spec/test/helpers/typing.py @@ -1,4 +1,18 @@ -from typing import NewType +from collections.abc import Sequence +from typing import ( + NewType, + Protocol, +) SpecForkName = NewType("SpecForkName", str) PresetBaseName = NewType("PresetBaseName", str) +SpecForks = Sequence[SpecForkName] + + +class Configuration(Protocol): + PRESET_BASE: str + + +class Spec(Protocol): + fork: str + config: Configuration diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 55ea0b5b0f..aa6a31885c 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -1,28 +1,42 @@ from random import Random -from eth2spec.utils import bls -from eth2spec.test.helpers.keys import privkeys +from eth2spec.test.context import expect_assertion_error +from eth2spec.test.helpers.forks import is_post_deneb +from eth2spec.test.helpers.keys import privkeys +from eth2spec.utils import bls -def prepare_signed_exits(spec, state, indices): - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) +def prepare_signed_exits(spec, state, indices, fork_version=None): def create_signed_exit(index): - exit = spec.VoluntaryExit( + voluntary_exit = spec.VoluntaryExit( epoch=spec.get_current_epoch(state), validator_index=index, ) - signing_root = spec.compute_signing_root(exit, domain) - return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) + return sign_voluntary_exit( + spec, state, voluntary_exit, privkeys[index], fork_version=fork_version + ) return [create_signed_exit(index) for index in indices] -def sign_voluntary_exit(spec, state, voluntary_exit, privkey): - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) +def sign_voluntary_exit(spec, state, voluntary_exit, privkey, fork_version=None): + if fork_version is None: + if is_post_deneb(spec): + domain = spec.compute_domain( + spec.DOMAIN_VOLUNTARY_EXIT, + spec.config.CAPELLA_FORK_VERSION, + state.genesis_validators_root, + ) + else: + domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) + else: + domain = spec.compute_domain( + spec.DOMAIN_VOLUNTARY_EXIT, fork_version, state.genesis_validators_root + ) + signing_root = spec.compute_signing_root(voluntary_exit, domain) return spec.SignedVoluntaryExit( - message=voluntary_exit, - signature=bls.Sign(privkey, signing_root) + message=voluntary_exit, signature=bls.Sign(privkey, signing_root) ) @@ -31,13 +45,16 @@ def sign_voluntary_exit(spec, state, voluntary_exit, privkey): # def get_exited_validators(spec, state): current_epoch = spec.get_current_epoch(state) - return [index for (index, validator) in enumerate(state.validators) if validator.exit_epoch <= current_epoch] + return [ + index + for (index, validator) in enumerate(state.validators) + if validator.exit_epoch <= current_epoch + ] def get_unslashed_exited_validators(spec, state): return [ - index for index in get_exited_validators(spec, state) - if not state.validators[index].slashed + index for index in get_exited_validators(spec, state) if not state.validators[index].slashed ] @@ -49,3 +66,36 @@ def exit_validators(spec, state, validator_count, rng=None): for index in indices: spec.initiate_validator_exit(state, index) return indices + + +# +# Run processing +# + + +def run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=True): + """ + Run ``process_voluntary_exit``, yielding: + - pre-state ('pre') + - voluntary_exit ('voluntary_exit') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + validator_index = signed_voluntary_exit.message.validator_index + + yield "pre", state + yield "voluntary_exit", signed_voluntary_exit + + if not valid: + expect_assertion_error(lambda: spec.process_voluntary_exit(state, signed_voluntary_exit)) + yield "post", None + return + + pre_exit_epoch = state.validators[validator_index].exit_epoch + + spec.process_voluntary_exit(state, signed_voluntary_exit) + + yield "post", state + + assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH + assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH diff --git a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py new file mode 100644 index 0000000000..4009e95f6b --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py @@ -0,0 +1,321 @@ +from eth2spec.test.helpers.forks import is_post_eip7732, is_post_electra + + +def get_expected_withdrawals(spec, state): + if is_post_electra(spec): + withdrawals, _ = spec.get_expected_withdrawals(state) + return withdrawals + else: + return spec.get_expected_withdrawals(state) + + +def set_validator_fully_withdrawable(spec, state, index, withdrawable_epoch=None): + if withdrawable_epoch is None: + withdrawable_epoch = spec.get_current_epoch(state) + + validator = state.validators[index] + validator.withdrawable_epoch = withdrawable_epoch + # set exit epoch as well to avoid interactions with other epoch process, e.g. forced ejections + validator.exit_epoch = min(validator.exit_epoch, withdrawable_epoch) + + if validator.withdrawal_credentials[0:1] == spec.BLS_WITHDRAWAL_PREFIX: + validator.withdrawal_credentials = ( + spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + validator.withdrawal_credentials[1:] + ) + + if state.balances[index] == 0: + state.balances[index] = 10000000000 + + assert spec.is_fully_withdrawable_validator( + validator, state.balances[index], withdrawable_epoch + ) + + +def set_eth1_withdrawal_credential_with_balance( + spec, state, index, effective_balance=None, balance=None, address=None +): + if balance is None and effective_balance is None: + balance = spec.MAX_EFFECTIVE_BALANCE + effective_balance = spec.MAX_EFFECTIVE_BALANCE + elif balance is None: + balance = effective_balance + elif effective_balance is None: + effective_balance = min( + balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE + ) + + if address is None: + address = b"\x11" * 20 + + validator = state.validators[index] + validator.withdrawal_credentials = spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX + b"\x00" * 11 + address + validator.effective_balance = effective_balance + state.balances[index] = balance + + +def set_validator_partially_withdrawable(spec, state, index, excess_balance=1000000000): + validator = state.validators[index] + if is_post_electra(spec) and spec.has_compounding_withdrawal_credential(validator): + validator.effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + state.balances[index] = validator.effective_balance + excess_balance + else: + set_eth1_withdrawal_credential_with_balance( + spec, + state, + index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE, + balance=spec.MAX_EFFECTIVE_BALANCE + excess_balance, + ) + + assert spec.is_partially_withdrawable_validator(state.validators[index], state.balances[index]) + + +def sample_withdrawal_indices(spec, state, rng, num_full_withdrawals, num_partial_withdrawals): + bound = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) + assert num_full_withdrawals + num_partial_withdrawals <= bound + eligible_validator_indices = list(range(bound)) + sampled_indices = rng.sample( + eligible_validator_indices, num_full_withdrawals + num_partial_withdrawals + ) + fully_withdrawable_indices = rng.sample(sampled_indices, num_full_withdrawals) + partial_withdrawals_indices = list( + set(sampled_indices).difference(set(fully_withdrawable_indices)) + ) + + return fully_withdrawable_indices, partial_withdrawals_indices + + +def prepare_expected_withdrawals( + spec, + state, + rng, + num_full_withdrawals=0, + num_partial_withdrawals=0, + num_full_withdrawals_comp=0, + num_partial_withdrawals_comp=0, +): + fully_withdrawable_indices, partial_withdrawals_indices = sample_withdrawal_indices( + spec, + state, + rng, + num_full_withdrawals + num_full_withdrawals_comp, + num_partial_withdrawals + num_partial_withdrawals_comp, + ) + + fully_withdrawable_indices_comp = rng.sample( + fully_withdrawable_indices, num_full_withdrawals_comp + ) + partial_withdrawals_indices_comp = rng.sample( + partial_withdrawals_indices, num_partial_withdrawals_comp + ) + + for index in fully_withdrawable_indices_comp + partial_withdrawals_indices_comp: + address = state.validators[index].withdrawal_credentials[12:] + set_compounding_withdrawal_credential_with_balance(spec, state, index, address=address) + + for index in fully_withdrawable_indices: + set_validator_fully_withdrawable(spec, state, index) + for index in partial_withdrawals_indices: + set_validator_partially_withdrawable(spec, state, index) + + return fully_withdrawable_indices, partial_withdrawals_indices + + +def set_compounding_withdrawal_credential(spec, state, index, address=None): + if address is None: + address = b"\x11" * 20 + + validator = state.validators[index] + validator.withdrawal_credentials = spec.COMPOUNDING_WITHDRAWAL_PREFIX + b"\x00" * 11 + address + + +def set_compounding_withdrawal_credential_with_balance( + spec, state, index, effective_balance=None, balance=None, address=None +): + set_compounding_withdrawal_credential(spec, state, index, address) + + if balance is None and effective_balance is None: + balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + elif balance is None: + balance = effective_balance + elif effective_balance is None: + effective_balance = min( + balance - balance % spec.EFFECTIVE_BALANCE_INCREMENT, spec.MAX_EFFECTIVE_BALANCE_ELECTRA + ) + + state.validators[index].effective_balance = effective_balance + state.balances[index] = balance + + +def prepare_pending_withdrawal( + spec, + state, + validator_index, + effective_balance=32_000_000_000, + amount=1_000_000_000, + withdrawable_epoch=None, +): + assert is_post_electra(spec) + + if withdrawable_epoch is None: + withdrawable_epoch = spec.get_current_epoch(state) + + balance = effective_balance + amount + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index, effective_balance, balance + ) + + withdrawal = spec.PendingPartialWithdrawal( + validator_index=validator_index, + amount=amount, + withdrawable_epoch=withdrawable_epoch, + ) + state.pending_partial_withdrawals.append(withdrawal) + + return withdrawal + + +def prepare_withdrawal_request(spec, state, validator_index, address=None, amount=None): + validator = state.validators[validator_index] + if not spec.has_execution_withdrawal_credential(validator): + set_eth1_withdrawal_credential_with_balance(spec, state, validator_index, address=address) + + if amount is None: + amount = spec.FULL_EXIT_REQUEST_AMOUNT + + return spec.WithdrawalRequest( + source_address=state.validators[validator_index].withdrawal_credentials[12:], + validator_pubkey=state.validators[validator_index].pubkey, + amount=amount, + ) + + +# +# Run processing +# + + +def verify_post_state( + state, spec, expected_withdrawals, fully_withdrawable_indices, partial_withdrawals_indices +): + # Consider verifying also the condition when no withdrawals are expected. + if len(expected_withdrawals) == 0: + return + + expected_withdrawals_validator_indices = [ + withdrawal.validator_index for withdrawal in expected_withdrawals + ] + assert state.next_withdrawal_index == expected_withdrawals[-1].index + 1 + + if len(expected_withdrawals) == spec.MAX_WITHDRAWALS_PER_PAYLOAD: + # NOTE: ideally we would also check in the case with + # fewer than maximum withdrawals but that requires the pre-state info + next_withdrawal_validator_index = (expected_withdrawals_validator_indices[-1] + 1) % len( + state.validators + ) + assert state.next_withdrawal_validator_index == next_withdrawal_validator_index + + for index in fully_withdrawable_indices: + if index in expected_withdrawals_validator_indices: + assert state.balances[index] == 0 + else: + assert state.balances[index] > 0 + for index in partial_withdrawals_indices: + if is_post_electra(spec): + max_effective_balance = spec.get_max_effective_balance(state.validators[index]) + else: + max_effective_balance = spec.MAX_EFFECTIVE_BALANCE + + if index in expected_withdrawals_validator_indices: + assert state.balances[index] == max_effective_balance + else: + assert state.balances[index] > max_effective_balance + + +def run_withdrawals_processing( + spec, + state, + execution_payload, + num_expected_withdrawals=None, + fully_withdrawable_indices=None, + partial_withdrawals_indices=None, + pending_withdrawal_requests=None, + valid=True, +): + """ + Run ``process_withdrawals``, yielding: + - pre-state ('pre') + - execution payload ('execution_payload') + - post-state ('post'). + If ``valid == False``, run expecting ``AssertionError`` + """ + expected_withdrawals = get_expected_withdrawals(spec, state) + assert len(expected_withdrawals) <= spec.MAX_WITHDRAWALS_PER_PAYLOAD + if num_expected_withdrawals is not None: + assert len(expected_withdrawals) == num_expected_withdrawals + + pre_state = state.copy() + yield "pre", state + yield "execution_payload", execution_payload + + if not valid: + try: + if is_post_eip7732(spec): + spec.process_withdrawals(state) + else: + spec.process_withdrawals(state, execution_payload) + raise AssertionError("expected an assertion error, but got none.") + except AssertionError: + pass + + yield "post", None + return + + if is_post_eip7732(spec): + spec.process_withdrawals(state) + else: + spec.process_withdrawals(state, execution_payload) + + yield "post", state + + # Check withdrawal indices + assert state.next_withdrawal_index == pre_state.next_withdrawal_index + len( + expected_withdrawals + ) + for index, withdrawal in enumerate(execution_payload.withdrawals): + assert withdrawal.index == pre_state.next_withdrawal_index + index + + if len(expected_withdrawals) == 0: + next_withdrawal_validator_index = ( + pre_state.next_withdrawal_validator_index + spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP + ) + assert state.next_withdrawal_validator_index == next_withdrawal_validator_index % len( + state.validators + ) + elif len(expected_withdrawals) <= spec.MAX_WITHDRAWALS_PER_PAYLOAD: + bound = min(spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP, spec.MAX_WITHDRAWALS_PER_PAYLOAD) + assert len(get_expected_withdrawals(spec, state)) <= bound + elif len(expected_withdrawals) > spec.MAX_WITHDRAWALS_PER_PAYLOAD: + raise ValueError( + "len(expected_withdrawals) should not be greater than MAX_WITHDRAWALS_PER_PAYLOAD" + ) + + if fully_withdrawable_indices is not None or partial_withdrawals_indices is not None: + verify_post_state( + state, + spec, + expected_withdrawals, + fully_withdrawable_indices, + partial_withdrawals_indices, + ) + + # Check withdrawal requests + if pending_withdrawal_requests is not None: + 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.validator_index + assert withdrawal.amount == request.amount + + return expected_withdrawals diff --git a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py b/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py deleted file mode 100644 index 4c68034d4a..0000000000 --- a/tests/core/pyspec/eth2spec/test/merge/block_processing/test_process_execution_payload.py +++ /dev/null @@ -1,201 +0,0 @@ -from eth2spec.test.helpers.execution_payload import ( - build_empty_execution_payload, - get_execution_payload_header, - build_state_with_incomplete_transition, - build_state_with_complete_transition, -) -from eth2spec.test.context import spec_state_test, expect_assertion_error, with_merge_and_later -from eth2spec.test.helpers.state import next_slot - - -def run_execution_payload_processing(spec, state, execution_payload, valid=True, execution_valid=True): - """ - Run ``process_execution_payload``, yielding: - - pre-state ('pre') - - execution payload ('execution_payload') - - execution details, to mock EVM execution ('execution.yml', a dict with 'execution_valid' key and boolean value) - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - - yield 'pre', state - yield 'execution', {'execution_valid': execution_valid} - yield 'execution_payload', execution_payload - - called_new_block = False - - class TestEngine(spec.NoopExecutionEngine): - def on_payload(self, payload) -> bool: - nonlocal called_new_block, execution_valid - called_new_block = True - assert payload == execution_payload - return execution_valid - - if not valid: - expect_assertion_error(lambda: spec.process_execution_payload(state, execution_payload, TestEngine())) - yield 'post', None - return - - spec.process_execution_payload(state, execution_payload, TestEngine()) - - # Make sure we called the engine - assert called_new_block - - yield 'post', state - - assert state.latest_execution_payload_header == get_execution_payload_header(spec, execution_payload) - - -@with_merge_and_later -@spec_state_test -def test_success_first_payload(spec, state): - # pre-state - state = build_state_with_incomplete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload) - - -@with_merge_and_later -@spec_state_test -def test_success_regular_payload(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload) - - -@with_merge_and_later -@spec_state_test -def test_success_first_payload_with_gap_slot(spec, state): - # pre-state - state = build_state_with_incomplete_transition(spec, state) - next_slot(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload) - - -@with_merge_and_later -@spec_state_test -def test_success_regular_payload_with_gap_slot(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload) - - -@with_merge_and_later -@spec_state_test -def test_bad_execution_first_payload(spec, state): - # completely valid payload, but execution itself fails (e.g. block exceeds gas limit) - - # pre-state - state = build_state_with_incomplete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_execution_regular_payload(spec, state): - # completely valid payload, but execution itself fails (e.g. block exceeds gas limit) - - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False, execution_valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_parent_hash_regular_payload(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - execution_payload.parent_hash = spec.Hash32() - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_number_regular_payload(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - execution_payload.block_number = execution_payload.block_number + 1 - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_everything_regular_payload(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - execution_payload.parent_hash = spec.Hash32() - execution_payload.block_number = execution_payload.block_number + 1 - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_timestamp_first_payload(spec, state): - # pre-state - state = build_state_with_incomplete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - execution_payload.timestamp = execution_payload.timestamp + 1 - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) - - -@with_merge_and_later -@spec_state_test -def test_bad_timestamp_regular_payload(spec, state): - # pre-state - state = build_state_with_complete_transition(spec, state) - next_slot(spec, state) - - # execution payload - execution_payload = build_empty_execution_payload(spec, state) - execution_payload.timestamp = execution_payload.timestamp + 1 - - yield from run_execution_payload_processing(spec, state, execution_payload, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py b/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py deleted file mode 100644 index d92b0015c9..0000000000 --- a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_basic.py +++ /dev/null @@ -1,82 +0,0 @@ -from eth2spec.test.context import ( - with_phases, - with_custom_state, - with_presets, - spec_test, with_state, - low_balances, misc_balances, large_validator_set, -) -from eth2spec.test.utils import with_meta_tags -from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, - MINIMAL, -) -from eth2spec.test.helpers.state import ( - next_epoch, - next_epoch_via_block, -) -from eth2spec.test.helpers.merge.fork import ( - MERGE_FORK_TEST_META_TAGS, - run_fork_test, -) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_base_state(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_next_epoch(spec, phases, state): - next_epoch(spec, state) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_next_epoch_with_block(spec, phases, state): - next_epoch_via_block(spec, state) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_many_next_epoch(spec, phases, state): - for _ in range(3): - next_epoch(spec, state) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_random_low_balances(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_random_misc_balances(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@spec_test -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_fork_random_large_validator_set(spec, phases, state): - yield from run_fork_test(phases[MERGE], state) diff --git a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py b/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py deleted file mode 100644 index 20101fac47..0000000000 --- a/tests/core/pyspec/eth2spec/test/merge/fork/test_merge_fork_random.py +++ /dev/null @@ -1,84 +0,0 @@ -from random import Random - -from eth2spec.test.context import ( - with_phases, - with_custom_state, - with_presets, - spec_test, with_state, - low_balances, misc_balances, large_validator_set, -) -from eth2spec.test.utils import with_meta_tags -from eth2spec.test.helpers.constants import ( - ALTAIR, MERGE, - MINIMAL, -) -from eth2spec.test.helpers.merge.fork import ( - MERGE_FORK_TEST_META_TAGS, - run_fork_test, -) -from eth2spec.test.helpers.random import randomize_state - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_0(spec, phases, state): - randomize_state(spec, state, rng=Random(1010)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_1(spec, phases, state): - randomize_state(spec, state, rng=Random(2020)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_2(spec, phases, state): - randomize_state(spec, state, rng=Random(3030)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_state -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_3(spec, phases, state): - randomize_state(spec, state, rng=Random(4040)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_low_balances(spec, phases, state): - randomize_state(spec, state, rng=Random(5050)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_misc_balances(spec, phases, state): - randomize_state(spec, state, rng=Random(6060)) - yield from run_fork_test(phases[MERGE], state) - - -@with_phases(phases=[ALTAIR], other_phases=[MERGE]) -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") -@spec_test -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) -@with_meta_tags(MERGE_FORK_TEST_META_TAGS) -def test_merge_fork_random_large_validator_set(spec, phases, state): - randomize_state(spec, state, rng=Random(7070)) - yield from run_fork_test(phases[MERGE], state) diff --git a/tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py deleted file mode 100644 index 4a6db41068..0000000000 --- a/tests/core/pyspec/eth2spec/test/merge/sanity/test_blocks.py +++ /dev/null @@ -1,25 +0,0 @@ -from eth2spec.test.helpers.state import ( - state_transition_and_sign_block -) -from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot -) -from eth2spec.test.context import ( - with_merge_and_later, spec_state_test -) - - -@with_merge_and_later -@spec_state_test -def test_empty_block_transition(spec, state): - yield 'pre', state - - block = build_empty_block_for_next_slot(spec, state) - assert len(block.body.execution_payload.transactions) == 0 - - signed_block = state_transition_and_sign_block(spec, state, block) - - yield 'blocks', [signed_block] - yield 'post', state - -# TODO: tests with EVM, mock or replacement? diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py index c303200667..3546d18328 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py @@ -1,21 +1,23 @@ from eth2spec.test.context import ( + always_bls, + low_balances, + never_bls, + single_phase, spec_state_test, - always_bls, never_bls, - with_all_phases, spec_test, - low_balances, + with_all_phases, with_custom_state, - single_phase, ) from eth2spec.test.helpers.attestations import ( - run_attestation_processing, + compute_max_inclusion_slot, get_valid_attestation, + run_attestation_processing, sign_aggregate_attestation, sign_attestation, ) from eth2spec.test.helpers.state import ( - next_slots, next_epoch_via_block, + next_slots, transition_to_slot_via_block, ) from eth2spec.utils.ssz.ssz_typing import Bitlist @@ -23,7 +25,7 @@ @with_all_phases @spec_state_test -def test_success(spec, state): +def test_one_basic_attestation(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -34,7 +36,7 @@ def test_success(spec, state): @spec_test @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @single_phase -def test_success_multi_proposer_index_iterations(spec, state): +def test_multi_proposer_index_iterations(spec, state): next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2) attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -44,7 +46,7 @@ def test_success_multi_proposer_index_iterations(spec, state): @with_all_phases @spec_state_test -def test_success_previous_epoch(spec, state): +def test_previous_epoch(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_epoch_via_block(spec, state) @@ -58,55 +60,70 @@ def test_invalid_attestation_signature(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test @always_bls -def test_empty_participants_zeroes_sig(spec, state): - attestation = get_valid_attestation(spec, state, filter_participant_set=lambda comm: []) # 0 participants - attestation.signature = spec.BLSSignature(b'\x00' * 96) +def test_invalid_empty_participants_zeroes_sig(spec, state): + attestation = get_valid_attestation( + spec, state, filter_participant_set=lambda comm: [] + ) # 0 participants + attestation.signature = spec.BLSSignature(b"\x00" * 96) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test @always_bls -def test_empty_participants_seemingly_valid_sig(spec, state): - attestation = get_valid_attestation(spec, state, filter_participant_set=lambda comm: []) # 0 participants +def test_invalid_empty_participants_seemingly_valid_sig(spec, state): + attestation = get_valid_attestation( + spec, state, filter_participant_set=lambda comm: [] + ) # 0 participants # Special BLS value, valid for zero pubkeys on some (but not all) BLS implementations. - attestation.signature = spec.BLSSignature(b'\xc0' + b'\x00' * 95) + attestation.signature = spec.BLSSignature(b"\xc0" + b"\x00" * 95) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_before_inclusion_delay(spec, state): +def test_invalid_before_inclusion_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=True) # do not increment slot to allow for inclusion delay - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_all_phases +@spec_state_test +def test_at_max_inclusion_slot(spec, state): + attestation = get_valid_attestation(spec, state, signed=True) + + # increment past latest inclusion slot + transition_to_slot_via_block(spec, state, compute_max_inclusion_slot(spec, attestation)) + + yield from run_attestation_processing(spec, state, attestation) @with_all_phases @spec_state_test -def test_after_epoch_slots(spec, state): +def test_invalid_after_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=True) # increment past latest inclusion slot - transition_to_slot_via_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH + 1) + transition_to_slot_via_block(spec, state, compute_max_inclusion_slot(spec, attestation) + 1) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_old_source_epoch(spec, state): +def test_invalid_old_source_epoch(spec, state): next_slots(spec, state, spec.SLOTS_PER_EPOCH * 5) state.finalized_checkpoint.epoch = 2 state.previous_justified_checkpoint.epoch = 3 @@ -121,34 +138,37 @@ def test_old_source_epoch(spec, state): sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test @always_bls -def test_wrong_index_for_committee_signature(spec, state): +def test_invalid_wrong_index_for_committee_signature(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) attestation.data.index += 1 - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) def reduce_state_committee_count_from_max(spec, state): """ Modified ``state`` to ensure that it has fewer committees at each slot than ``MAX_COMMITTEES_PER_SLOT`` """ - while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT: - state.validators = state.validators[:len(state.validators) // 2] - state.balances = state.balances[:len(state.balances) // 2] + while ( + spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) + >= spec.MAX_COMMITTEES_PER_SLOT + ): + state.validators = state.validators[: len(state.validators) // 2] + state.balances = state.balances[: len(state.balances) // 2] @with_all_phases @spec_state_test @never_bls -def test_wrong_index_for_slot_0(spec, state): +def test_invalid_wrong_index_for_slot_0(spec, state): reduce_state_committee_count_from_max(spec, state) attestation = get_valid_attestation(spec, state) @@ -157,13 +177,13 @@ def test_wrong_index_for_slot_0(spec, state): # Invalid index: current committees per slot is less than the max attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT - 1 - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test @never_bls -def test_wrong_index_for_slot_1(spec, state): +def test_invalid_wrong_index_for_slot_1(spec, state): reduce_state_committee_count_from_max(spec, state) current_epoch = spec.get_current_epoch(state) @@ -175,7 +195,7 @@ def test_wrong_index_for_slot_1(spec, state): # Invalid index: off by one attestation.data.index = committee_count - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @@ -188,12 +208,12 @@ def test_invalid_index(spec, state): # Invalid index: off by one (with respect to valid range) on purpose attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_mismatched_target_and_slot(spec, state): +def test_invalid_mismatched_target_and_slot(spec, state): next_epoch_via_block(spec, state) next_epoch_via_block(spec, state) @@ -202,46 +222,44 @@ def test_mismatched_target_and_slot(spec, state): sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_old_target_epoch(spec, state): +def test_invalid_old_target_epoch(spec, state): assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2 attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.SLOTS_PER_EPOCH * 2) # target epoch will be too old to handle - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_future_target_epoch(spec, state): +def test_invalid_future_target_epoch(spec, state): assert spec.MIN_ATTESTATION_INCLUSION_DELAY < spec.SLOTS_PER_EPOCH * 2 attestation = get_valid_attestation(spec, state) - participants = spec.get_attesting_indices( - state, - attestation.data, - attestation.aggregation_bits - ) - attestation.data.target.epoch = spec.get_current_epoch(state) + 1 # target epoch will be too new to handle + participants = spec.get_attesting_indices(state, attestation) + attestation.data.target.epoch = ( + spec.get_current_epoch(state) + 1 + ) # target epoch will be too new to handle # manually add signature for correct participants attestation.signature = sign_aggregate_attestation(spec, state, attestation.data, participants) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_new_source_epoch(spec, state): +def test_invalid_new_source_epoch(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -249,12 +267,12 @@ def test_new_source_epoch(spec, state): sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_source_root_is_target_root(spec, state): +def test_invalid_source_root_is_target_root(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -262,7 +280,7 @@ def test_source_root_is_target_root(spec, state): sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @@ -272,13 +290,41 @@ def test_invalid_current_source_root(spec, state): state.finalized_checkpoint.epoch = 2 - state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b'\x01' * 32) - state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b'\x32' * 32) + state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b"\x01" * 32) + state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b"\x32" * 32) - attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 3) + 1) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + attestation = get_valid_attestation(spec, state, slot=spec.SLOTS_PER_EPOCH * 5) + + # Test logic sanity checks: + assert attestation.data.target.epoch == spec.get_current_epoch(state) + assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root + assert attestation.data.source.root == state.current_justified_checkpoint.root + + # Make attestation source root invalid: should be current justified, not previous one + attestation.data.source.root = state.previous_justified_checkpoint.root + + sign_attestation(spec, state, attestation) + + yield from run_attestation_processing(spec, state, attestation, valid=False) + + +@with_all_phases +@spec_state_test +def test_invalid_previous_source_root(spec, state): + next_slots(spec, state, spec.SLOTS_PER_EPOCH * 5) + + state.finalized_checkpoint.epoch = 2 + + state.previous_justified_checkpoint = spec.Checkpoint(epoch=3, root=b"\x01" * 32) + state.current_justified_checkpoint = spec.Checkpoint(epoch=4, root=b"\x32" * 32) + + attestation = get_valid_attestation(spec, state, slot=(spec.SLOTS_PER_EPOCH * 4) + 1) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) # Test logic sanity checks: + assert attestation.data.target.epoch == spec.get_previous_epoch(state) assert state.current_justified_checkpoint.root != state.previous_justified_checkpoint.root assert attestation.data.source.root == state.previous_justified_checkpoint.root @@ -287,58 +333,60 @@ def test_invalid_current_source_root(spec, state): sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_bad_source_root(spec, state): +def test_invalid_bad_source_root(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - attestation.data.source.root = b'\x42' * 32 + attestation.data.source.root = b"\x42" * 32 sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_too_many_aggregation_bits(spec, state): +def test_invalid_too_many_aggregation_bits(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) # one too many bits attestation.aggregation_bits.append(0b0) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) @with_all_phases @spec_state_test -def test_too_few_aggregation_bits(spec, state): +def test_invalid_too_few_aggregation_bits(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) attestation.aggregation_bits = Bitlist[spec.MAX_VALIDATORS_PER_COMMITTEE]( - *([0b1] + [0b0] * (len(attestation.aggregation_bits) - 1))) + *([0b1] + [0b0] * (len(attestation.aggregation_bits) - 1)) + ) sign_attestation(spec, state, attestation) # one too few bits attestation.aggregation_bits = attestation.aggregation_bits[:-1] - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) # -# Full correct atttestation contents at different slot inclusions +# Full correct attestation contents at different slot inclusions # + @with_all_phases @spec_state_test -def test_correct_min_inclusion_delay(spec, state): +def test_correct_attestation_included_at_min_inclusion_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) @@ -347,7 +395,7 @@ def test_correct_min_inclusion_delay(spec, state): @with_all_phases @spec_state_test -def test_correct_sqrt_epoch_delay(spec, state): +def test_correct_attestation_included_at_sqrt_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) @@ -356,7 +404,7 @@ def test_correct_sqrt_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_correct_epoch_delay(spec, state): +def test_correct_attestation_included_at_one_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=True) next_slots(spec, state, spec.SLOTS_PER_EPOCH) @@ -365,26 +413,36 @@ def test_correct_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_correct_after_epoch_delay(spec, state): +def test_correct_attestation_included_at_max_inclusion_slot(spec, state): + attestation = get_valid_attestation(spec, state, signed=True) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation)) + + yield from run_attestation_processing(spec, state, attestation) + + +@with_all_phases +@spec_state_test +def test_invalid_correct_attestation_included_after_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=True) # increment past latest inclusion slot - next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation) + 1) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) # # Incorrect head but correct source/target at different slot inclusions # + @with_all_phases @spec_state_test -def test_incorrect_head_min_inclusion_delay(spec, state): +def test_incorrect_head_included_at_min_inclusion_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -392,11 +450,11 @@ def test_incorrect_head_min_inclusion_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_sqrt_epoch_delay(spec, state): +def test_incorrect_head_included_at_sqrt_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) - attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -404,11 +462,11 @@ def test_incorrect_head_sqrt_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_epoch_delay(spec, state): +def test_incorrect_head_included_at_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=False) - next_slots(spec, state, spec.SLOTS_PER_EPOCH) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation)) - attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -416,30 +474,31 @@ def test_incorrect_head_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_after_epoch_delay(spec, state): +def test_invalid_incorrect_head_included_after_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=False) # increment past latest inclusion slot - next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation) + 1) - attestation.data.beacon_block_root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) # # Incorrect head and target but correct source at different slot inclusions # + @with_all_phases @spec_state_test def test_incorrect_head_and_target_min_inclusion_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - attestation.data.beacon_block_root = b'\x42' * 32 - attestation.data.target.root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -447,12 +506,12 @@ def test_incorrect_head_and_target_min_inclusion_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_and_target_sqrt_epoch_delay(spec, state): +def test_incorrect_head_and_target_included_at_sqrt_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) - attestation.data.beacon_block_root = b'\x42' * 32 - attestation.data.target.root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -460,12 +519,12 @@ def test_incorrect_head_and_target_sqrt_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_and_target_epoch_delay(spec, state): +def test_incorrect_head_and_target_included_at_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.SLOTS_PER_EPOCH) - attestation.data.beacon_block_root = b'\x42' * 32 - attestation.data.target.root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -473,29 +532,30 @@ def test_incorrect_head_and_target_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_head_and_target_after_epoch_delay(spec, state): +def test_invalid_incorrect_head_and_target_included_after_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=False) # increment past latest inclusion slot - next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation) + 1) - attestation.data.beacon_block_root = b'\x42' * 32 - attestation.data.target.root = b'\x42' * 32 + attestation.data.beacon_block_root = b"\x42" * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) # # Correct head and source but incorrect target at different slot inclusions # + @with_all_phases @spec_state_test -def test_incorrect_target_min_inclusion_delay(spec, state): +def test_incorrect_target_included_at_min_inclusion_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - attestation.data.target.root = b'\x42' * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -503,11 +563,11 @@ def test_incorrect_target_min_inclusion_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_target_sqrt_epoch_delay(spec, state): +def test_incorrect_target_included_at_sqrt_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.integer_squareroot(spec.SLOTS_PER_EPOCH)) - attestation.data.target.root = b'\x42' * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -515,11 +575,11 @@ def test_incorrect_target_sqrt_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_target_epoch_delay(spec, state): +def test_incorrect_target_included_at_epoch_delay(spec, state): attestation = get_valid_attestation(spec, state, signed=False) next_slots(spec, state, spec.SLOTS_PER_EPOCH) - attestation.data.target.root = b'\x42' * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) yield from run_attestation_processing(spec, state, attestation) @@ -527,12 +587,12 @@ def test_incorrect_target_epoch_delay(spec, state): @with_all_phases @spec_state_test -def test_incorrect_target_after_epoch_delay(spec, state): +def test_invalid_incorrect_target_included_after_max_inclusion_slot(spec, state): attestation = get_valid_attestation(spec, state, signed=False) # increment past latest inclusion slot - next_slots(spec, state, spec.SLOTS_PER_EPOCH + 1) + next_slots(spec, state, compute_max_inclusion_slot(spec, attestation) + 1) - attestation.data.target.root = b'\x42' * 32 + attestation.data.target.root = b"\x42" * 32 sign_attestation(spec, state, attestation) - yield from run_attestation_processing(spec, state, attestation, False) + yield from run_attestation_processing(spec, state, attestation, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py index 13d64e03b0..70799da218 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attester_slashing.py @@ -1,16 +1,28 @@ from random import Random from eth2spec.test.context import ( - spec_state_test, expect_assertion_error, always_bls, with_all_phases, - with_custom_state, spec_test, single_phase, - low_balances, misc_balances, + always_bls, + expect_assertion_error, + low_balances, + misc_balances, + single_phase, + spec_state_test, + spec_test, + with_all_phases, + with_custom_state, ) from eth2spec.test.helpers.attestations import sign_indexed_attestation from eth2spec.test.helpers.attester_slashings import ( - get_valid_attester_slashing, get_valid_attester_slashing_by_indices, - get_indexed_attestation_participants, get_attestation_2_data, get_attestation_1_data, + get_attestation_1_data, + get_attestation_2_data, + get_indexed_attestation_participants, + get_valid_attester_slashing, + get_valid_attester_slashing_by_indices, +) +from eth2spec.test.helpers.proposer_slashings import ( + get_min_slashing_penalty_quotient, + get_whistleblower_reward_quotient, ) -from eth2spec.test.helpers.proposer_slashings import get_min_slashing_penalty_quotient from eth2spec.test.helpers.state import ( get_balance, next_epoch_via_block, @@ -26,19 +38,21 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) If ``valid == False``, run expecting ``AssertionError`` """ - yield 'pre', state - yield 'attester_slashing', attester_slashing + yield "pre", state + yield "attester_slashing", attester_slashing if not valid: expect_assertion_error(lambda: spec.process_attester_slashing(state, attester_slashing)) - yield 'post', None + yield "post", None return slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) proposer_index = spec.get_beacon_proposer_index(state) pre_proposer_balance = get_balance(state, proposer_index) - pre_slashing_balances = {slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices} + pre_slashing_balances = { + slashed_index: get_balance(state, slashed_index) for slashed_index in slashed_indices + } pre_slashing_effectives = { slashed_index: state.validators[slashed_index].effective_balance for slashed_index in slashed_indices @@ -49,7 +63,7 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) } total_proposer_rewards = sum( - effective_balance // spec.WHISTLEBLOWER_REWARD_QUOTIENT + effective_balance // get_whistleblower_reward_quotient(spec) for effective_balance in pre_slashing_effectives.values() ) @@ -66,12 +80,14 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) if pre_withdrawalable_epoch < spec.FAR_FUTURE_EPOCH: expected_withdrawable_epoch = max( pre_withdrawalable_epoch, - spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR + spec.get_current_epoch(state) + spec.EPOCHS_PER_SLASHINGS_VECTOR, ) assert slashed_validator.withdrawable_epoch == expected_withdrawable_epoch else: assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH - assert get_balance(state, slashed_index) < pre_slashing_balances[slashed_index] + if slashed_index != proposer_index: + # NOTE: check proposer balances below + assert get_balance(state, slashed_index) < pre_slashing_balances[slashed_index] if proposer_index not in slashed_indices: # gained whistleblower reward @@ -86,12 +102,12 @@ def run_attester_slashing_processing(spec, state, attester_slashing, valid=True) assert get_balance(state, proposer_index) == expected_balance - yield 'post', state + yield "post", state @with_all_phases @spec_state_test -def test_success_double(spec, state): +def test_basic_double(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) yield from run_attester_slashing_processing(spec, state, attester_slashing) @@ -99,7 +115,7 @@ def test_success_double(spec, state): @with_all_phases @spec_state_test -def test_success_surround(spec, state): +def test_basic_surround(spec, state): next_epoch_via_block(spec, state) state.current_justified_checkpoint.epoch += 1 @@ -107,7 +123,7 @@ def test_success_surround(spec, state): att_1_data = get_attestation_1_data(spec, attester_slashing) att_2_data = get_attestation_2_data(spec, attester_slashing) - # set attestion1 to surround attestation 2 + # set attestation1 to surround attestation 2 att_1_data.source.epoch = att_2_data.source.epoch - 1 att_1_data.target.epoch = att_2_data.target.epoch + 1 @@ -119,7 +135,7 @@ def test_success_surround(spec, state): @with_all_phases @spec_state_test @always_bls -def test_success_already_exited_recent(spec, state): +def test_already_exited_recent(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) for index in slashed_indices: @@ -131,15 +147,17 @@ def test_success_already_exited_recent(spec, state): @with_all_phases @spec_state_test @always_bls -def test_success_proposer_index_slashed(spec, state): +def test_proposer_index_slashed(spec, state): # Transition past genesis slot because generally doesn't have a proposer next_epoch_via_block(spec, state) proposer_index = spec.get_beacon_proposer_index(state) attester_slashing = get_valid_attester_slashing_by_indices( - spec, state, + spec, + state, [proposer_index], - signed_1=True, signed_2=True, + signed_1=True, + signed_2=True, ) yield from run_attester_slashing_processing(spec, state, attester_slashing) @@ -147,15 +165,17 @@ def test_success_proposer_index_slashed(spec, state): @with_all_phases @spec_state_test -def test_success_attestation_from_future(spec, state): +def test_attestation_from_future(spec, state): # Transition state to future to enable generation of a "future" attestation future_state = state.copy() next_epoch_via_block(spec, future_state) # Generate slashing using the future state attester_slashing = get_valid_attester_slashing( - spec, future_state, + spec, + future_state, slot=state.slot + 5, # Slot is in the future wrt `state` - signed_1=True, signed_2=True + signed_1=True, + signed_2=True, ) yield from run_attester_slashing_processing(spec, state, attester_slashing) @@ -165,27 +185,31 @@ def test_success_attestation_from_future(spec, state): @with_custom_state(balances_fn=low_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) @spec_test @single_phase -def test_success_low_balances(spec, state): +def test_low_balances(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) yield from run_attester_slashing_processing(spec, state, attester_slashing) @with_all_phases -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase -def test_success_misc_balances(spec, state): +def test_misc_balances(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) yield from run_attester_slashing_processing(spec, state, attester_slashing) @with_all_phases -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase -def test_success_with_effective_balance_disparity(spec, state): +def test_with_effective_balance_disparity(spec, state): # Jitter balances to be different from effective balances rng = Random(12345) for i in range(len(state.balances)): @@ -200,7 +224,7 @@ def test_success_with_effective_balance_disparity(spec, state): @with_all_phases @spec_state_test @always_bls -def test_success_already_exited_long_ago(spec, state): +def test_already_exited_long_ago(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) slashed_indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) for index in slashed_indices: @@ -213,30 +237,30 @@ def test_success_already_exited_long_ago(spec, state): @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_1(spec, state): +def test_invalid_incorrect_sig_1(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_2(spec, state): +def test_invalid_incorrect_sig_2(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_1_and_2(spec, state): +def test_invalid_incorrect_sig_1_and_2(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test -def test_same_data(spec, state): +def test_invalid_same_data(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) indexed_att_1 = attester_slashing.attestation_1 @@ -244,12 +268,12 @@ def test_same_data(spec, state): indexed_att_1.data = att_2_data sign_indexed_attestation(spec, state, attester_slashing.attestation_1) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test -def test_no_double_or_surround(spec, state): +def test_invalid_no_double_or_surround(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) att_1_data = get_attestation_1_data(spec, attester_slashing) @@ -257,12 +281,12 @@ def test_no_double_or_surround(spec, state): sign_indexed_attestation(spec, state, attester_slashing.attestation_1) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test -def test_participants_already_slashed(spec, state): +def test_invalid_participants_already_slashed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) # set all indices to slashed @@ -270,63 +294,63 @@ def test_participants_already_slashed(spec, state): for index in validator_indices: state.validators[index].slashed = True - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_high_index(spec, state): +def test_invalid_att1_high_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) indices.append(spec.ValidatorIndex(len(state.validators))) # off by 1 attester_slashing.attestation_1.attesting_indices = indices - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_high_index(spec, state): +def test_invalid_att2_high_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_2) indices.append(spec.ValidatorIndex(len(state.validators))) # off by 1 attester_slashing.attestation_2.attesting_indices = indices - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_empty_indices(spec, state): +def test_invalid_att1_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) attester_slashing.attestation_1.attesting_indices = [] attester_slashing.attestation_1.signature = spec.bls.G2_POINT_AT_INFINITY - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_empty_indices(spec, state): +def test_invalid_att2_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) attester_slashing.attestation_2.attesting_indices = [] attester_slashing.attestation_2.signature = spec.bls.G2_POINT_AT_INFINITY - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_all_empty_indices(spec, state): +def test_invalid_all_empty_indices(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False) attester_slashing.attestation_1.attesting_indices = [] @@ -335,13 +359,13 @@ def test_all_empty_indices(spec, state): attester_slashing.attestation_2.attesting_indices = [] attester_slashing.attestation_2.signature = spec.bls.G2_POINT_AT_INFINITY - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_bad_extra_index(spec, state): +def test_invalid_att1_bad_extra_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = get_indexed_attestation_participants(spec, attester_slashing.attestation_1) @@ -351,29 +375,31 @@ def test_att1_bad_extra_index(spec, state): # Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not), # see if the bad extra index is spotted, and slashing is aborted. - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_bad_replaced_index(spec, state): +def test_invalid_att1_bad_replaced_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = attester_slashing.attestation_1.attesting_indices options = list(set(range(len(state.validators))) - set(indices)) - indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation. + indices[3] = options[ + len(options) // 2 + ] # replace with random index, not previously in attestation. attester_slashing.attestation_1.attesting_indices = sorted(indices) # Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not), # see if the bad replaced index is spotted, and slashing is aborted. - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_bad_extra_index(spec, state): +def test_invalid_att2_bad_extra_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = attester_slashing.attestation_2.attesting_indices @@ -383,29 +409,31 @@ def test_att2_bad_extra_index(spec, state): # Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not), # see if the bad extra index is spotted, and slashing is aborted. - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_bad_replaced_index(spec, state): +def test_invalid_att2_bad_replaced_index(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) indices = attester_slashing.attestation_2.attesting_indices options = list(set(range(len(state.validators))) - set(indices)) - indices[3] = options[len(options) // 2] # replace with random index, not previously in attestation. + indices[3] = options[ + len(options) // 2 + ] # replace with random index, not previously in attestation. attester_slashing.attestation_2.attesting_indices = sorted(indices) # Do not sign the modified attestation (it's ok to slash if attester signed, not if they did not), # see if the bad replaced index is spotted, and slashing is aborted. - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_duplicate_index_normal_signed(spec, state): +def test_invalid_att1_duplicate_index_normal_signed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) indices = list(attester_slashing.attestation_1.attesting_indices) @@ -419,13 +447,13 @@ def test_att1_duplicate_index_normal_signed(spec, state): attester_slashing.attestation_1.attesting_indices = sorted(indices) # it will just appear normal, unless the double index is spotted - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_duplicate_index_normal_signed(spec, state): +def test_invalid_att2_duplicate_index_normal_signed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) indices = list(attester_slashing.attestation_2.attesting_indices) @@ -439,42 +467,46 @@ def test_att2_duplicate_index_normal_signed(spec, state): attester_slashing.attestation_2.attesting_indices = sorted(indices) # it will just appear normal, unless the double index is spotted - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att1_duplicate_index_double_signed(spec, state): +def test_invalid_att1_duplicate_index_double_signed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) indices = list(attester_slashing.attestation_1.attesting_indices) indices.pop(1) # remove an index, make room for the additional duplicate index. indices.append(indices[2]) # add one of the indices a second time attester_slashing.attestation_1.attesting_indices = sorted(indices) - sign_indexed_attestation(spec, state, attester_slashing.attestation_1) # will have one attester signing it double + sign_indexed_attestation( + spec, state, attester_slashing.attestation_1 + ) # will have one attester signing it double - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_att2_duplicate_index_double_signed(spec, state): +def test_invalid_att2_duplicate_index_double_signed(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) indices = list(attester_slashing.attestation_2.attesting_indices) indices.pop(1) # remove an index, make room for the additional duplicate index. indices.append(indices[2]) # add one of the indices a second time attester_slashing.attestation_2.attesting_indices = sorted(indices) - sign_indexed_attestation(spec, state, attester_slashing.attestation_2) # will have one attester signing it double + sign_indexed_attestation( + spec, state, attester_slashing.attestation_2 + ) # will have one attester signing it double - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test -def test_unsorted_att_1(spec, state): +def test_invalid_unsorted_att_1(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=False, signed_2=True) indices = attester_slashing.attestation_1.attesting_indices @@ -482,12 +514,12 @@ def test_unsorted_att_1(spec, state): indices[1], indices[2] = indices[2], indices[1] # unsort second and third index sign_indexed_attestation(spec, state, attester_slashing.attestation_1) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) @with_all_phases @spec_state_test -def test_unsorted_att_2(spec, state): +def test_invalid_unsorted_att_2(spec, state): attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=False) indices = attester_slashing.attestation_2.attesting_indices @@ -495,4 +527,4 @@ def test_unsorted_att_2(spec, state): indices[1], indices[2] = indices[2], indices[1] # unsort second and third index sign_indexed_attestation(spec, state, attester_slashing.attestation_2) - yield from run_attester_slashing_processing(spec, state, attester_slashing, False) + yield from run_attester_slashing_processing(spec, state, attester_slashing, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py index b57090568b..1098e100c1 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_block_header.py @@ -1,7 +1,13 @@ from copy import deepcopy -from eth2spec.test.context import spec_state_test, expect_assertion_error, with_all_phases +from eth2spec.test.context import expect_assertion_error, spec_state_test, with_all_phases from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.forks import is_post_bellatrix, is_post_eip7732 from eth2spec.test.helpers.state import next_slot @@ -20,21 +26,21 @@ def run_block_header_processing(spec, state, block, prepare_state=True, valid=Tr if prepare_state: prepare_state_for_header_processing(spec, state) - yield 'pre', state - yield 'block', block + yield "pre", state + yield "block", block if not valid: expect_assertion_error(lambda: spec.process_block_header(state, block)) - yield 'post', None + yield "post", None return spec.process_block_header(state, block) - yield 'post', state + yield "post", state @with_all_phases @spec_state_test -def test_success_block_header(spec, state): +def test_basic_block_header(spec, state): block = build_empty_block_for_next_slot(spec, state) yield from run_block_header_processing(spec, state, block) @@ -64,7 +70,14 @@ def test_invalid_proposer_index(spec, state): @spec_state_test def test_invalid_parent_root(spec, state): block = build_empty_block_for_next_slot(spec, state) - block.parent_root = b'\12' * 32 # invalid prev root + block.parent_root = b"\12" * 32 # invalid prev root + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + elif is_post_bellatrix(spec): + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) yield from run_block_header_processing(spec, state, block, valid=False) @@ -81,13 +94,24 @@ def test_invalid_multiple_blocks_single_slot(spec, state): child_block = block.copy() child_block.parent_root = block.hash_tree_root() + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + child_block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + elif is_post_bellatrix(spec): + child_block.body.execution_payload.block_hash = compute_el_block_hash_for_block( + spec, child_block + ) - yield from run_block_header_processing(spec, state, child_block, prepare_state=False, valid=False) + yield from run_block_header_processing( + spec, state, child_block, prepare_state=False, valid=False + ) @with_all_phases @spec_state_test -def test_proposer_slashed(spec, state): +def test_invalid_proposer_slashed(spec, state): # use stub state to get proposer index of next slot stub_state = deepcopy(state) next_slot(spec, stub_state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py index 36e76f46c8..5d1bde5bdd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_deposit.py @@ -1,61 +1,13 @@ -from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.context import always_bls, spec_state_test, with_all_phases from eth2spec.test.helpers.deposits import ( build_deposit, prepare_state_and_deposit, + run_deposit_processing, + run_deposit_processing_with_specific_fork_version, sign_deposit_data, - deposit_from_context) -from eth2spec.test.helpers.state import get_balance +) +from eth2spec.test.helpers.forks import is_post_electra from eth2spec.test.helpers.keys import privkeys, pubkeys -from eth2spec.utils import bls - - -def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True): - """ - Run ``process_deposit``, yielding: - - pre-state ('pre') - - deposit ('deposit') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - pre_validator_count = len(state.validators) - pre_balance = 0 - if validator_index < pre_validator_count: - pre_balance = get_balance(state, validator_index) - - yield 'pre', state - yield 'deposit', deposit - - if not valid: - expect_assertion_error(lambda: spec.process_deposit(state, deposit)) - yield 'post', None - return - - spec.process_deposit(state, deposit) - - yield 'post', state - - if not effective: - assert len(state.validators) == pre_validator_count - assert len(state.balances) == pre_validator_count - if validator_index < pre_validator_count: - assert get_balance(state, validator_index) == pre_balance - else: - if validator_index < pre_validator_count: - # top-up - assert len(state.validators) == pre_validator_count - assert len(state.balances) == pre_validator_count - else: - # new validator - assert len(state.validators) == pre_validator_count + 1 - assert len(state.balances) == pre_validator_count + 1 - assert get_balance(state, validator_index) == pre_balance + deposit.data.amount - - effective = min(spec.MAX_EFFECTIVE_BALANCE, - pre_balance + deposit.data.amount) - effective -= effective % spec.EFFECTIVE_BALANCE_INCREMENT - assert state.validators[validator_index].effective_balance == effective - - assert state.eth1_deposit_index == state.eth1_data.deposit_count @with_all_phases @@ -101,12 +53,13 @@ def test_new_deposit_eth1_withdrawal_credentials(spec, state): validator_index = len(state.validators) withdrawal_credentials = ( spec.ETH1_ADDRESS_WITHDRAWAL_PREFIX - + b'\x00' * 11 # specified 0s - + b'\x59' * 20 # a 20-byte eth1 address + + b"\x00" * 11 # specified 0s + + b"\x59" * 20 # a 20-byte eth1 address ) amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit( - spec, state, + spec, + state, validator_index, amount, withdrawal_credentials=withdrawal_credentials, @@ -122,12 +75,12 @@ def test_new_deposit_non_versioned_withdrawal_credentials(spec, state): # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) withdrawal_credentials = ( - b'\xFF' # Non specified withdrawal credentials version - + b'\x02' * 31 # Garabage bytes + b"\xff" + b"\x02" * 31 # Non specified withdrawal credentials version # Garbage bytes ) amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit( - spec, state, + spec, + state, validator_index, amount, withdrawal_credentials=withdrawal_credentials, @@ -140,96 +93,112 @@ def test_new_deposit_non_versioned_withdrawal_credentials(spec, state): @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_other_version(spec, state): +def test_correct_sig_but_forked_state(spec, state): validator_index = len(state.validators) amount = spec.MAX_EFFECTIVE_BALANCE - - pubkey = pubkeys[validator_index] - privkey = privkeys[validator_index] - withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] - - # Go through the effort of manually signing, not something normally done. This sig domain will be invalid. - deposit_message = spec.DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount) - domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=spec.Version('0xaabbccdd')) - deposit_data = spec.DepositData( - pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount, - signature=bls.Sign(privkey, spec.compute_signing_root(deposit_message, domain)) - ) - deposit, root, _ = deposit_from_context(spec, [deposit_data], 0) - - state.eth1_deposit_index = 0 - state.eth1_data.deposit_root = root - state.eth1_data.deposit_count = 1 - - yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=False) + # deposits will always be valid, regardless of the current fork + state.fork.current_version = spec.Version("0x1234abcd") + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + yield from run_deposit_processing(spec, state, deposit, validator_index) @with_all_phases @spec_state_test @always_bls -def test_valid_sig_but_forked_state(spec, state): +def test_incorrect_sig_new_deposit(spec, state): + # fresh deposit = next validator index = validator appended to registry validator_index = len(state.validators) amount = spec.MAX_EFFECTIVE_BALANCE - # deposits will always be valid, regardless of the current fork - state.fork.current_version = spec.Version('0x1234abcd') + deposit = prepare_state_and_deposit(spec, state, validator_index, amount) + yield from run_deposit_processing(spec, state, deposit, validator_index, effective=False) + + +@with_all_phases +@spec_state_test +def test_top_up__max_effective_balance(spec, state): + validator_index = 0 + amount = spec.MAX_EFFECTIVE_BALANCE // 4 deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) - yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True) + + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE + state.validators[validator_index].effective_balance = spec.MAX_EFFECTIVE_BALANCE + + yield from run_deposit_processing(spec, state, deposit, validator_index) + + if not is_post_electra(spec): + assert state.balances[validator_index] == spec.MAX_EFFECTIVE_BALANCE + amount + assert state.validators[validator_index].effective_balance == spec.MAX_EFFECTIVE_BALANCE @with_all_phases @spec_state_test -@always_bls -def test_invalid_sig_new_deposit(spec, state): - # fresh deposit = next validator index = validator appended to registry - validator_index = len(state.validators) - amount = spec.MAX_EFFECTIVE_BALANCE - deposit = prepare_state_and_deposit(spec, state, validator_index, amount) - yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=False) +def test_top_up__less_effective_balance(spec, state): + validator_index = 0 + amount = spec.MAX_EFFECTIVE_BALANCE // 4 + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + initial_balance = spec.MAX_EFFECTIVE_BALANCE - 1000 + initial_effective_balance = spec.MAX_EFFECTIVE_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + state.balances[validator_index] = initial_balance + state.validators[validator_index].effective_balance = initial_effective_balance + + yield from run_deposit_processing(spec, state, deposit, validator_index) + + if not is_post_electra(spec): + assert state.balances[validator_index] == initial_balance + amount + # unchanged effective balance + assert state.validators[validator_index].effective_balance == initial_effective_balance @with_all_phases @spec_state_test -def test_success_top_up(spec, state): +def test_top_up__zero_balance(spec, state): validator_index = 0 amount = spec.MAX_EFFECTIVE_BALANCE // 4 deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + initial_balance = 0 + initial_effective_balance = 0 + state.balances[validator_index] = initial_balance + state.validators[validator_index].effective_balance = initial_effective_balance + yield from run_deposit_processing(spec, state, deposit, validator_index) + if not is_post_electra(spec): + assert state.balances[validator_index] == initial_balance + amount + # unchanged effective balance + assert state.validators[validator_index].effective_balance == initial_effective_balance + @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_top_up(spec, state): +def test_incorrect_sig_top_up(spec, state): validator_index = 0 amount = spec.MAX_EFFECTIVE_BALANCE // 4 deposit = prepare_state_and_deposit(spec, state, validator_index, amount) # invalid signatures, in top-ups, are allowed! - yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True) + yield from run_deposit_processing(spec, state, deposit, validator_index) @with_all_phases @spec_state_test -def test_invalid_withdrawal_credentials_top_up(spec, state): +def test_incorrect_withdrawal_credentials_top_up(spec, state): validator_index = 0 amount = spec.MAX_EFFECTIVE_BALANCE // 4 withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(b"junk")[1:] deposit = prepare_state_and_deposit( - spec, - state, - validator_index, - amount, - withdrawal_credentials=withdrawal_credentials + spec, state, validator_index, amount, withdrawal_credentials=withdrawal_credentials ) # inconsistent withdrawal credentials, in top-ups, are allowed! - yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True) + yield from run_deposit_processing(spec, state, deposit, validator_index) @with_all_phases @spec_state_test -def test_wrong_deposit_for_deposit_count(spec, state): +def test_invalid_wrong_deposit_for_deposit_count(spec, state): deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))] # build root for deposit_1 @@ -242,7 +211,7 @@ def test_wrong_deposit_for_deposit_count(spec, state): pubkey_1, privkey_1, spec.MAX_EFFECTIVE_BALANCE, - withdrawal_credentials=b'\x00' * 32, + withdrawal_credentials=b"\x00" * 32, signed=True, ) deposit_count_1 = len(deposit_data_leaves) @@ -257,7 +226,7 @@ def test_wrong_deposit_for_deposit_count(spec, state): pubkey_2, privkey_2, spec.MAX_EFFECTIVE_BALANCE, - withdrawal_credentials=b'\x00' * 32, + withdrawal_credentials=b"\x00" * 32, signed=True, ) @@ -270,7 +239,7 @@ def test_wrong_deposit_for_deposit_count(spec, state): @with_all_phases @spec_state_test -def test_bad_merkle_proof(spec, state): +def test_invalid_bad_merkle_proof(spec, state): validator_index = len(state.validators) amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(spec, state, validator_index, amount) @@ -281,3 +250,49 @@ def test_bad_merkle_proof(spec, state): sign_deposit_data(spec, deposit.data, privkeys[validator_index]) yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False) + + +@with_all_phases +@spec_state_test +def test_key_validate_invalid_subgroup(spec, state): + validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + + # All-zero pubkey would not pass `bls.KeyValidate`, but `process_deposit` would not throw exception. + pubkey = b"\x00" * 48 + + deposit = prepare_state_and_deposit( + spec, state, validator_index, amount, pubkey=pubkey, signed=True + ) + + yield from run_deposit_processing(spec, state, deposit, validator_index) + + +@with_all_phases +@spec_state_test +def test_key_validate_invalid_decompression(spec, state): + validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + + # `deserialization_fails_infinity_with_true_b_flag` BLS G1 deserialization test case. + # This pubkey would not pass `bls.KeyValidate`, but `process_deposit` would not throw exception. + pubkey_hex = "c01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + pubkey = bytes.fromhex(pubkey_hex) + + deposit = prepare_state_and_deposit( + spec, state, validator_index, amount, pubkey=pubkey, signed=True + ) + + yield from run_deposit_processing(spec, state, deposit, validator_index) + + +@with_all_phases +@spec_state_test +@always_bls +def test_ineffective_deposit_with_bad_fork_version(spec, state): + yield from run_deposit_processing_with_specific_fork_version( + spec, + state, + fork_version=spec.Version("0xAaBbCcDd"), + effective=False, + ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py index 6ca87adaec..857bcd1849 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_proposer_slashing.py @@ -1,8 +1,16 @@ -from eth2spec.test.context import spec_state_test, expect_assertion_error, always_bls, with_all_phases +from eth2spec.test.context import ( + always_bls, + expect_assertion_error, + spec_state_test, + with_all_phases, +) from eth2spec.test.helpers.block import build_empty_block_for_next_slot from eth2spec.test.helpers.block_header import sign_block_header from eth2spec.test.helpers.keys import privkeys -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect +from eth2spec.test.helpers.proposer_slashings import ( + check_proposer_slashing_effect, + get_valid_proposer_slashing, +) from eth2spec.test.helpers.state import next_epoch @@ -17,16 +25,16 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True) pre_state = state.copy() - yield 'pre', state - yield 'proposer_slashing', proposer_slashing + yield "pre", state + yield "proposer_slashing", proposer_slashing if not valid: expect_assertion_error(lambda: spec.process_proposer_slashing(state, proposer_slashing)) - yield 'post', None + yield "post", None return spec.process_proposer_slashing(state, proposer_slashing) - yield 'post', state + yield "post", state slashed_proposer_index = proposer_slashing.signed_header_1.message.proposer_index check_proposer_slashing_effect(spec, pre_state, state, slashed_proposer_index) @@ -34,7 +42,7 @@ def run_proposer_slashing_processing(spec, state, proposer_slashing, valid=True) @with_all_phases @spec_state_test -def test_success(spec, state): +def test_basic(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) yield from run_proposer_slashing_processing(spec, state, proposer_slashing) @@ -42,23 +50,25 @@ def test_success(spec, state): @with_all_phases @spec_state_test -def test_success_slashed_and_proposer_index_the_same(spec, state): +def test_slashed_and_proposer_index_the_same(spec, state): # Get proposer for next slot block = build_empty_block_for_next_slot(spec, state) proposer_index = block.proposer_index # Create slashing for same proposer - proposer_slashing = get_valid_proposer_slashing(spec, state, - slashed_index=proposer_index, - signed_1=True, signed_2=True) + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=proposer_index, signed_1=True, signed_2=True + ) yield from run_proposer_slashing_processing(spec, state, proposer_slashing) @with_all_phases @spec_state_test -def test_success_block_header_from_future(spec, state): - proposer_slashing = get_valid_proposer_slashing(spec, state, slot=state.slot + 5, signed_1=True, signed_2=True) +def test_block_header_from_future(spec, state): + proposer_slashing = get_valid_proposer_slashing( + spec, state, slot=state.slot + 5, signed_1=True, signed_2=True + ) yield from run_proposer_slashing_processing(spec, state, proposer_slashing) @@ -66,31 +76,31 @@ def test_success_block_header_from_future(spec, state): @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_1(spec, state): +def test_invalid_incorrect_sig_1(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=True) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_2(spec, state): +def test_invalid_incorrect_sig_2(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_1_and_2(spec, state): +def test_invalid_incorrect_sig_1_and_2(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=False, signed_2=False) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test @always_bls -def test_invalid_sig_1_and_2_swap(spec, state): +def test_invalid_incorrect_sig_1_and_2_swap(spec, state): # Get valid signatures for the slashings proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) @@ -98,18 +108,18 @@ def test_invalid_sig_1_and_2_swap(spec, state): signature_1 = proposer_slashing.signed_header_1.signature proposer_slashing.signed_header_1.signature = proposer_slashing.signed_header_2.signature proposer_slashing.signed_header_2.signature = signature_1 - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_invalid_proposer_index(spec, state): +def test_invalid_incorrect_proposer_index(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # Index just too high (by 1) proposer_slashing.signed_header_1.message.proposer_index = len(state.validators) proposer_slashing.signed_header_2.message.proposer_index = len(state.validators) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @@ -123,78 +133,86 @@ def test_invalid_different_proposer_indices(spec, state): active_indices = [i for i in active_indices if i != header_1.proposer_index] header_2.proposer_index = active_indices[0] - proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[header_2.proposer_index]) + proposer_slashing.signed_header_2 = sign_block_header( + spec, state, header_2, privkeys[header_2.proposer_index] + ) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_epochs_are_different(spec, state): +def test_invalid_slots_of_different_epochs(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) # set slots to be in different epochs header_2 = proposer_slashing.signed_header_2.message proposer_index = header_2.proposer_index header_2.slot += spec.SLOTS_PER_EPOCH - proposer_slashing.signed_header_2 = sign_block_header(spec, state, header_2, privkeys[proposer_index]) + proposer_slashing.signed_header_2 = sign_block_header( + spec, state, header_2, privkeys[proposer_index] + ) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_headers_are_same_sigs_are_same(spec, state): +def test_invalid_headers_are_same_sigs_are_same(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) # set headers to be the same proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1.copy() - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_headers_are_same_sigs_are_different(spec, state): +def test_invalid_headers_are_same_sigs_are_different(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=False) # set headers to be the same proposer_slashing.signed_header_2 = proposer_slashing.signed_header_1.copy() # but signatures to be different - proposer_slashing.signed_header_2.signature = proposer_slashing.signed_header_2.signature[:-1] + b'\x00' + proposer_slashing.signed_header_2.signature = ( + proposer_slashing.signed_header_2.signature[:-1] + b"\x00" + ) - assert proposer_slashing.signed_header_1.signature != proposer_slashing.signed_header_2.signature + assert ( + proposer_slashing.signed_header_1.signature != proposer_slashing.signed_header_2.signature + ) - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_proposer_is_not_activated(spec, state): +def test_invalid_proposer_is_not_activated(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # set proposer to be not active yet proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].activation_epoch = spec.get_current_epoch(state) + 1 - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_proposer_is_slashed(spec, state): +def test_invalid_proposer_is_slashed(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # set proposer to slashed proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].slashed = True - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) @with_all_phases @spec_state_test -def test_proposer_is_withdrawn(spec, state): +def test_invalid_proposer_is_withdrawn(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) # move 1 epoch into future, to allow for past withdrawable epoch @@ -204,4 +222,4 @@ def test_proposer_is_withdrawn(spec, state): proposer_index = proposer_slashing.signed_header_1.message.proposer_index state.validators[proposer_index].withdrawable_epoch = current_epoch - 1 - yield from run_proposer_slashing_processing(spec, state, proposer_slashing, False) + yield from run_proposer_slashing_processing(spec, state, proposer_slashing, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py index 9e209f23e2..e34bec23af 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_voluntary_exit.py @@ -1,45 +1,24 @@ -from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( - spec_state_test, expect_assertion_error, - always_bls, with_all_phases, with_presets, - spec_test, single_phase, - with_custom_state, scaled_churn_balances, + always_bls, + scaled_churn_balances_min_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_all_phases, + with_custom_state, + with_presets, ) +from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.keys import pubkey_to_privkey -from eth2spec.test.helpers.voluntary_exits import sign_voluntary_exit - - -def run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=True): - """ - Run ``process_voluntary_exit``, yielding: - - pre-state ('pre') - - voluntary_exit ('voluntary_exit') - - post-state ('post'). - If ``valid == False``, run expecting ``AssertionError`` - """ - validator_index = signed_voluntary_exit.message.validator_index - - yield 'pre', state - yield 'voluntary_exit', signed_voluntary_exit - - if not valid: - expect_assertion_error(lambda: spec.process_voluntary_exit(state, signed_voluntary_exit)) - yield 'post', None - return - - pre_exit_epoch = state.validators[validator_index].exit_epoch - - spec.process_voluntary_exit(state, signed_voluntary_exit) - - yield 'post', state - - assert pre_exit_epoch == spec.FAR_FUTURE_EPOCH - assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH +from eth2spec.test.helpers.voluntary_exits import ( + run_voluntary_exit_processing, + sign_voluntary_exit, +) @with_all_phases @spec_state_test -def test_success(spec, state): +def test_basic(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -48,17 +27,23 @@ def test_success(spec, state): privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) - assert state.validators[validator_index].exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + assert state.validators[validator_index].exit_epoch == spec.compute_activation_exit_epoch( + current_epoch + ) @with_all_phases @spec_state_test @always_bls -def test_invalid_signature(spec, state): +def test_invalid_incorrect_signature(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -71,7 +56,7 @@ def test_invalid_signature(spec, state): ) signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, 12345) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) def run_test_success_exit_queue(spec, state): @@ -81,7 +66,9 @@ def run_test_success_exit_queue(spec, state): current_epoch = spec.get_current_epoch(state) # exit `MAX_EXITS_PER_EPOCH` - initial_indices = spec.get_active_validator_indices(state, current_epoch)[:spec.get_validator_churn_limit(state)] + initial_indices = spec.get_active_validator_indices(state, current_epoch)[ + : spec.get_validator_churn_limit(state) + ] # Prepare a bunch of exits, based on the current state exit_queue = [] @@ -89,7 +76,8 @@ def run_test_success_exit_queue(spec, state): privkey = pubkey_to_privkey[state.validators[index].pubkey] signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=index), privkey) + spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=index), privkey + ) exit_queue.append(signed_voluntary_exit) @@ -104,7 +92,11 @@ def run_test_success_exit_queue(spec, state): privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) # This is the interesting part of the test: on a pre-state with a full exit queue, # when processing an additional exit, it results in an exit in a later epoch @@ -112,8 +104,7 @@ def run_test_success_exit_queue(spec, state): for index in initial_indices: assert ( - state.validators[validator_index].exit_epoch == - state.validators[index].exit_epoch + 1 + state.validators[validator_index].exit_epoch == state.validators[index].exit_epoch + 1 ) @@ -124,10 +115,15 @@ def test_success_exit_queue__min_churn(spec, state): @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=scaled_churn_balances_min_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) @single_phase def test_success_exit_queue__scaled_churn(spec, state): churn_limit = spec.get_validator_churn_limit(state) @@ -146,7 +142,11 @@ def test_default_exit_epoch_subsequent_exit(spec, state): privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) # Exit one validator prior to this new one exited_index = spec.get_active_validator_indices(state, current_epoch)[-1] @@ -154,12 +154,14 @@ def test_default_exit_epoch_subsequent_exit(spec, state): yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit) - assert state.validators[validator_index].exit_epoch == spec.compute_activation_exit_epoch(current_epoch) + assert state.validators[validator_index].exit_epoch == spec.compute_activation_exit_epoch( + current_epoch + ) @with_all_phases @spec_state_test -def test_validator_exit_in_future(spec, state): +def test_invalid_validator_exit_in_future(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -173,12 +175,12 @@ def test_validator_exit_in_future(spec, state): ) signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) @with_all_phases @spec_state_test -def test_validator_invalid_validator_index(spec, state): +def test_invalid_validator_incorrect_validator_index(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -192,12 +194,12 @@ def test_validator_invalid_validator_index(spec, state): ) signed_voluntary_exit = sign_voluntary_exit(spec, state, voluntary_exit, privkey) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) @with_all_phases @spec_state_test -def test_validator_not_active(spec, state): +def test_invalid_validator_not_active(spec, state): current_epoch = spec.get_current_epoch(state) validator_index = spec.get_active_validator_indices(state, current_epoch)[0] privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] @@ -205,14 +207,18 @@ def test_validator_not_active(spec, state): state.validators[validator_index].activation_epoch = spec.FAR_FUTURE_EPOCH signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) @with_all_phases @spec_state_test -def test_validator_already_exited(spec, state): +def test_invalid_validator_already_exited(spec, state): # move state forward SHARD_COMMITTEE_PERIOD epochs to allow validator able to exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH @@ -224,24 +230,32 @@ def test_validator_already_exited(spec, state): state.validators[validator_index].exit_epoch = current_epoch + 2 signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) @with_all_phases @spec_state_test -def test_validator_not_active_long_enough(spec, state): +def test_invalid_validator_not_active_long_enough(spec, state): current_epoch = spec.get_current_epoch(state) validator_index = spec.get_active_validator_indices(state, current_epoch)[0] privkey = pubkey_to_privkey[state.validators[validator_index].pubkey] signed_voluntary_exit = sign_voluntary_exit( - spec, state, spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), privkey) + spec, + state, + spec.VoluntaryExit(epoch=current_epoch, validator_index=validator_index), + privkey, + ) assert ( - current_epoch - state.validators[validator_index].activation_epoch < - spec.config.SHARD_COMMITTEE_PERIOD + current_epoch - state.validators[validator_index].activation_epoch + < spec.config.SHARD_COMMITTEE_PERIOD ) - yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, False) + yield from run_voluntary_exit_processing(spec, state, signed_voluntary_exit, valid=False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py index 43fe12220d..9de298bb44 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_effective_balance_updates.py @@ -1,15 +1,36 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.epoch_processing import run_epoch_processing_to +from eth2spec.test.helpers.epoch_processing import ( + run_epoch_processing_from, + run_epoch_processing_to, + run_process_slots_up_to_epoch_boundary, +) +from eth2spec.test.helpers.forks import is_post_electra +from eth2spec.test.helpers.withdrawals import ( + set_compounding_withdrawal_credential, +) @with_all_phases @spec_state_test def test_effective_balance_hysteresis(spec, state): + yield from run_test_effective_balance_hysteresis(spec, state) + + +def run_test_effective_balance_hysteresis(spec, state, with_compounding_credentials=False): + assert is_post_electra(spec) or not with_compounding_credentials # Prepare state up to the final-updates. # Then overwrite the balances, we only want to focus to be on the hysteresis based changes. - run_epoch_processing_to(spec, state, 'process_effective_balance_updates') + run_process_slots_up_to_epoch_boundary(spec, state) + yield "pre_epoch", state + run_epoch_processing_to( + spec, state, "process_effective_balance_updates", enable_slots_processing=False + ) # Set some edge cases for balances - max = spec.MAX_EFFECTIVE_BALANCE + max = ( + spec.MAX_EFFECTIVE_BALANCE_ELECTRA + if with_compounding_credentials + else spec.MAX_EFFECTIVE_BALANCE + ) min = spec.config.EJECTION_BALANCE inc = spec.EFFECTIVE_BALANCE_INCREMENT div = spec.HYSTERESIS_QUOTIENT @@ -28,19 +49,57 @@ def test_effective_balance_hysteresis(spec, state): (max, max - inc + 1, max - inc, "close to 1 step lower"), (min, min + (hys_inc * up), min, "bigger balance, but not high enough"), (min, min + (hys_inc * up) + 1, min + inc, "bigger balance, high enough, but small step"), - (min, min + (hys_inc * div * 2) - 1, min + inc, "bigger balance, high enough, close to double step"), + ( + min, + min + (hys_inc * div * 2) - 1, + min + inc, + "bigger balance, high enough, close to double step", + ), (min, min + (hys_inc * div * 2), min + (2 * inc), "exact two step balance increment"), (min, min + (hys_inc * div * 2) + 1, min + (2 * inc), "over two steps, round down"), ] + + if with_compounding_credentials: + min = spec.MIN_ACTIVATION_BALANCE + cases = cases + [ + (min, min + (hys_inc * up), min, "bigger balance, but not high enough"), + ( + min, + min + (hys_inc * up) + 1, + min + inc, + "bigger balance, high enough, but small step", + ), + ( + min, + min + (hys_inc * div * 2) - 1, + min + inc, + "bigger balance, high enough, close to double step", + ), + (min, min + (hys_inc * div * 2), min + (2 * inc), "exact two step balance increment"), + (min, min + (hys_inc * div * 2) + 1, min + (2 * inc), "over two steps, round down"), + (min, min * 2 + 1, min * 2, "top up or consolidation doubling the balance"), + ( + min, + min * 2 - 1, + min * 2 - spec.EFFECTIVE_BALANCE_INCREMENT, + "top up or consolidation almost doubling the balance", + ), + ] + current_epoch = spec.get_current_epoch(state) for i, (pre_eff, bal, _, _) in enumerate(cases): assert spec.is_active_validator(state.validators[i], current_epoch) + if with_compounding_credentials: + set_compounding_withdrawal_credential(spec, state, i) state.validators[i].effective_balance = pre_eff state.balances[i] = bal - yield 'pre', state + yield "pre", state spec.process_effective_balance_updates(state) - yield 'post', state + yield "post", state for i, (_, _, post_eff, name) in enumerate(cases): assert state.validators[i].effective_balance == post_eff, name + + run_epoch_processing_from(spec, state, "process_effective_balance_updates") + yield "post_epoch", state diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_eth1_data_reset.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_eth1_data_reset.py index 71af69f79c..188fd332dd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_eth1_data_reset.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_eth1_data_reset.py @@ -6,7 +6,7 @@ def run_process_eth1_data_reset(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_eth1_data_reset') + yield from run_epoch_processing_with(spec, state, "process_eth1_data_reset") @with_all_phases @@ -18,9 +18,12 @@ def test_eth1_vote_no_reset(spec, state): for i in range(state.slot + 1): # add a vote for each skipped slot. state.eth1_data_votes.append( - spec.Eth1Data(deposit_root=b'\xaa' * 32, - deposit_count=state.eth1_deposit_index, - block_hash=b'\xbb' * 32)) + spec.Eth1Data( + deposit_root=b"\xaa" * 32, + deposit_count=state.eth1_deposit_index, + block_hash=b"\xbb" * 32, + ) + ) yield from run_process_eth1_data_reset(spec, state) @@ -34,9 +37,12 @@ def test_eth1_vote_reset(spec, state): state.slot = (spec.EPOCHS_PER_ETH1_VOTING_PERIOD * spec.SLOTS_PER_EPOCH) - 1 for i in range(state.slot + 1): # add a vote for each skipped slot. state.eth1_data_votes.append( - spec.Eth1Data(deposit_root=b'\xaa' * 32, - deposit_count=state.eth1_deposit_index, - block_hash=b'\xbb' * 32)) + spec.Eth1Data( + deposit_root=b"\xaa" * 32, + deposit_count=state.eth1_deposit_index, + block_hash=b"\xbb" * 32, + ) + ) yield from run_process_eth1_data_reset(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_historical_roots_update.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_historical_roots_update.py index 02ce7ccba3..f600fc65a7 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_historical_roots_update.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_historical_roots_update.py @@ -1,14 +1,18 @@ -from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with +from eth2spec.test.context import ( + ALTAIR, + BELLATRIX, + PHASE0, + spec_state_test, + with_phases, ) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with def run_process_historical_roots_update(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_historical_roots_update') + yield from run_epoch_processing_with(spec, state, "process_historical_roots_update") -@with_all_phases +@with_phases([PHASE0, ALTAIR, BELLATRIX]) @spec_state_test def test_historical_root_accumulator(spec, state): # skip ahead to near the end of the historical roots period (excl block before epoch processing) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py index 1d3197ba61..9fbc080c6c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_justification_and_finalization.py @@ -1,17 +1,21 @@ from random import Random -from eth2spec.test.context import is_post_altair, spec_state_test, with_all_phases + +from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.helpers.epoch_processing import ( run_epoch_processing_with, ) -from eth2spec.test.helpers.state import transition_to, next_epoch_via_block, next_slot +from eth2spec.test.helpers.forks import is_post_altair +from eth2spec.test.helpers.state import next_epoch_via_block, next_slot, transition_to from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators def run_process_just_and_fin(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_justification_and_finalization') + yield from run_epoch_processing_with(spec, state, "process_justification_and_finalization") -def add_mock_attestations(spec, state, epoch, source, target, sufficient_support=False, messed_up_target=False): +def add_mock_attestations( + spec, state, epoch, source, target, sufficient_support=False, messed_up_target=False +): # we must be at the end of the epoch assert (state.slot + 1) % spec.SLOTS_PER_EPOCH == 0 @@ -24,14 +28,17 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support elif previous_epoch == epoch: attestations = state.previous_epoch_attestations else: - raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}") + raise Exception( + f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}" + ) + elif current_epoch == epoch: + epoch_participation = state.current_epoch_participation + elif previous_epoch == epoch: + epoch_participation = state.previous_epoch_participation else: - if current_epoch == epoch: - epoch_participation = state.current_epoch_participation - elif previous_epoch == epoch: - epoch_participation = state.previous_epoch_participation - else: - raise Exception(f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}") + raise Exception( + f"cannot include attestations in epoch ${epoch} from epoch ${current_epoch}" + ) total_balance = spec.get_total_active_balance(state) remaining_balance = int(total_balance * 2 // 3) # can become negative @@ -64,40 +71,50 @@ def add_mock_attestations(spec, state, epoch, source, target, sufficient_support # Update state if not is_post_altair(spec): - attestations.append(spec.PendingAttestation( - aggregation_bits=aggregation_bits, - data=spec.AttestationData( - slot=slot, - beacon_block_root=b'\xff' * 32, # irrelevant to testing - source=source, - target=target, - index=index, - ), - inclusion_delay=1, - )) + attestations.append( + spec.PendingAttestation( + aggregation_bits=aggregation_bits, + data=spec.AttestationData( + slot=slot, + beacon_block_root=b"\xff" * 32, # irrelevant to testing + source=source, + target=target, + index=index, + ), + inclusion_delay=1, + ) + ) if messed_up_target: - attestations[len(attestations) - 1].data.target.root = b'\x99' * 32 + attestations[len(attestations) - 1].data.target.root = b"\x99" * 32 else: for i, index in enumerate(committee): if aggregation_bits[i]: - epoch_participation[index] |= spec.ParticipationFlags(2**spec.TIMELY_HEAD_FLAG_INDEX) - epoch_participation[index] |= spec.ParticipationFlags(2**spec.TIMELY_SOURCE_FLAG_INDEX) + epoch_participation[index] |= spec.ParticipationFlags( + 2**spec.TIMELY_HEAD_FLAG_INDEX + ) + epoch_participation[index] |= spec.ParticipationFlags( + 2**spec.TIMELY_SOURCE_FLAG_INDEX + ) if not messed_up_target: - epoch_participation[index] |= spec.ParticipationFlags(2**spec.TIMELY_TARGET_FLAG_INDEX) + epoch_participation[index] |= spec.ParticipationFlags( + 2**spec.TIMELY_TARGET_FLAG_INDEX + ) def get_checkpoints(spec, epoch): - c1 = None if epoch < 1 else spec.Checkpoint(epoch=epoch - 1, root=b'\xaa' * 32) - c2 = None if epoch < 2 else spec.Checkpoint(epoch=epoch - 2, root=b'\xbb' * 32) - c3 = None if epoch < 3 else spec.Checkpoint(epoch=epoch - 3, root=b'\xcc' * 32) - c4 = None if epoch < 4 else spec.Checkpoint(epoch=epoch - 4, root=b'\xdd' * 32) - c5 = None if epoch < 5 else spec.Checkpoint(epoch=epoch - 5, root=b'\xee' * 32) + c1 = None if epoch < 1 else spec.Checkpoint(epoch=epoch - 1, root=b"\xaa" * 32) + c2 = None if epoch < 2 else spec.Checkpoint(epoch=epoch - 2, root=b"\xbb" * 32) + c3 = None if epoch < 3 else spec.Checkpoint(epoch=epoch - 3, root=b"\xcc" * 32) + c4 = None if epoch < 4 else spec.Checkpoint(epoch=epoch - 4, root=b"\xdd" * 32) + c5 = None if epoch < 5 else spec.Checkpoint(epoch=epoch - 5, root=b"\xee" * 32) return c1, c2, c3, c4, c5 def put_checkpoints_in_block_roots(spec, state, checkpoints): for c in checkpoints: - state.block_roots[spec.compute_start_slot_at_epoch(c.epoch) % spec.SLOTS_PER_HISTORICAL_ROOT] = c.root + state.block_roots[ + spec.compute_start_slot_at_epoch(c.epoch) % spec.SLOTS_PER_HISTORICAL_ROOT + ] = c.root def finalize_on_234(spec, state, epoch, sufficient_support): @@ -115,13 +132,14 @@ def finalize_on_234(spec, state, epoch, sufficient_support): state.previous_justified_checkpoint = c4 state.current_justified_checkpoint = c3 state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]() - state.justification_bits[1:3] = [1, 1] # mock 3rd and 4th latest epochs as justified (indices are pre-shift) + state.justification_bits[1:3] = [ + 1, + 1, + ] # mock 3rd and 4th latest epochs as justified (indices are pre-shift) # mock the 2nd latest epoch as justifiable, with 4th as source - add_mock_attestations(spec, state, - epoch=epoch - 2, - source=c4, - target=c2, - sufficient_support=sufficient_support) + add_mock_attestations( + spec, state, epoch=epoch - 2, source=c4, target=c2, sufficient_support=sufficient_support + ) # process! yield from run_process_just_and_fin(spec, state) @@ -153,11 +171,9 @@ def finalize_on_23(spec, state, epoch, sufficient_support): state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]() state.justification_bits[1] = 1 # mock 3rd latest epoch as justified (index is pre-shift) # mock the 2nd latest epoch as justifiable, with 3rd as source - add_mock_attestations(spec, state, - epoch=epoch - 2, - source=c3, - target=c2, - sufficient_support=sufficient_support) + add_mock_attestations( + spec, state, epoch=epoch - 2, source=c3, target=c2, sufficient_support=sufficient_support + ) # process! yield from run_process_just_and_fin(spec, state) @@ -189,17 +205,13 @@ def finalize_on_123(spec, state, epoch, sufficient_support): state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]() state.justification_bits[1] = 1 # mock 3rd latest epochs as justified (index is pre-shift) # mock the 2nd latest epoch as justifiable, with 5th as source - add_mock_attestations(spec, state, - epoch=epoch - 2, - source=c5, - target=c2, - sufficient_support=sufficient_support) + add_mock_attestations( + spec, state, epoch=epoch - 2, source=c5, target=c2, sufficient_support=sufficient_support + ) # mock the 1st latest epoch as justifiable, with 3rd as source - add_mock_attestations(spec, state, - epoch=epoch - 1, - source=c3, - target=c1, - sufficient_support=sufficient_support) + add_mock_attestations( + spec, state, epoch=epoch - 1, source=c3, target=c1, sufficient_support=sufficient_support + ) # process! yield from run_process_just_and_fin(spec, state) @@ -231,12 +243,15 @@ def finalize_on_12(spec, state, epoch, sufficient_support, messed_up_target): state.justification_bits = spec.Bitvector[spec.JUSTIFICATION_BITS_LENGTH]() state.justification_bits[0] = 1 # mock 2nd latest epoch as justified (this is pre-shift) # mock the 1st latest epoch as justifiable, with 2nd as source - add_mock_attestations(spec, state, - epoch=epoch - 1, - source=c2, - target=c1, - sufficient_support=sufficient_support, - messed_up_target=messed_up_target) + add_mock_attestations( + spec, + state, + epoch=epoch - 1, + source=c2, + target=c1, + sufficient_support=sufficient_support, + messed_up_target=messed_up_target, + ) # process! yield from run_process_just_and_fin(spec, state) @@ -330,16 +345,15 @@ def test_balance_threshold_with_exited_validators(spec, state): validator = state.validators[index] validator.exit_epoch = epoch validator.withdrawable_epoch = epoch + 1 - validator.withdrawable_epoch = validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + validator.withdrawable_epoch = ( + validator.exit_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) exited_validators = get_unslashed_exited_validators(spec, state) assert len(exited_validators) != 0 source = state.current_justified_checkpoint - target = spec.Checkpoint( - epoch=epoch, - root=spec.get_block_root(state, epoch) - ) + target = spec.Checkpoint(epoch=epoch, root=spec.get_block_root(state, epoch)) add_mock_attestations( spec, state, @@ -358,10 +372,14 @@ def test_balance_threshold_with_exited_validators(spec, state): assert not does_justify # Ensure we would have justified the current checkpoint w/ the exited validators current_exited_balance = spec.get_total_balance(state, exited_validators) - does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2 + does_justify = ( + current_target_balance + current_exited_balance + ) * 3 >= total_active_balance * 2 assert does_justify else: - current_indices = spec.get_unslashed_participating_indices(state, spec.TIMELY_TARGET_FLAG_INDEX, epoch) + current_indices = spec.get_unslashed_participating_indices( + state, spec.TIMELY_TARGET_FLAG_INDEX, epoch + ) total_active_balance = spec.get_total_active_balance(state) current_target_balance = spec.get_total_balance(state, current_indices) # Check we will not justify the current checkpoint @@ -369,7 +387,9 @@ def test_balance_threshold_with_exited_validators(spec, state): assert not does_justify # Ensure we would have justified the current checkpoint w/ the exited validators current_exited_balance = spec.get_total_balance(state, exited_validators) - does_justify = (current_target_balance + current_exited_balance) * 3 >= total_active_balance * 2 + does_justify = ( + current_target_balance + current_exited_balance + ) * 3 >= total_active_balance * 2 assert does_justify yield from run_process_just_and_fin(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py index c4d4332bc6..6dd4c7ee42 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_participation_record_updates.py @@ -1,12 +1,10 @@ from eth2spec.test.context import spec_state_test, with_phases from eth2spec.test.helpers.constants import PHASE0 -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with -) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with def run_process_participation_record_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_participation_record_updates') + yield from run_epoch_processing_with(spec, state, "process_participation_record_updates") @with_phases([PHASE0]) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_randao_mixes_reset.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_randao_mixes_reset.py index 1d35965b5f..b6c168b1fd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_randao_mixes_reset.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_randao_mixes_reset.py @@ -1,21 +1,19 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with -) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with def run_process_randao_mixes_reset(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_randao_mixes_reset') + yield from run_epoch_processing_with(spec, state, "process_randao_mixes_reset") @with_all_phases @spec_state_test def test_updated_randao_mixes(spec, state): next_epoch = spec.get_current_epoch(state) + 1 - state.randao_mixes[next_epoch % spec.EPOCHS_PER_HISTORICAL_VECTOR] = b'\x56' * 32 + state.randao_mixes[next_epoch % spec.EPOCHS_PER_HISTORICAL_VECTOR] = b"\x56" * 32 yield from run_process_randao_mixes_reset(spec, state) - assert state.randao_mixes[next_epoch % spec.EPOCHS_PER_HISTORICAL_VECTOR] == spec.get_randao_mix( - state, spec.get_current_epoch(state) - ) + assert state.randao_mixes[ + next_epoch % spec.EPOCHS_PER_HISTORICAL_VECTOR + ] == spec.get_randao_mix(state, spec.get_current_epoch(state)) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py index 6539dc92d4..871c4bf61a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_registry_updates.py @@ -1,17 +1,21 @@ -from eth2spec.test.helpers.deposits import mock_deposit -from eth2spec.test.helpers.state import next_epoch, next_slots -from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.context import ( - spec_test, spec_state_test, - with_all_phases, single_phase, - with_custom_state, with_presets, - scaled_churn_balances, + scaled_churn_balances_min_churn_limit, + single_phase, + spec_state_test, + spec_test, + with_all_phases, + with_custom_state, + with_presets, ) +from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.deposits import mock_deposit from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with +from eth2spec.test.helpers.forks import is_post_electra +from eth2spec.test.helpers.state import next_epoch, next_slots def run_process_registry_updates(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_registry_updates') + yield from run_epoch_processing_with(spec, state, "process_registry_updates") @with_all_phases @@ -30,6 +34,7 @@ def test_add_to_activation_queue(spec, state): assert state.validators[index].activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH assert state.validators[index].activation_epoch == spec.FAR_FUTURE_EPOCH assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) + assert spec.get_committee_assignment(state, spec.get_current_epoch(state), index) is None @with_all_phases @@ -55,8 +60,7 @@ def test_activation_queue_to_activated_if_finalized(spec, state): assert state.validators[index].activation_epoch != spec.FAR_FUTURE_EPOCH assert not spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) assert spec.is_active_validator( - state.validators[index], - spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + state.validators[index], spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) ) @@ -88,7 +92,7 @@ def test_activation_queue_no_activation_no_finality(spec, state): def test_activation_queue_sorting(spec, state): churn_limit = spec.get_validator_churn_limit(state) - # try to activate more than the per-epoch churn linmit + # try to activate more than the per-epoch churn limit mock_activations = churn_limit * 2 epoch = spec.get_current_epoch(state) @@ -105,17 +109,23 @@ def test_activation_queue_sorting(spec, state): yield from run_process_registry_updates(spec, state) - # the first got in as second - assert state.validators[0].activation_epoch != spec.FAR_FUTURE_EPOCH - # the prioritized got in as first - assert state.validators[mock_activations - 1].activation_epoch != spec.FAR_FUTURE_EPOCH - # the second last is at the end of the queue, and did not make the churn, - # hence is not assigned an activation_epoch yet. - assert state.validators[mock_activations - 2].activation_epoch == spec.FAR_FUTURE_EPOCH - # the one at churn_limit did not make it, it was out-prioritized - assert state.validators[churn_limit].activation_epoch == spec.FAR_FUTURE_EPOCH - # but the the one in front of the above did - assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH + if is_post_electra(spec): + # NOTE: EIP-7521 changed how activations are gated + # given the prefix setup here, all validators should be activated + activation_epochs = [state.validators[i].activation_epoch for i in range(mock_activations)] + assert all([epoch != spec.FAR_FUTURE_EPOCH for epoch in activation_epochs]) + else: + # the first got in as second + assert state.validators[0].activation_epoch != spec.FAR_FUTURE_EPOCH + # the prioritized got in as first + assert state.validators[mock_activations - 1].activation_epoch != spec.FAR_FUTURE_EPOCH + # the second last is at the end of the queue, and did not make the churn, + # hence is not assigned an activation_epoch yet. + assert state.validators[mock_activations - 2].activation_epoch == spec.FAR_FUTURE_EPOCH + # the one at churn_limit did not make it, it was out-prioritized + assert state.validators[churn_limit].activation_epoch == spec.FAR_FUTURE_EPOCH + # but the one in front of the above did + assert state.validators[churn_limit - 1].activation_epoch != spec.FAR_FUTURE_EPOCH def run_test_activation_queue_efficiency(spec, state): @@ -141,7 +151,9 @@ def run_test_activation_queue_efficiency(spec, state): # Half should churn in first run of registry update for i in range(mock_activations): - if i < churn_limit_0: + # NOTE: EIP-7251 changes how activations are gated + # given the prefix setup here, all validators are eligible for activation + if i < churn_limit_0 or is_post_electra(spec): assert state.validators[i].activation_epoch < spec.FAR_FUTURE_EPOCH else: assert state.validators[i].activation_epoch == spec.FAR_FUTURE_EPOCH @@ -161,10 +173,15 @@ def test_activation_queue_efficiency_min(spec, state): @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=scaled_churn_balances_min_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) @single_phase def test_activation_queue_efficiency_scaled(spec, state): assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT @@ -186,8 +203,7 @@ def test_ejection(spec, state): assert state.validators[index].exit_epoch != spec.FAR_FUTURE_EPOCH assert spec.is_active_validator(state.validators[index], spec.get_current_epoch(state)) assert not spec.is_active_validator( - state.validators[index], - spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + state.validators[index], spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) ) @@ -204,16 +220,32 @@ def run_test_ejection_past_churn_limit(spec, state): yield from run_process_registry_updates(spec, state) + if is_post_electra(spec): + per_epoch_churn = spec.get_activation_exit_churn_limit(state) + + def map_index_to_exit_epoch(i): + balance_so_far = i * spec.config.EJECTION_BALANCE + offset_epoch = balance_so_far // per_epoch_churn + if spec.config.EJECTION_BALANCE > per_epoch_churn - (balance_so_far % per_epoch_churn): + offset_epoch += 1 + return expected_ejection_epoch + offset_epoch + + else: + + def map_index_to_exit_epoch(i): + # first third ejected in normal speed + if i < mock_ejections // 3: + return expected_ejection_epoch + # second third gets delayed by 1 epoch + elif mock_ejections // 3 <= i < mock_ejections * 2 // 3: + return expected_ejection_epoch + 1 + # final third gets delayed by 2 epochs + else: + return expected_ejection_epoch + 2 + for i in range(mock_ejections): - # first third ejected in normal speed - if i < mock_ejections // 3: - assert state.validators[i].exit_epoch == expected_ejection_epoch - # second third gets delayed by 1 epoch - elif mock_ejections // 3 <= i < mock_ejections * 2 // 3: - assert state.validators[i].exit_epoch == expected_ejection_epoch + 1 - # final third gets delayed by 2 epochs - else: - assert state.validators[i].exit_epoch == expected_ejection_epoch + 2 + target_exit_epoch = map_index_to_exit_epoch(i) + assert state.validators[i].exit_epoch == target_exit_epoch @with_all_phases @@ -224,10 +256,15 @@ def test_ejection_past_churn_limit_min(spec, state): @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=scaled_churn_balances_min_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) @single_phase def test_ejection_past_churn_limit_scaled(spec, state): assert spec.get_validator_churn_limit(state) > spec.config.MIN_PER_EPOCH_CHURN_LIMIT @@ -241,17 +278,23 @@ def run_test_activation_queue_activation_and_ejection(spec, state, num_per_statu # ready for entrance into activation queue activation_queue_start_index = 0 - activation_queue_indices = list(range(activation_queue_start_index, activation_queue_start_index + num_per_status)) + activation_queue_indices = list( + range(activation_queue_start_index, activation_queue_start_index + num_per_status) + ) for validator_index in activation_queue_indices: mock_deposit(spec, state, validator_index) # ready for activation state.finalized_checkpoint.epoch = spec.get_current_epoch(state) - 1 activation_start_index = num_per_status - activation_indices = list(range(activation_start_index, activation_start_index + num_per_status)) + activation_indices = list( + range(activation_start_index, activation_start_index + num_per_status) + ) for validator_index in activation_indices: mock_deposit(spec, state, validator_index) - state.validators[validator_index].activation_eligibility_epoch = state.finalized_checkpoint.epoch + state.validators[ + validator_index + ].activation_eligibility_epoch = state.finalized_checkpoint.epoch # ready for ejection ejection_start_index = num_per_status * 2 @@ -276,15 +319,17 @@ def run_test_activation_queue_activation_and_ejection(spec, state, num_per_statu assert validator.activation_epoch != spec.FAR_FUTURE_EPOCH assert not spec.is_active_validator(validator, spec.get_current_epoch(state)) assert spec.is_active_validator( - validator, - spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + validator, spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) ) # any remaining validators do not exit the activation queue for validator_index in activation_indices[churn_limit:]: validator = state.validators[validator_index] assert validator.activation_eligibility_epoch != spec.FAR_FUTURE_EPOCH - assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH + # NOTE: activations are gated differently after EIP-7251 + # all eligible validators were activated, regardless of churn limit + if not is_post_electra(spec): + assert validator.activation_epoch == spec.FAR_FUTURE_EPOCH # all ejection balance validators ejected for a future epoch for i, validator_index in enumerate(ejection_indices): @@ -294,7 +339,7 @@ def run_test_activation_queue_activation_and_ejection(spec, state, num_per_statu queue_offset = i // churn_limit assert not spec.is_active_validator( validator, - spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + queue_offset + spec.compute_activation_exit_epoch(spec.get_current_epoch(state)) + queue_offset, ) @@ -321,10 +366,15 @@ def test_activation_queue_activation_and_ejection__exceed_churn_limit(spec, stat @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=scaled_churn_balances_min_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) @single_phase def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, state): churn_limit = spec.get_validator_churn_limit(state) @@ -333,12 +383,49 @@ def test_activation_queue_activation_and_ejection__scaled_churn_limit(spec, stat @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=scaled_churn_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=scaled_churn_balances_min_churn_limit, + threshold_fn=lambda spec: spec.config.EJECTION_BALANCE, +) @single_phase def test_activation_queue_activation_and_ejection__exceed_scaled_churn_limit(spec, state): churn_limit = spec.get_validator_churn_limit(state) assert churn_limit > spec.config.MIN_PER_EPOCH_CHURN_LIMIT yield from run_test_activation_queue_activation_and_ejection(spec, state, churn_limit * 2) + + +@with_all_phases +@spec_state_test +def test_invalid_large_withdrawable_epoch(spec, state): + """ + This test forces a validator into a withdrawable epoch that overflows the + epoch (uint64) type. To do this we need two validators, one validator that + already has an exit epoch and another with a low effective balance. When + calculating the withdrawable epoch for the second validator, it will + use the greatest exit epoch of all of the validators. If the first + validator is given an exit epoch between + (FAR_FUTURE_EPOCH-MIN_VALIDATOR_WITHDRAWABILITY_DELAY+1) and + (FAR_FUTURE_EPOCH-1), it will cause an overflow. + """ + assert spec.is_active_validator(state.validators[0], spec.get_current_epoch(state)) + assert spec.is_active_validator(state.validators[1], spec.get_current_epoch(state)) + + exit_epoch = spec.FAR_FUTURE_EPOCH - 1 + state.validators[0].exit_epoch = exit_epoch + state.validators[1].effective_balance = spec.config.EJECTION_BALANCE + + if is_post_electra(spec): + state.earliest_exit_epoch = exit_epoch + + try: + yield from run_process_registry_updates(spec, state) + except ValueError: + yield "post", None + return + + raise AssertionError("expected ValueError") diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 7ff4e83d3b..119d026006 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,30 +1,37 @@ +from random import Random + from eth2spec.test.context import ( - spec_state_test, spec_test, - with_all_phases, single_phase, - with_phases, PHASE0, + low_single_balance, + misc_balances, + PHASE0, + single_phase, + spec_state_test, + spec_test, + with_all_phases, with_custom_state, + with_phases, zero_activation_threshold, - misc_balances, low_single_balance, - is_post_altair, -) -from eth2spec.test.helpers.state import ( - next_epoch, - next_slot, ) from eth2spec.test.helpers.attestations import ( add_attestations_to_state, get_valid_attestation, - sign_attestation, prepare_state_with_attestations, + sign_attestation, ) -from eth2spec.test.helpers.rewards import leaking from eth2spec.test.helpers.attester_slashings import get_indexed_attestation_participants from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with -from random import Random +from eth2spec.test.helpers.forks import ( + is_post_altair, +) +from eth2spec.test.helpers.rewards import leaking +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, +) def run_process_rewards_and_penalties(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_rewards_and_penalties') + yield from run_epoch_processing_with(spec, state, "process_rewards_and_penalties") def validate_resulting_balances(spec, pre_state, post_state, attestations): @@ -45,23 +52,20 @@ def validate_resulting_balances(spec, pre_state, post_state, attestations): assert post_state.balances[index] == pre_state.balances[index] else: assert post_state.balances[index] < pre_state.balances[index] + elif index in attesting_indices: + assert post_state.balances[index] > pre_state.balances[index] else: - if index in attesting_indices: - assert post_state.balances[index] > pre_state.balances[index] - else: - assert post_state.balances[index] < pre_state.balances[index] - else: - if spec.is_in_inactivity_leak(post_state): - if index in attesting_indices: - # If not proposer but participated optimally, should have exactly neutral balance - assert post_state.balances[index] == pre_state.balances[index] - else: - assert post_state.balances[index] < pre_state.balances[index] + assert post_state.balances[index] < pre_state.balances[index] + elif spec.is_in_inactivity_leak(post_state): + if index in attesting_indices: + # If not proposer but participated optimally, should have exactly neutral balance + assert post_state.balances[index] == pre_state.balances[index] else: - if index in attesting_indices: - assert post_state.balances[index] > pre_state.balances[index] - else: - assert post_state.balances[index] < pre_state.balances[index] + assert post_state.balances[index] < pre_state.balances[index] + elif index in attesting_indices: + assert post_state.balances[index] > pre_state.balances[index] + else: + assert post_state.balances[index] < pre_state.balances[index] @with_all_phases @@ -110,10 +114,10 @@ def test_full_attestations_random_incorrect_fields(spec, state): for i, attestation in enumerate(state.previous_epoch_attestations): if i % 3 == 0: # Mess up some head votes - attestation.data.beacon_block_root = b'\x56' * 32 + attestation.data.beacon_block_root = b"\x56" * 32 if i % 3 == 1: # Message up some target votes - attestation.data.target.root = b'\x23' * 32 + attestation.data.target.root = b"\x23" * 32 if i % 3 == 2: # Keep some votes 100% correct pass @@ -128,7 +132,9 @@ def test_full_attestations_random_incorrect_fields(spec, state): @with_all_phases @spec_test -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.MAX_EFFECTIVE_BALANCE // 2) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.MAX_EFFECTIVE_BALANCE // 2 +) @single_phase def test_full_attestations_misc_balances(spec, state): attestations = prepare_state_with_attestations(spec, state) @@ -153,7 +159,7 @@ def test_full_attestations_misc_balances(spec, state): @spec_test @with_custom_state(balances_fn=low_single_balance, threshold_fn=zero_activation_threshold) @single_phase -def test_full_attestations_one_validaor_one_gwei(spec, state): +def test_full_attestations_one_validator_one_gwei(spec, state): attestations = prepare_state_with_attestations(spec, state) yield from run_process_rewards_and_penalties(spec, state) @@ -185,7 +191,9 @@ def participation_tracker(slot, comm_index, comm): participated.update(att_participants) return att_participants - attestations = prepare_state_with_attestations(spec, state, participation_fn=participation_tracker) + attestations = prepare_state_with_attestations( + spec, state, participation_fn=participation_tracker + ) pre_state = state.copy() yield from run_process_rewards_and_penalties(spec, state) @@ -200,7 +208,11 @@ def participation_tracker(slot, comm_index, comm): @spec_state_test def test_almost_empty_attestations(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), 1) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -208,14 +220,22 @@ def test_almost_empty_attestations(spec, state): @leaking() def test_almost_empty_attestations_with_leak(spec, state): rng = Random(1234) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), 1) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @spec_state_test def test_random_fill_attestations(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) // 3) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -223,14 +243,22 @@ def test_random_fill_attestations(spec, state): @leaking() def test_random_fill_attestations_with_leak(spec, state): rng = Random(4567) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) // 3)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) // 3) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @spec_state_test def test_almost_full_attestations(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) - 1) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -238,7 +266,11 @@ def test_almost_full_attestations(spec, state): @leaking() def test_almost_full_attestations_with_leak(spec, state): rng = Random(8901) - yield from run_with_participation(spec, state, lambda slot, comm_index, comm: rng.sample(comm, len(comm) - 1)) + + def participation_fn(slot, comm_index, comm): + return rng.sample(sorted(comm), len(comm) - 1) + + yield from run_with_participation(spec, state, participation_fn) @with_all_phases @@ -302,7 +334,7 @@ def test_duplicate_participants_different_attestation_1(spec, state): """ correct_attestation = get_valid_attestation(spec, state, signed=True) incorrect_attestation = correct_attestation.copy() - incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + incorrect_attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, incorrect_attestation) indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) @@ -315,7 +347,9 @@ def test_duplicate_participants_different_attestation_1(spec, state): inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot) - add_attestations_to_state(spec, dup_state, [correct_attestation, incorrect_attestation], inclusion_slot) + add_attestations_to_state( + spec, dup_state, [correct_attestation, incorrect_attestation], inclusion_slot + ) next_epoch(spec, single_correct_state) next_epoch(spec, dup_state) @@ -342,7 +376,7 @@ def test_duplicate_participants_different_attestation_2(spec, state): """ correct_attestation = get_valid_attestation(spec, state, signed=True) incorrect_attestation = correct_attestation.copy() - incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + incorrect_attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, incorrect_attestation) indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) @@ -355,7 +389,9 @@ def test_duplicate_participants_different_attestation_2(spec, state): inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot) - add_attestations_to_state(spec, dup_state, [incorrect_attestation, correct_attestation], inclusion_slot) + add_attestations_to_state( + spec, dup_state, [incorrect_attestation, correct_attestation], inclusion_slot + ) next_epoch(spec, single_correct_state) next_epoch(spec, dup_state) @@ -383,7 +419,7 @@ def test_duplicate_participants_different_attestation_3(spec, state): """ correct_attestation = get_valid_attestation(spec, state, signed=True) incorrect_attestation = correct_attestation.copy() - incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + incorrect_attestation.data.beacon_block_root = b"\x42" * 32 sign_attestation(spec, state, incorrect_attestation) indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) @@ -420,7 +456,9 @@ def test_duplicate_participants_different_attestation_3(spec, state): # Case when some eligible attestations are slashed. Modifies attesting_balance and consequently rewards/penalties. def test_attestations_some_slashed(spec, state): attestations = prepare_state_with_attestations(spec, state) - attesting_indices_before_slashings = list(spec.get_unslashed_attesting_indices(state, attestations)) + attesting_indices_before_slashings = list( + spec.get_unslashed_attesting_indices(state, attestations) + ) # Slash maximum amount of validators allowed per epoch. for i in range(spec.config.MIN_PER_EPOCH_CHURN_LIMIT): @@ -435,5 +473,8 @@ def test_attestations_some_slashed(spec, state): attesting_indices = spec.get_unslashed_attesting_indices(state, attestations) assert len(attesting_indices) > 0 - assert len(attesting_indices_before_slashings) - len(attesting_indices) == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + assert ( + len(attesting_indices_before_slashings) - len(attesting_indices) + == spec.config.MIN_PER_EPOCH_CHURN_LIMIT + ) validate_resulting_balances(spec, pre_state, state, attestations) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 1b977640d5..7ffc355aff 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -1,16 +1,22 @@ from random import Random -from eth2spec.test.context import spec_state_test, with_all_phases, is_post_altair + +from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with, run_epoch_processing_to + run_epoch_processing_to, + run_epoch_processing_with, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_bellatrix, + is_post_electra, ) from eth2spec.test.helpers.random import randomize_state -from eth2spec.test.helpers.state import has_active_balance_differential +from eth2spec.test.helpers.state import has_active_balance_differential, next_epoch from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators -from eth2spec.test.helpers.state import next_epoch def run_process_slashings(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_slashings') + yield from run_epoch_processing_with(spec, state, "process_slashings") def slash_validators(spec, state, indices, out_epochs): @@ -22,27 +28,48 @@ def slash_validators(spec, state, indices, out_epochs): v.withdrawable_epoch = out_epoch total_slashed_balance += v.effective_balance - state.slashings[ - spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR - ] = total_slashed_balance + state.slashings[spec.get_current_epoch(state) % spec.EPOCHS_PER_SLASHINGS_VECTOR] = ( + total_slashed_balance + ) # verify some slashings happened... assert total_slashed_balance != 0 def get_slashing_multiplier(spec): - if is_post_altair(spec): + if is_post_bellatrix(spec): + return spec.PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX + elif is_post_altair(spec): return spec.PROPORTIONAL_SLASHING_MULTIPLIER_ALTAIR else: return spec.PROPORTIONAL_SLASHING_MULTIPLIER +def _compute_expected_correlation_penalty( + spec, effective_balance, total_slashed_balance, total_balance +): + if is_post_electra(spec): + return ( + (get_slashing_multiplier(spec) * total_slashed_balance) + // (total_balance // spec.EFFECTIVE_BALANCE_INCREMENT) + * (effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT) + ) + else: + return ( + effective_balance + // spec.EFFECTIVE_BALANCE_INCREMENT + * (get_slashing_multiplier(spec) * total_slashed_balance) + // total_balance + * spec.EFFECTIVE_BALANCE_INCREMENT + ) + + def _setup_process_slashings_test(spec, state, not_slashable_set=set()): # Slashed count to ensure that enough validators are slashed to induce maximum penalties slashed_count = min( (len(state.validators) // get_slashing_multiplier(spec)) + 1, # Can't slash more than validator count! - len(state.validators) + len(state.validators), ) out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2) @@ -96,7 +123,9 @@ def test_minimal_penalty(spec, state): # # Just the bare minimum for this one validator - state.balances[0] = state.validators[0].effective_balance = spec.config.EJECTION_BALANCE + state.balances[0] = state.validators[0].effective_balance = ( + spec.config.EJECTION_BALANCE + spec.EFFECTIVE_BALANCE_INCREMENT + ) # All the other validators get the maximum. for i in range(1, len(state.validators)): state.validators[i].effective_balance = state.balances[i] = spec.MAX_EFFECTIVE_BALANCE @@ -110,21 +139,17 @@ def test_minimal_penalty(spec, state): assert total_balance // 3 > total_penalties - run_epoch_processing_to(spec, state, 'process_slashings') + run_epoch_processing_to(spec, state, "process_slashings") pre_slash_balances = list(state.balances) - yield 'pre', state + yield "pre", state spec.process_slashings(state) - yield 'post', state + yield "post", state - expected_penalty = ( - state.validators[0].effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT - * (get_slashing_multiplier(spec) * total_penalties) - // total_balance - * spec.EFFECTIVE_BALANCE_INCREMENT + expected_penalty = _compute_expected_correlation_penalty( + spec, state.validators[0].effective_balance, total_penalties, total_balance ) - assert expected_penalty == 0 - assert state.balances[0] == pre_slash_balances[0] + assert state.balances[0] == pre_slash_balances[0] - expected_penalty @with_all_phases @@ -165,24 +190,21 @@ def test_scaled_penalties(spec, state): # Process up to the sub-transition, then Hi-jack and get the balances. # We just want to test the slashings. # But we are not interested in the other balance changes during the same epoch transition. - run_epoch_processing_to(spec, state, 'process_slashings') + run_epoch_processing_to(spec, state, "process_slashings") pre_slash_balances = list(state.balances) slash_validators(spec, state, slashed_indices, [out_epoch] * slashed_count) - yield 'pre', state + yield "pre", state spec.process_slashings(state) - yield 'post', state + yield "post", state total_penalties = sum(state.slashings) for i in slashed_indices: v = state.validators[i] - expected_penalty = ( - v.effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT - * (get_slashing_multiplier(spec) * total_penalties) - // (total_balance) - * spec.EFFECTIVE_BALANCE_INCREMENT + expected_penalty = _compute_expected_correlation_penalty( + spec, v.effective_balance, total_penalties, total_balance ) assert state.balances[i] == pre_slash_balances[i] - expected_penalty @@ -199,7 +221,9 @@ def test_slashings_with_random_state(spec, state): assert len(target_validators) != 0 assert has_active_balance_differential(spec, state) - slashed_indices = _setup_process_slashings_test(spec, state, not_slashable_set=target_validators) + slashed_indices = _setup_process_slashings_test( + spec, state, not_slashable_set=target_validators + ) # ensure no accidental slashings of protected set... current_target_validators = get_unslashed_exited_validators(spec, state) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings_reset.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings_reset.py index 24c350b251..42960252ce 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings_reset.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings_reset.py @@ -1,11 +1,9 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.epoch_processing import ( - run_epoch_processing_with -) +from eth2spec.test.helpers.epoch_processing import run_epoch_processing_with def run_process_slashings_reset(spec, state): - yield from run_epoch_processing_with(spec, state, 'process_slashings_reset') + yield from run_epoch_processing_with(spec, state, "process_slashings_reset") @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py index c414f645e0..e7c962a1ce 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py +++ b/tests/core/pyspec/eth2spec/test/phase0/finality/test_finality.py @@ -1,23 +1,35 @@ from eth2spec.test.context import spec_state_test, with_all_phases -from eth2spec.test.helpers.state import next_epoch_via_block from eth2spec.test.helpers.attestations import next_epoch_with_attestations +from eth2spec.test.helpers.state import next_epoch_via_block -def check_finality(spec, - state, - prev_state, - current_justified_changed, - previous_justified_changed, - finalized_changed): +def check_finality( + spec, + state, + prev_state, + current_justified_changed, + previous_justified_changed, + finalized_changed, +): if current_justified_changed: - assert state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch - assert state.current_justified_checkpoint.root != prev_state.current_justified_checkpoint.root + assert ( + state.current_justified_checkpoint.epoch > prev_state.current_justified_checkpoint.epoch + ) + assert ( + state.current_justified_checkpoint.root != prev_state.current_justified_checkpoint.root + ) else: assert state.current_justified_checkpoint == prev_state.current_justified_checkpoint if previous_justified_changed: - assert state.previous_justified_checkpoint.epoch > prev_state.previous_justified_checkpoint.epoch - assert state.previous_justified_checkpoint.root != prev_state.previous_justified_checkpoint.root + assert ( + state.previous_justified_checkpoint.epoch + > prev_state.previous_justified_checkpoint.epoch + ) + assert ( + state.previous_justified_checkpoint.root + != prev_state.previous_justified_checkpoint.root + ) else: assert state.previous_justified_checkpoint == prev_state.previous_justified_checkpoint @@ -33,7 +45,7 @@ def check_finality(spec, def test_finality_no_updates_at_genesis(spec, state): assert spec.get_current_epoch(state) == spec.GENESIS_EPOCH - yield 'pre', state + yield "pre", state blocks = [] for epoch in range(2): @@ -47,8 +59,8 @@ def test_finality_no_updates_at_genesis(spec, state): elif epoch == 1: check_finality(spec, state, prev_state, False, False, False) - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state @with_all_phases @@ -58,7 +70,7 @@ def test_finality_rule_4(spec, state): next_epoch_via_block(spec, state) next_epoch_via_block(spec, state) - yield 'pre', state + yield "pre", state blocks = [] for epoch in range(2): @@ -72,8 +84,8 @@ def test_finality_rule_4(spec, state): check_finality(spec, state, prev_state, True, True, True) assert state.finalized_checkpoint == prev_state.current_justified_checkpoint - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state @with_all_phases @@ -83,7 +95,7 @@ def test_finality_rule_1(spec, state): next_epoch_via_block(spec, state) next_epoch_via_block(spec, state) - yield 'pre', state + yield "pre", state blocks = [] for epoch in range(3): @@ -99,8 +111,8 @@ def test_finality_rule_1(spec, state): check_finality(spec, state, prev_state, True, True, True) assert state.finalized_checkpoint == prev_state.previous_justified_checkpoint - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state @with_all_phases @@ -110,7 +122,7 @@ def test_finality_rule_2(spec, state): next_epoch_via_block(spec, state) next_epoch_via_block(spec, state) - yield 'pre', state + yield "pre", state blocks = [] for epoch in range(3): @@ -128,8 +140,8 @@ def test_finality_rule_2(spec, state): blocks += new_blocks - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state @with_all_phases @@ -137,13 +149,13 @@ def test_finality_rule_2(spec, state): def test_finality_rule_3(spec, state): """ Test scenario described here - https://github.com/ethereum/eth2.0-specs/issues/611#issuecomment-463612892 + https://github.com/ethereum/consensus-specs/issues/611#issuecomment-463612892 """ # get past first two epochs that finality does not run on next_epoch_via_block(spec, state) next_epoch_via_block(spec, state) - yield 'pre', state + yield "pre", state blocks = [] prev_state, new_blocks, state = next_epoch_with_attestations(spec, state, True, False) @@ -174,5 +186,5 @@ def test_finality_rule_3(spec, state): check_finality(spec, state, prev_state, True, True, True) assert state.finalized_checkpoint == prev_state.current_justified_checkpoint - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py new file mode 100644 index 0000000000..67b85b0522 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_ex_ante.py @@ -0,0 +1,472 @@ +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, + with_altair_and_later, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, + sign_attestation, +) +from eth2spec.test.helpers.block import ( + build_empty_block, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + EIP7732, + MAINNET, +) +from eth2spec.test.helpers.fork_choice import ( + add_attestation, + add_block, + check_head_against_root, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + tick_and_add_block, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.state import ( + payload_state_transition, + state_transition_and_sign_block, +) + + +def _apply_base_block_a(spec, state, store, test_steps): + # On receiving block A at slot `N` + block = build_empty_block(spec, state, slot=state.slot + 1) + signed_block_a = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block_a, test_steps) + payload_state_transition(spec, store, signed_block_a.message) + head = spec.get_head(store) + expected_root = signed_block_a.message.hash_tree_root() + if is_post_eip7732(spec): + assert head.root == expected_root + else: + check_head_against_root(spec, store, signed_block_a.message.hash_tree_root()) + + +@with_altair_and_later +@spec_state_test +def test_ex_ante_vanilla(spec, state): + """ + With a single adversarial attestation + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_1 (Block B); size `1` - slot N+1 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+2 — C is head + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Attestation_1 at slot `N + 1` voting for block B + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, + state_b, + slot=state_b.slot, + signed=False, + filter_participant_set=_filter_participant_set, + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_b, attestation) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Block B received at N+2 — C is head due to proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_b.message) + + # Attestation_1 received at N+2 — C is head + yield from add_attestation(spec, store, attestation, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + + yield "steps", test_steps + + +def _get_greater_than_proposer_boost_score(spec, store, state, proposer_boost_root, root): + """ + Return the minimum attestation participant count such that attestation_score > proposer_score + """ + # calculate proposer boost score + block = store.blocks[root] + proposer_score = 0 + if spec.get_ancestor(store, root, block.slot) == proposer_boost_root: + num_validators = len( + spec.get_active_validator_indices(state, spec.get_current_epoch(state)) + ) + avg_balance = spec.get_total_active_balance(state) // num_validators + committee_size = num_validators // spec.SLOTS_PER_EPOCH + committee_weight = committee_size * avg_balance + proposer_score = (committee_weight * spec.config.PROPOSER_SCORE_BOOST) // 100 + + # calculate minimum participant count such that attestation_score > proposer_score + base_effective_balance = state.validators[0].effective_balance + + return proposer_score // base_effective_balance + 1 + + +# TODO(jtraglia): Investigate why this doesn't work with eip7732 +@with_all_phases_from_except(ALTAIR, [EIP7732]) +@with_presets([MAINNET], reason="to create non-duplicate committee") +@spec_state_test +def test_ex_ante_attestations_is_greater_than_proposer_boost_with_boost(spec, state): + """ + Adversarial attestations > proposer boost + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Attestation_set_1 (Block B); size `proposer_boost + 1` - slot N+1 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+2 — B is head + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Block B received at N+2 — C is head due to proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Attestation_set_1 at slot `N + 1` voting for block B + proposer_boost_root = signed_block_b.message.hash_tree_root() + root = signed_block_b.message.hash_tree_root() + participant_num = _get_greater_than_proposer_boost_score( + spec, store, state, proposer_boost_root, root + ) + + def _filter_participant_set(participants): + return [index for i, index in enumerate(participants) if i < participant_num] + + attestation = get_valid_attestation( + spec, + state_b, + slot=state_b.slot, + signed=False, + filter_participant_set=_filter_participant_set, + ) + attestation.data.beacon_block_root = signed_block_b.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num + sign_attestation(spec, state_b, attestation) + + # Attestation_set_1 received at N+2 — B is head because B's attestation_score > C's proposer_score. + # (B's proposer_score = C's attestation_score = 0) + yield from add_attestation(spec, store, attestation, test_steps) + check_head_against_root(spec, store, signed_block_b.message.hash_tree_root()) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_ex_ante_sandwich_without_attestations(spec, state): + """ + Simple Sandwich test with boost and no attestations. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head (with boost) + Block D received at N+3 — D is head (with boost) + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_b.message) + + # Block D received at N+3 - D is head, it has proposer score boost + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_d, test_steps) + check_head_against_root(spec, store, signed_block_d.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_d.message) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_ex_ante_sandwich_with_honest_attestation(spec, state): + """ + Boosting necessary to sandwich attack. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Attestation_1 (Block C); size 1 - slot N+2 (honest) + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_1 received at N+3 — C is head + Block D received at N+3 — D is head + + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Attestation_1 at N+2 voting for block C + def _filter_participant_set(participants): + return [next(iter(participants))] + + attestation = get_valid_attestation( + spec, + state_c, + slot=state_c.slot, + signed=False, + filter_participant_set=_filter_participant_set, + ) + attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == 1 + sign_attestation(spec, state_c, attestation) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_b.message) + + # Attestation_1 received at N+3 — C is head + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_attestation(spec, store, attestation, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + + # Block D received at N+3 - D is head, it has proposer score boost + yield from add_block(spec, store, signed_block_d, test_steps) + check_head_against_root(spec, store, signed_block_d.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_d.message) + + yield "steps", test_steps + + +# TODO(jtraglia): Investigate why this doesn't work with eip7732 +@with_all_phases_from_except(ALTAIR, [EIP7732]) +@with_presets([MAINNET], reason="to create non-duplicate committee") +@spec_state_test +def test_ex_ante_sandwich_with_boost_not_sufficient(spec, state): + """ + Boost not sufficient to sandwich attack. + Objects: + Block A - slot N + Block B (parent A) - slot N+1 + Block C (parent A) - slot N+2 + Block D (parent B) - slot N+3 + Attestation_set_1 (Block C); size proposer_boost + 1 - slot N+2 + Steps: + Block A received at N — A is head + Block C received at N+2 — C is head + Block B received at N+2 — C is head + Attestation_set_1 received — C is head + Block D received at N+3 — C is head + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving block A at slot `N` + yield from _apply_base_block_a(spec, state, store, test_steps) + state_a = state.copy() + + # Block B at slot `N + 1`, parent is A + state_b = state_a.copy() + block = build_empty_block(spec, state_a, slot=state_a.slot + 1) + signed_block_b = state_transition_and_sign_block(spec, state_b, block) + + # Block C at slot `N + 2`, parent is A + state_c = state_a.copy() + block = build_empty_block(spec, state_c, slot=state_a.slot + 2) + signed_block_c = state_transition_and_sign_block(spec, state_c, block) + + # Block D at slot `N + 3`, parent is B + state_d = state_b.copy() + block = build_empty_block(spec, state_d, slot=state_a.slot + 3) + signed_block_d = state_transition_and_sign_block(spec, state_d, block) + + # Block C received at N+2 — C is head + time = state_c.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_c, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_c.message) + + # Block B received at N+2 — C is head, it has proposer score boost + yield from add_block(spec, store, signed_block_b, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_b.message) + + # Attestation_set_1 at N+2 voting for block C + proposer_boost_root = signed_block_c.message.hash_tree_root() + root = signed_block_c.message.hash_tree_root() + participant_num = _get_greater_than_proposer_boost_score( + spec, store, state, proposer_boost_root, root + ) + + def _filter_participant_set(participants): + return [index for i, index in enumerate(participants) if i < participant_num] + + attestation = get_valid_attestation( + spec, + state_c, + slot=state_c.slot, + signed=False, + filter_participant_set=_filter_participant_set, + ) + attestation.data.beacon_block_root = signed_block_c.message.hash_tree_root() + assert len([i for i in attestation.aggregation_bits if i == 1]) == participant_num + sign_attestation(spec, state_c, attestation) + + # Attestation_1 received at N+3 — B is head because B's attestation_score > C's proposer_score. + # (B's proposer_score = C's attestation_score = 0) + time = state_d.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_attestation(spec, store, attestation, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + + # Block D received at N+3 - C is head, D's boost not sufficient! + yield from add_block(spec, store, signed_block_d, test_steps) + check_head_against_root(spec, store, signed_block_c.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block_d.message) + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py index 12b261e4e5..cefc885ab0 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_head.py @@ -1,91 +1,109 @@ -from eth_utils import encode_hex +import random from eth2spec.test.context import ( - is_post_altair, spec_state_test, - with_all_phases, + with_all_phases_from_except, + with_altair_and_later, with_presets, ) from eth2spec.test.helpers.attestations import get_valid_attestation, next_epoch_with_attestations -from eth2spec.test.helpers.block import build_empty_block_for_next_slot -from eth2spec.test.helpers.constants import MINIMAL +from eth2spec.test.helpers.block import ( + apply_empty_block, + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + EIP7732, + MINIMAL, +) from eth2spec.test.helpers.fork_choice import ( - tick_and_run_on_attestation, - tick_and_add_block, + add_attestation, + add_attester_slashing, + add_block, + apply_next_epoch_with_attestations, + check_head_against_root, get_anchor_root, - get_genesis_forkchoice_store_and_block, get_formatted_head_output, + get_genesis_forkchoice_store_and_block, on_tick_and_append_step, - add_block, + output_head_check, + tick_and_add_block, + tick_and_run_on_attestation, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_eip7732, ) from eth2spec.test.helpers.state import ( next_epoch, + next_slots, + payload_state_transition, state_transition_and_sign_block, ) -@with_all_phases +@with_altair_and_later @spec_state_test def test_genesis(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block anchor_root = get_anchor_root(spec, state) - assert spec.get_head(store) == anchor_root - test_steps.append({ - 'checks': { - 'genesis_time': int(store.genesis_time), - 'head': get_formatted_head_output(spec, store), + check_head_against_root(spec, store, anchor_root) + + test_steps.append( + { + "checks": { + "genesis_time": int(store.genesis_time), + "head": get_formatted_head_output(spec, store), + } } - }) + ) - yield 'steps', test_steps + yield "steps", test_steps if is_post_altair(spec): - yield 'description', 'meta', f"Although it's not phase 0, we may use {spec.fork} spec to start testnets." + yield ( + "description", + "meta", + f"Although it's not phase 0, we may use {spec.fork} spec to start testnets.", + ) -@with_all_phases +@with_altair_and_later @spec_state_test def test_chain_no_attestations(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block anchor_root = get_anchor_root(spec, state) - assert spec.get_head(store) == anchor_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) # On receiving a block of `GENESIS_SLOT + 1` slot block_1 = build_empty_block_for_next_slot(spec, state) signed_block_1 = state_transition_and_sign_block(spec, state, block_1) yield from tick_and_add_block(spec, store, signed_block_1, test_steps) + payload_state_transition(spec, store, signed_block_1.message) # On receiving a block of next epoch block_2 = build_empty_block_for_next_slot(spec, state) signed_block_2 = state_transition_and_sign_block(spec, state, block_2) yield from tick_and_add_block(spec, store, signed_block_2, test_steps) + check_head_against_root(spec, store, spec.hash_tree_root(block_2)) + payload_state_transition(spec, store, signed_block_2.message) + output_head_check(spec, store, test_steps) - assert spec.get_head(store) == spec.hash_tree_root(block_2) - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) - - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test def test_split_tie_breaker_no_attestations(spec, state): test_steps = [] @@ -93,41 +111,40 @@ def test_split_tie_breaker_no_attestations(spec, state): # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block anchor_root = get_anchor_root(spec, state) - assert spec.get_head(store) == anchor_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) - # block at slot 1 + # Create block at slot 1 block_1_state = genesis_state.copy() block_1 = build_empty_block_for_next_slot(spec, block_1_state) signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1) - yield from tick_and_add_block(spec, store, signed_block_1, test_steps) - # additional block at slot 1 + # Create additional block at slot 1 block_2_state = genesis_state.copy() block_2 = build_empty_block_for_next_slot(spec, block_2_state) - block_2.body.graffiti = b'\x42' * 32 + block_2.body.graffiti = b"\x42" * 32 signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2) - yield from tick_and_add_block(spec, store, signed_block_2, test_steps) + + # Tick time past slot 1 so proposer score boost does not apply + time = store.genesis_time + (block_2.slot + 1) * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + + yield from add_block(spec, store, signed_block_1, test_steps) + payload_state_transition(spec, store, signed_block_1.message) + yield from add_block(spec, store, signed_block_2, test_steps) + payload_state_transition(spec, store, signed_block_2.message) highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2)) - assert spec.get_head(store) == highest_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, highest_root) + output_head_check(spec, store, test_steps) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test def test_shorter_chain_but_heavier_weight(spec, state): test_steps = [] @@ -135,15 +152,11 @@ def test_shorter_chain_but_heavier_weight(spec, state): # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block anchor_root = get_anchor_root(spec, state) - assert spec.get_head(store) == anchor_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) # build longer tree long_state = genesis_state.copy() @@ -151,43 +164,40 @@ def test_shorter_chain_but_heavier_weight(spec, state): long_block = build_empty_block_for_next_slot(spec, long_state) signed_long_block = state_transition_and_sign_block(spec, long_state, long_block) yield from tick_and_add_block(spec, store, signed_long_block, test_steps) + payload_state_transition(spec, store, signed_long_block.message) # build short tree short_state = genesis_state.copy() short_block = build_empty_block_for_next_slot(spec, short_state) - short_block.body.graffiti = b'\x42' * 32 + short_block.body.graffiti = b"\x42" * 32 signed_short_block = state_transition_and_sign_block(spec, short_state, short_block) yield from tick_and_add_block(spec, store, signed_short_block, test_steps) + payload_state_transition(spec, store, signed_short_block.message) + + # Since the long chain has higher proposer_score at slot 1, the latest long block is the head + check_head_against_root(spec, store, spec.hash_tree_root(long_block)) short_attestation = get_valid_attestation(spec, short_state, short_block.slot, signed=True) yield from tick_and_run_on_attestation(spec, store, short_attestation, test_steps) - assert spec.get_head(store) == spec.hash_tree_root(short_block) - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, spec.hash_tree_root(short_block)) + output_head_check(spec, store, test_steps) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_filtered_block_tree(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block anchor_root = get_anchor_root(spec, state) - assert spec.get_head(store) == anchor_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - } - }) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) # transition state past initial couple of epochs next_epoch(spec, state) @@ -201,19 +211,14 @@ def test_filtered_block_tree(spec, state): on_tick_and_append_step(spec, store, current_time, test_steps) for signed_block in signed_blocks: yield from add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) assert store.justified_checkpoint == state.current_justified_checkpoint # the last block in the branch should be the head expected_head_root = spec.hash_tree_root(signed_blocks[-1].message) - assert spec.get_head(store) == expected_head_root - - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store), - 'justified_checkpoint_root': encode_hex(store.justified_checkpoint.root), - } - }) + check_head_against_root(spec, store, expected_head_root) + output_head_check(spec, store, test_steps) # # create branch containing the justified block but not containing enough on @@ -221,7 +226,10 @@ def test_filtered_block_tree(spec, state): # # build a chain without attestations off of previous justified block - non_viable_state = store.block_states[store.justified_checkpoint.root].copy() + if is_post_eip7732(spec): + non_viable_state = store.execution_payload_states[store.justified_checkpoint.root].copy() + else: + non_viable_state = store.block_states[store.justified_checkpoint.root].copy() # ensure that next wave of votes are for future epoch next_epoch(spec, non_viable_state) @@ -238,26 +246,548 @@ def test_filtered_block_tree(spec, state): attestations = [] for i in range(spec.SLOTS_PER_EPOCH): slot = rogue_block.slot + i - for index in range(spec.get_committee_count_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot))): + for index in range( + spec.get_committee_count_per_slot(non_viable_state, spec.compute_epoch_at_slot(slot)) + ): attestation = get_valid_attestation(spec, non_viable_state, slot, index, signed=True) attestations.append(attestation) # tick time forward to be able to include up to the latest attestation - current_time = (attestations[-1].data.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time + current_time = ( + attestations[-1].data.slot + 1 + ) * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) # include rogue block and associated attestations in the store yield from add_block(spec, store, signed_rogue_block, test_steps) + payload_state_transition(spec, store, signed_rogue_block.message) for attestation in attestations: yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) # ensure that get_head still returns the head from the previous branch - assert spec.get_head(store) == expected_head_root - test_steps.append({ - 'checks': { - 'head': get_formatted_head_output(spec, store) - } - }) + check_head_against_root(spec, store, expected_head_root) + output_head_check(spec, store, test_steps) + + yield "steps", test_steps + + +# This test is skipped in EIP-7732 because the block's slot decides first on weight ties +@with_all_phases_from_except(ALTAIR, [EIP7732]) +@spec_state_test +def test_proposer_boost_correct_head(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + anchor_root = get_anchor_root(spec, state) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) + + # Build block that serves as head ONLY on timely arrival, and ONLY in that slot + state_1 = genesis_state.copy() + next_slots(spec, state_1, 3) + block_1 = build_empty_block_for_next_slot(spec, state_1) + signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) + + # Build block that serves as current head, and remains the head after block_1.slot + state_2 = genesis_state.copy() + next_slots(spec, state_2, 2) + block_2 = build_empty_block_for_next_slot(spec, state_2) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + rng = random.Random(1001) + while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): + block_2.body.graffiti = spec.Bytes32(hex(rng.getrandbits(8 * 32))[2:].zfill(64)) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) + + # Tick to block_1 slot time + time = store.genesis_time + block_1.slot * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + + # Process block_2 + yield from add_block(spec, store, signed_block_2, test_steps) + assert store.proposer_boost_root == spec.Root() + check_head_against_root(spec, store, spec.hash_tree_root(block_2)) + + # Process block_1 on timely arrival + # The head should temporarily change to block_1 + yield from add_block(spec, store, signed_block_1, test_steps) + assert store.proposer_boost_root == spec.hash_tree_root(block_1) + check_head_against_root(spec, store, spec.hash_tree_root(block_1)) + + # After block_1.slot, the head should revert to block_2 + time = store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + assert store.proposer_boost_root == spec.Root() + check_head_against_root(spec, store, spec.hash_tree_root(block_2)) + output_head_check(spec, store, test_steps) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_discard_equivocations_on_attester_slashing(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + anchor_root = get_anchor_root(spec, state) + check_head_against_root(spec, store, anchor_root) + output_head_check(spec, store, test_steps) + + # Build block that serves as head before discarding equivocations + state_1 = genesis_state.copy() + next_slots(spec, state_1, 2) + block_1 = build_empty_block_for_next_slot(spec, state_1) + signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) + + # Build equivocating attestations to feed to store + state_eqv = state_1.copy() + block_eqv = apply_empty_block(spec, state_eqv, state_eqv.slot + 1) + attestation_eqv = get_valid_attestation(spec, state_eqv, slot=block_eqv.slot, signed=True) + + next_slots(spec, state_1, 1) + attestation = get_valid_attestation(spec, state_1, slot=block_eqv.slot, signed=True) + assert spec.is_slashable_attestation_data(attestation.data, attestation_eqv.data) + + indexed_attestation = spec.get_indexed_attestation(state_1, attestation) + indexed_attestation_eqv = spec.get_indexed_attestation(state_eqv, attestation_eqv) + attester_slashing = spec.AttesterSlashing( + attestation_1=indexed_attestation, attestation_2=indexed_attestation_eqv + ) + + # Build block that serves as head after discarding equivocations + state_2 = genesis_state.copy() + next_slots(spec, state_2, 3) + block_2 = build_empty_block_for_next_slot(spec, state_2) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + rng = random.Random(1001) + while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2): + block_2.body.graffiti = spec.Bytes32(hex(rng.getrandbits(8 * 32))[2:].zfill(64)) + signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2) + assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2) + + # Tick to (block_eqv.slot + 2) slot time + time = store.genesis_time + (block_eqv.slot + 2) * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + + # Process block_1 + yield from add_block(spec, store, signed_block_1, test_steps) + payload_state_transition(spec, store, signed_block_1.message) + assert store.proposer_boost_root == spec.Root() + check_head_against_root(spec, store, spec.hash_tree_root(block_1)) + + # Process block_2 head should switch to block_2 + yield from add_block(spec, store, signed_block_2, test_steps) + payload_state_transition(spec, store, signed_block_2.message) + assert store.proposer_boost_root == spec.Root() + check_head_against_root(spec, store, spec.hash_tree_root(block_2)) + + # Process attestation + # The head should change to block_1 + yield from add_attestation(spec, store, attestation, test_steps) + check_head_against_root(spec, store, spec.hash_tree_root(block_1)) + + # Process attester_slashing + # The head should revert to block_2 + yield from add_attester_slashing(spec, store, attester_slashing, test_steps) + check_head_against_root(spec, store, spec.hash_tree_root(block_2)) + output_head_check(spec, store, test_steps) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_discard_equivocations_slashed_validator_censoring(spec, state): + # Check that the store does not count LMD votes from validators that are slashed in the justified state + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 0 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 + + # We will slash all validators voting at the 2nd slot of epoch 0 + current_slot = spec.get_current_slot(store) + eqv_slot = current_slot + 1 + eqv_epoch = spec.compute_epoch_at_slot(eqv_slot) + assert eqv_slot % spec.SLOTS_PER_EPOCH == 1 + assert eqv_epoch == 0 + slashed_validators = [] + comm_count = spec.get_committee_count_per_slot(state, eqv_epoch) + for comm_index in range(comm_count): + comm = spec.get_beacon_committee(state, eqv_slot, comm_index) + slashed_validators += comm + assert len(slashed_validators) > 0 + + # Slash those validators in the state + for val_index in slashed_validators: + state.validators[val_index].slashed = True + + # Store this state as the anchor state + anchor_state = state.copy() + # Generate an anchor block with correct state root + anchor_block = spec.BeaconBlock(state_root=anchor_state.hash_tree_root()) + if is_post_eip7732(spec): + anchor_block.body.signed_execution_payload_header.message.block_hash = ( + anchor_state.latest_block_hash + ) + yield "anchor_state", anchor_state + yield "anchor_block", anchor_block + + # Get a new store with the anchor state & anchor block + store = spec.get_forkchoice_store(anchor_state, anchor_block) + if is_post_eip7732(spec): + store.execution_payload_states = store.block_states.copy() + + # Now generate the store checks + current_time = anchor_state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # Create two competing blocks at eqv_slot + next_slots(spec, state, eqv_slot - state.slot - 1) + assert state.slot == eqv_slot - 1 + + state_1 = state.copy() + block_1 = build_empty_block_for_next_slot(spec, state_1) + signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1) + + state_2 = state.copy() + block_2 = build_empty_block_for_next_slot(spec, state_2) + block_2.body.graffiti = block_2.body.graffiti = b"\x42" * 32 + signed_block_2 = state_transition_and_sign_block(spec, state_2, block_2) + + assert block_1.slot == block_2.slot == eqv_slot + + # Add both blocks to the store + yield from tick_and_add_block(spec, store, signed_block_1, test_steps) + payload_state_transition(spec, store, signed_block_1.message) + yield from tick_and_add_block(spec, store, signed_block_2, test_steps) + payload_state_transition(spec, store, signed_block_2.message) + + # Find out which block will win in tie breaking + if spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2): + block_low_root = block_1.hash_tree_root() + block_low_root_post_state = state_1 + block_high_root = block_2.hash_tree_root() + else: + block_low_root = block_2.hash_tree_root() + block_low_root_post_state = state_2 + block_high_root = block_1.hash_tree_root() + assert block_low_root < block_high_root + + # Tick to next slot so proposer boost does not apply + current_time = store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, current_time, test_steps) + + # Check that block with higher root wins + check_head_against_root(spec, store, block_high_root) + + # Create attestation for block with lower root + attestation = get_valid_attestation( + spec, block_low_root_post_state, slot=eqv_slot, index=0, signed=True + ) + # Check that all attesting validators were slashed in the anchor state + att_comm = spec.get_beacon_committee(block_low_root_post_state, eqv_slot, 0) + for i in att_comm: + assert anchor_state.validators[i].slashed + # Add attestation to the store + yield from add_attestation(spec, store, attestation, test_steps) + # Check that block with higher root still wins + check_head_against_root(spec, store, block_high_root) + output_head_check(spec, store, test_steps) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_voting_source_within_two_epoch(spec, state): + """ + Check that the store allows for a head block that has: + - store.voting_source[block_root].epoch != store.justified_checkpoint.epoch, and + - store.unrealized_justifications[block_root].epoch >= store.justified_checkpoint.epoch, and + - store.voting_source[block_root].epoch + 2 >= current_epoch, and + - store.finalized_checkpoint.root == get_checkpoint_block(store, block_root, store.finalized_checkpoint.epoch) + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Copy the state to use later + fork_state = state.copy() + + # Fill epoch 4 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.finalized_checkpoint.epoch == 3 + + # Create a fork from the earlier saved state + next_epoch(spec, fork_state) + assert spec.compute_epoch_at_slot(fork_state.slot) == 5 + _, signed_blocks, fork_state = next_epoch_with_attestations(spec, fork_state, True, True) + # Only keep the blocks from epoch 5, so discard the last generated block + signed_blocks = signed_blocks[:-1] + last_fork_block = signed_blocks[-1].message + assert spec.compute_epoch_at_slot(last_fork_block.slot) == 5 + + # Now add the fork to the store + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.finalized_checkpoint.epoch == 3 + + # Check that the last block from the fork is the head + # LMD votes for the competing branch are overwritten so this fork should win + last_fork_block_root = last_fork_block.hash_tree_root() + # assert store.voting_source[last_fork_block_root].epoch != store.justified_checkpoint.epoch + assert ( + store.unrealized_justifications[last_fork_block_root].epoch + >= store.justified_checkpoint.epoch + ) + # assert store.voting_source[last_fork_block_root].epoch + 2 >= \ + # spec.compute_epoch_at_slot(spec.get_current_slot(store)) + assert store.finalized_checkpoint.root == spec.get_checkpoint_block( + store, last_fork_block_root, store.finalized_checkpoint.epoch + ) + check_head_against_root(spec, store, last_fork_block_root) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_voting_source_beyond_two_epoch(spec, state): + """ + Check that the store doesn't allow for a head block that has: + - store.voting_source[block_root].epoch != store.justified_checkpoint.epoch, and + - store.unrealized_justifications[block_root].epoch >= store.justified_checkpoint.epoch, and + - store.voting_source[block_root].epoch + 2 < current_epoch, and + - store.finalized_checkpoint.root == get_checkpoint_block(store, block_root, store.finalized_checkpoint.epoch) + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Copy the state to use later + fork_state = state.copy() + + # Fill epoch 4 and 5 + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert store.finalized_checkpoint.epoch == 4 + + # Create a fork from the earlier saved state + for _ in range(2): + next_epoch(spec, fork_state) + assert spec.compute_epoch_at_slot(fork_state.slot) == 6 + assert fork_state.current_justified_checkpoint.epoch == 3 + _, signed_blocks, fork_state = next_epoch_with_attestations(spec, fork_state, True, True) + # Only keep the blocks from epoch 6, so discard the last generated block + signed_blocks = signed_blocks[:-1] + last_fork_block = signed_blocks[-1].message + assert spec.compute_epoch_at_slot(last_fork_block.slot) == 6 + + # Store the head before adding the fork to the store + correct_head = spec.get_head(store) + if is_post_eip7732(spec): + correct_head = correct_head.root + + # Now add the fork to the store + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert store.finalized_checkpoint.epoch == 4 + + last_fork_block_root = last_fork_block.hash_tree_root() + last_fork_block_state = store.block_states[last_fork_block_root] + assert last_fork_block_state.current_justified_checkpoint.epoch == 3 + + # Check that the head is unchanged + # assert store.voting_source[last_fork_block_root].epoch != store.justified_checkpoint.epoch + assert ( + store.unrealized_justifications[last_fork_block_root].epoch + >= store.justified_checkpoint.epoch + ) + # assert store.voting_source[last_fork_block_root].epoch + 2 < \ + # spec.compute_epoch_at_slot(spec.get_current_slot(store)) + assert store.finalized_checkpoint.root == spec.get_checkpoint_block( + store, last_fork_block_root, store.finalized_checkpoint.epoch + ) + check_head_against_root(spec, store, correct_head) + + yield "steps", test_steps + + +""" +Note: +We are unable to generate test vectors that check failure of the correct_finalized condition. +We cannot generate a block that: +- has !correct_finalized, and +- has correct_justified, and +- is a descendant of store.justified_checkpoint.root + +The block being a descendant of store.justified_checkpoint.root is necessary because +filter_block_tree descends the tree starting at store.justified_checkpoint.root + +@with_altair_and_later +@spec_state_test +def test_incorrect_finalized(spec, state): + # Check that the store doesn't allow for a head block that has: + # - store.voting_source[block_root].epoch == store.justified_checkpoint.epoch, and + # - store.finalized_checkpoint.epoch != GENESIS_EPOCH, and + # - store.finalized_checkpoint.root != get_checkpoint_block(store, block_root, store.finalized_checkpoint.epoch) + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield 'anchor_state', state + yield 'anchor_block', anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + + # Fill epoch 1 to 4 + for _ in range(4): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert store.finalized_checkpoint.epoch == 3 + + # Identify the fork block as the last block in epoch 4 + fork_block_root = state.latest_block_header.parent_root + fork_block = store.blocks[fork_block_root] + assert spec.compute_epoch_at_slot(fork_block.slot) == 4 + # Copy the state to use later + fork_state = store.block_states[fork_block_root].copy() + assert spec.compute_epoch_at_slot(fork_state.slot) == 4 + assert fork_state.current_justified_checkpoint.epoch == 3 + assert fork_state.finalized_checkpoint.epoch == 2 + + # Create a fork from the earlier saved state + for _ in range(2): + next_epoch(spec, fork_state) + assert spec.compute_epoch_at_slot(fork_state.slot) == 6 + assert fork_state.current_justified_checkpoint.epoch == 4 + assert fork_state.finalized_checkpoint.epoch == 3 + # Fill epoch 6 + signed_blocks = [] + _, signed_blocks_1, fork_state = next_epoch_with_attestations(spec, fork_state, True, False) + signed_blocks += signed_blocks_1 + assert spec.compute_epoch_at_slot(fork_state.slot) == 7 + # Check that epoch 6 is justified in this fork - it will be used as voting source for the tip of this fork + assert fork_state.current_justified_checkpoint.epoch == 6 + assert fork_state.finalized_checkpoint.epoch == 3 + # Create a chain in epoch 7 that has new justification for epoch 7 + _, signed_blocks_2, fork_state = next_epoch_with_attestations(spec, fork_state, True, False) + # Only keep the blocks from epoch 7, so discard the last generated block + signed_blocks_2 = signed_blocks_2[:-1] + signed_blocks += signed_blocks_2 + last_fork_block = signed_blocks[-1].message + assert spec.compute_epoch_at_slot(last_fork_block.slot) == 7 + + # Now add the fork to the store + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + assert store.justified_checkpoint.epoch == 6 + assert store.finalized_checkpoint.epoch == 3 + + # Fill epoch 5 and 6 in the original chain + for _ in range(2): + state, store, signed_head_block = yield from apply_next_epoch_with_attestations( + spec, state, store, True, False, test_steps=test_steps) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 6 + assert store.finalized_checkpoint.epoch == 5 + # Store the expected head + head_root = signed_head_block.message.hash_tree_root() + + # Check that the head is unchanged + last_fork_block_root = last_fork_block.hash_tree_root() + assert store.voting_source[last_fork_block_root].epoch == store.justified_checkpoint.epoch + assert store.finalized_checkpoint.epoch != spec.GENESIS_EPOCH + finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) + assert store.finalized_checkpoint.root != spec.get_checkpoint_block( + store, + block_root, + store.finalized_checkpoint.epoch + ) + assert spec.get_head(store) != last_fork_block_root + check_head_against_root(spec, store, head_root) yield 'steps', test_steps +""" diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_proposer_head.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_proposer_head.py new file mode 100644 index 0000000000..b9cb93c927 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_get_proposer_head.py @@ -0,0 +1,178 @@ +from eth_utils import encode_hex + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases_from_except, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestations_at_slot, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + ALTAIR, + EIP7732, +) +from eth2spec.test.helpers.fork_choice import ( + apply_next_epoch_with_attestations, + apply_next_slots_with_attestations, + get_genesis_forkchoice_store_and_block, + on_tick_and_append_step, + output_store_checks, + tick_and_add_block, + tick_and_run_on_attestation, +) +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, + state_transition_and_sign_block, +) + + +@with_all_phases_from_except(ALTAIR, [EIP7732]) +@spec_state_test +def test_basic_is_head_root(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + # On receiving a block of `GENESIS_SLOT + 1` slot + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + assert spec.get_head(store) == signed_block.message.hash_tree_root() + + # Proposer of next slot + head_root = spec.get_head(store) + + # Proposing next slot + next_slot(spec, state) + slot = state.slot + + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + proposer_head = spec.get_proposer_head(store, head_root, slot) + assert proposer_head == head_root + + output_store_checks(spec, store, test_steps) + test_steps.append( + { + "checks": { + "get_proposer_head": encode_hex(proposer_head), + } + } + ) + + yield "steps", test_steps + + +@with_all_phases_from_except(ALTAIR, [EIP7732]) +@spec_state_test +def test_basic_is_parent_root(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + + # Make an empty block + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + # Fill a slot (parent) + state, store, signed_parent_block = yield from apply_next_slots_with_attestations( + spec, state, store, 1, True, True, test_steps + ) + + # Fill a slot with attestations to its parent + block = build_empty_block_for_next_slot(spec, state) + parent_block_slot = block.slot - 1 + block.body.attestations = get_valid_attestations_at_slot( + state, + spec, + parent_block_slot, + ) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Make the head block late + attesting_cutoff = spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + attesting_cutoff + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + yield from tick_and_add_block(spec, store, signed_block, test_steps) + + # Check conditions + head_root = spec.get_head(store) + head_block = store.blocks[head_root] + parent_root = head_block.parent_root + assert parent_root == signed_parent_block.message.hash_tree_root() + parent_block = store.blocks[parent_root] + + # Proposing next slot + next_slot(spec, state) + slot = state.slot + + # Add attestations to the parent block + attestations = get_valid_attestations_at_slot( + state, + spec, + slot_to_attest=slot - 1, + beacon_block_root=parent_root, + ) + for attestation in attestations: + yield from tick_and_run_on_attestation(spec, store, attestation, test_steps) + + # The conditions in `get_proposer_head` + assert spec.is_head_late(store, head_root) + assert spec.is_shuffling_stable(slot) + assert spec.is_ffg_competitive(store, head_root, parent_root) + assert spec.is_finalization_ok(store, slot) + assert spec.is_proposing_on_time(store) + + parent_slot_ok = parent_block.slot + 1 == head_block.slot + current_time_ok = head_block.slot + 1 == slot + single_slot_reorg = parent_slot_ok and current_time_ok + assert single_slot_reorg + + assert spec.is_head_weak(store, head_root) + assert spec.is_parent_strong(store, parent_root) + + proposer_head = spec.get_proposer_head(store, head_root, state.slot) + assert proposer_head == parent_root + + output_store_checks(spec, store, test_steps) + test_steps.append( + { + "checks": { + "get_proposer_head": encode_hex(proposer_head), + } + } + ) + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py index c00a91b28b..786c86ceb3 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_on_block.py @@ -1,33 +1,46 @@ import random -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets +from eth_utils import encode_hex + +from eth2spec.test.context import MINIMAL, spec_state_test, with_altair_and_later, with_presets from eth2spec.test.helpers.attestations import ( next_epoch_with_attestations, next_slots_with_attestations, - state_transition_with_full_block, - state_transition_with_full_attestations_block, ) from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, build_empty_block, - transition_unsigned_block, + build_empty_block_for_next_slot, sign_block, + transition_unsigned_block, +) +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + compute_el_block_hash, + compute_el_block_hash_for_block, ) from eth2spec.test.helpers.fork_choice import ( - get_genesis_forkchoice_store_and_block, - on_tick_and_append_step, add_block, - tick_and_add_block, apply_next_epoch_with_attestations, apply_next_slots_with_attestations, + check_head_against_root, + find_next_justifying_slot, + get_genesis_forkchoice_store_and_block, + get_store_full_state, + is_ready_to_justify, + on_tick_and_append_step, + tick_and_add_block, +) +from eth2spec.test.helpers.forks import ( + is_post_bellatrix, + is_post_eip7732, ) from eth2spec.test.helpers.state import ( next_epoch, next_slots, + payload_state_transition, state_transition_and_sign_block, ) - +from eth2spec.utils.ssz.ssz_impl import hash_tree_root rng = random.Random(2020) @@ -37,17 +50,17 @@ def _drop_random_one_third(_slot, _index, indices): assert committee_len >= 3 filter_len = committee_len // 3 participant_count = committee_len - filter_len - return rng.sample(indices, participant_count) + return rng.sample(sorted(indices), participant_count) -@with_all_phases +@with_altair_and_later @spec_state_test def test_basic(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time @@ -56,65 +69,78 @@ def test_basic(spec, state): block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert spec.get_head(store) == signed_block.message.hash_tree_root() + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) # On receiving a block of next epoch store.time = current_time + spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert spec.get_head(store) == signed_block.message.hash_tree_root() + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) - yield 'steps', test_steps + yield "steps", test_steps # TODO: add tests for justified_root and finalized_root -@with_all_phases +@with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_checkpoints(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Run for 1 epoch with full attestations next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) state, store, last_signed_block = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) + spec, state, store, True, False, test_steps=test_steps + ) last_block_root = hash_tree_root(last_signed_block.message) - assert spec.get_head(store) == last_block_root + check_head_against_root(spec, store, last_block_root) # Forward 1 epoch next_epoch(spec, state) - on_tick_and_append_step(spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) # Mock the finalized_checkpoint and build a block on it - fin_state = store.block_states[last_block_root].copy() - fin_state.finalized_checkpoint = store.block_states[last_block_root].current_justified_checkpoint.copy() - + if is_post_eip7732(spec): + fin_state = store.execution_payload_states[last_block_root].copy() + else: + fin_state = store.block_states[last_block_root].copy() + + fin_state.finalized_checkpoint = store.block_states[ + last_block_root + ].current_justified_checkpoint.copy() block = build_empty_block_for_next_slot(spec, fin_state) - signed_block = state_transition_and_sign_block(spec, fin_state.copy(), block) + signed_block = state_transition_and_sign_block(spec, fin_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert spec.get_head(store) == signed_block.message.hash_tree_root() - yield 'steps', test_steps + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test def test_on_block_future_block(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time @@ -125,17 +151,17 @@ def test_on_block_future_block(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) yield from add_block(spec, store, signed_block, test_steps, valid=False) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test def test_on_block_bad_parent_root(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time @@ -145,24 +171,31 @@ def test_on_block_bad_parent_root(spec, state): transition_unsigned_block(spec, state, block) block.state_root = state.hash_tree_root() - block.parent_root = b'\x45' * 32 + block.parent_root = b"\x45" * 32 + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + elif is_post_bellatrix(spec): + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) signed_block = sign_block(spec, state, block) yield from add_block(spec, store, signed_block, test_steps, valid=False) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_before_finalized(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time @@ -173,20 +206,21 @@ def test_on_block_before_finalized(spec, state): # Create a finalized chain for _ in range(4): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) + spec, state, store, True, False, test_steps=test_steps + ) assert store.finalized_checkpoint.epoch == 2 # Fail receiving block of `GENESIS_SLOT + 1` slot block = build_empty_block_for_next_slot(spec, another_state) - block.body.graffiti = b'\x12' * 32 + block.body.graffiti = b"\x12" * 32 signed_block = state_transition_and_sign_block(spec, another_state, block) assert signed_block.message.hash_tree_root() not in store.blocks yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_finalized_skip_slots(spec, state): @@ -197,15 +231,16 @@ def test_on_block_finalized_skip_slots(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Fill epoch 0 and the first slot of epoch 1 state, store, _ = yield from apply_next_slots_with_attestations( - spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps) + spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps + ) # Skip the rest slots of epoch 1 and the first slot of epoch 2 next_slots(spec, state, spec.SLOTS_PER_EPOCH) @@ -216,11 +251,16 @@ def test_on_block_finalized_skip_slots(spec, state): # Fill epoch 3 and 4 for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, True, test_steps=test_steps) + spec, state, store, True, True, test_steps=test_steps + ) # Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) + assert ( + store.finalized_checkpoint.root + == spec.get_block_root(state, 1) + == spec.get_block_root(state, 2) + ) assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 assert store.justified_checkpoint == state.current_justified_checkpoint @@ -229,11 +269,12 @@ def test_on_block_finalized_skip_slots(spec, state): block = build_empty_block_for_next_slot(spec, target_state) signed_block = state_transition_and_sign_block(spec, target_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +@with_altair_and_later @spec_state_test @with_presets([MINIMAL], reason="too slow") def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): @@ -244,15 +285,16 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time # Fill epoch 0 and the first slot of epoch 1 state, store, _ = yield from apply_next_slots_with_attestations( - spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps) + spec, state, store, spec.SLOTS_PER_EPOCH, True, False, test_steps + ) # Skip the rest slots of epoch 1 and the first slot of epoch 2 next_slots(spec, state, spec.SLOTS_PER_EPOCH) @@ -260,323 +302,48 @@ def test_on_block_finalized_skip_slots_not_in_skip_chain(spec, state): # Fill epoch 3 and 4 for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, True, test_steps=test_steps) + spec, state, store, True, True, test_steps=test_steps + ) # Now we get finalized epoch 2, where `compute_start_slot_at_epoch(2)` is a skipped slot assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert store.finalized_checkpoint.root == spec.get_block_root(state, 1) == spec.get_block_root(state, 2) + assert ( + store.finalized_checkpoint.root + == spec.get_block_root(state, 1) + == spec.get_block_root(state, 2) + ) assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 assert store.justified_checkpoint == state.current_justified_checkpoint # Now build a block after the block of the finalized **root** # Includes finalized block in chain, but does not include finalized skipped slots another_state = store.block_states[store.finalized_checkpoint.root].copy() - assert another_state.slot == spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch - 1) + assert another_state.slot == spec.compute_start_slot_at_epoch( + store.finalized_checkpoint.epoch - 1 + ) block = build_empty_block_for_next_slot(spec, another_state) signed_block = state_transition_and_sign_block(spec, another_state, block) yield from tick_and_add_block(spec, store, signed_block, test_steps, valid=False) - yield 'steps', test_steps - - -@with_all_phases -@spec_state_test -@with_presets([MINIMAL], reason="mainnet config requires too many pre-generated public/private keys") -def test_on_block_update_justified_checkpoint_within_safe_slots(spec, state): - """ - Test `should_update_justified_checkpoint`: - compute_slots_since_epoch_start(get_current_slot(store)) < SAFE_SLOTS_TO_UPDATE_JUSTIFIED - """ - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # Skip epoch 0 & 1 - for _ in range(2): - next_epoch(spec, state) - # Fill epoch 2 - state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 - # Skip epoch 3 & 4 - for _ in range(2): - next_epoch(spec, state) - # Epoch 5: Attest current epoch - state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, participation_fn=_drop_random_one_third, test_steps=test_steps) - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 - assert state.current_justified_checkpoint.epoch == 2 - assert store.justified_checkpoint.epoch == 2 - assert state.current_justified_checkpoint == store.justified_checkpoint - - # Skip epoch 6 - next_epoch(spec, state) - - pre_state = state.copy() - - # Build a block to justify epoch 5 - signed_block = state_transition_with_full_block(spec, state, True, True) - assert state.finalized_checkpoint.epoch == 0 - assert state.current_justified_checkpoint.epoch == 5 - assert state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH < spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - # Run on_block - yield from tick_and_add_block(spec, store, signed_block, test_steps) - # Ensure justified_checkpoint has been changed but finality is unchanged - assert store.justified_checkpoint.epoch == 5 - assert store.justified_checkpoint == state.current_justified_checkpoint - assert store.finalized_checkpoint.epoch == pre_state.finalized_checkpoint.epoch == 0 - - yield 'steps', test_steps - - -@with_all_phases -@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") -@spec_state_test -def test_on_block_outside_safe_slots_but_finality(spec, state): - """ - Test `should_update_justified_checkpoint` case - - compute_slots_since_epoch_start(get_current_slot(store)) > SAFE_SLOTS_TO_UPDATE_JUSTIFIED - - new_justified_checkpoint and store.justified_checkpoint.root are NOT conflicting - - Thus should_update_justified_checkpoint returns True. - - Part of this script is similar to `test_new_justified_is_later_than_store_justified`. - """ - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # Skip epoch 0 - next_epoch(spec, state) - # Fill epoch 1 to 3, attest current epoch - for _ in range(3): - state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 - - # Skip epoch 4-6 - for _ in range(3): - next_epoch(spec, state) - - # epoch 7 - state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, True, test_steps=test_steps) - assert state.finalized_checkpoint.epoch == 2 - assert state.current_justified_checkpoint.epoch == 7 - - # epoch 8, attest the first 5 blocks - state, store, _ = yield from apply_next_slots_with_attestations( - spec, state, store, 5, True, True, test_steps) - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 - - # Propose a block at epoch 9, 5th slot - next_epoch(spec, state) - next_slots(spec, state, 4) - signed_block = state_transition_with_full_attestations_block(spec, state, True, True) - yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 - assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 7 - - # Propose an empty block at epoch 10, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot - # This block would trigger justification and finality updates on store - next_epoch(spec, state) - next_slots(spec, state, 4) - block = build_empty_block_for_next_slot(spec, state) - signed_block = state_transition_and_sign_block(spec, state, block) - assert state.finalized_checkpoint.epoch == 7 - assert state.current_justified_checkpoint.epoch == 8 - # Step time past safe slots and run on_block - if store.time < spec.compute_time_at_slot(state, signed_block.message.slot): - time = store.genesis_time + signed_block.message.slot * spec.config.SECONDS_PER_SLOT - on_tick_and_append_step(spec, store, time, test_steps) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - yield from add_block(spec, store, signed_block, test_steps) - - # Ensure justified_checkpoint finality has been changed - assert store.finalized_checkpoint.epoch == 7 - assert store.finalized_checkpoint == state.finalized_checkpoint - assert store.justified_checkpoint.epoch == 8 - assert store.justified_checkpoint == state.current_justified_checkpoint - - yield 'steps', test_steps - - -@with_all_phases -@with_presets([MINIMAL], reason="It assumes that `MAX_ATTESTATIONS` >= 2/3 attestations of an epoch") -@spec_state_test -def test_new_justified_is_later_than_store_justified(spec, state): - """ - J: Justified - F: Finalized - fork_1_state (forked from genesis): - epoch - [0] <- [1] <- [2] <- [3] <- [4] - F J - - fork_2_state (forked from fork_1_state's epoch 2): - epoch - └──── [3] <- [4] <- [5] <- [6] - F J - - fork_3_state (forked from genesis): - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J - """ - # The 1st fork, from genesis - fork_1_state = state.copy() - # The 3rd fork, from genesis - fork_3_state = state.copy() - - test_steps = [] - # Initialization - store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block - current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time - on_tick_and_append_step(spec, store, current_time, test_steps) - assert store.time == current_time - - # ----- Process fork_1_state - # Skip epoch 0 - next_epoch(spec, fork_1_state) - # Fill epoch 1 with previous epoch attestations - fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( - spec, fork_1_state, store, False, True, test_steps=test_steps) - - # Fork `fork_2_state` at the start of epoch 2 - fork_2_state = fork_1_state.copy() - assert spec.get_current_epoch(fork_2_state) == 2 - - # Skip epoch 2 - next_epoch(spec, fork_1_state) - # # Fill epoch 3 & 4 with previous epoch attestations - for _ in range(2): - fork_1_state, store, _ = yield from apply_next_epoch_with_attestations( - spec, fork_1_state, store, False, True, test_steps=test_steps) - - assert fork_1_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 0 - assert fork_1_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 - assert store.justified_checkpoint == fork_1_state.current_justified_checkpoint - - # ------ fork_2_state: Create a chain to set store.best_justified_checkpoint - # NOTE: The goal is to make `store.best_justified_checkpoint.epoch > store.justified_checkpoint.epoch` - all_blocks = [] - - # Proposed an empty block at epoch 2, 1st slot - block = build_empty_block_for_next_slot(spec, fork_2_state) - signed_block = state_transition_and_sign_block(spec, fork_2_state, block) - yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Skip to epoch 4 - for _ in range(2): - next_epoch(spec, fork_2_state) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Propose a block at epoch 4, 5th slot - # Propose a block at epoch 5, 5th slot - for _ in range(2): - next_epoch(spec, fork_2_state) - next_slots(spec, fork_2_state, 4) - signed_block = state_transition_with_full_attestations_block(spec, fork_2_state, True, True) - yield from tick_and_add_block(spec, store, signed_block, test_steps) - assert fork_2_state.current_justified_checkpoint.epoch == 0 - - # Propose a block at epoch 6, SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2 slot - next_epoch(spec, fork_2_state) - next_slots(spec, fork_2_state, spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED + 2) - signed_block = state_transition_with_full_attestations_block(spec, fork_2_state, True, True) - assert fork_2_state.finalized_checkpoint.epoch == 0 - assert fork_2_state.current_justified_checkpoint.epoch == 5 - # Check SAFE_SLOTS_TO_UPDATE_JUSTIFIED - time = store.genesis_time + fork_2_state.slot * spec.config.SECONDS_PER_SLOT - on_tick_and_append_step(spec, store, time, test_steps) - assert spec.compute_slots_since_epoch_start(spec.get_current_slot(store)) >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - # Run on_block - yield from add_block(spec, store, signed_block, test_steps) - assert store.finalized_checkpoint.epoch == 0 - assert store.justified_checkpoint.epoch == 3 - assert store.best_justified_checkpoint.epoch == 5 - - # ------ fork_3_state: Create another chain to test the - # "Update justified if new justified is later than store justified" case - all_blocks = [] - for _ in range(3): - next_epoch(spec, fork_3_state) - - # epoch 3 - _, signed_blocks, fork_3_state = next_epoch_with_attestations(spec, fork_3_state, True, True) - all_blocks += signed_blocks - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # epoch 4, attest the first 5 blocks - _, blocks, fork_3_state = next_slots_with_attestations(spec, fork_3_state, 5, True, True) - all_blocks += blocks.copy() - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # Propose a block at epoch 5, 5th slot - next_epoch(spec, fork_3_state) - next_slots(spec, fork_3_state, 4) - signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) - all_blocks.append(signed_block.copy()) - assert fork_3_state.finalized_checkpoint.epoch == 0 - - # Propose a block at epoch 6, 5th slot - next_epoch(spec, fork_3_state) - next_slots(spec, fork_3_state, 4) - signed_block = state_transition_with_full_block(spec, fork_3_state, True, True) - all_blocks.append(signed_block.copy()) - assert fork_3_state.finalized_checkpoint.epoch == 3 - assert fork_3_state.current_justified_checkpoint.epoch == 4 - - # FIXME: pending on the `on_block`, `on_attestation` fix - # # Apply blocks of `fork_3_state` to `store` - # for block in all_blocks: - # if store.time < spec.compute_time_at_slot(fork_2_state, block.message.slot): - # time = store.genesis_time + block.message.slot * spec.config.SECONDS_PER_SLOT - # on_tick_and_append_step(spec, store, time, test_steps) - # # valid_attestations=False because the attestations are outdated (older than previous epoch) - # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=False) - - # assert store.finalized_checkpoint == fork_3_state.finalized_checkpoint - # assert (store.justified_checkpoint - # == fork_3_state.current_justified_checkpoint - # != store.best_justified_checkpoint) - # assert (store.best_justified_checkpoint - # == fork_2_state.current_justified_checkpoint) - - yield 'steps', test_steps + yield "steps", test_steps -@with_all_phases +""" +@with_altair_and_later @spec_state_test +@with_presets([MINIMAL], reason="too slow") def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): - """ - J: Justified - F: Finalized - state (forked from genesis): - epoch - [0] <- [1] <- [2] <- [3] <- [4] <- [5] - F J + # J: Justified + # F: Finalized + # state (forked from genesis): + # epoch + # [0] <- [1] <- [2] <- [3] <- [4] <- [5] + # F J + + # another_state (forked from epoch 0): + # └──── [1] <- [2] <- [3] <- [4] <- [5] + # F J - another_state (forked from epoch 0): - └──── [1] <- [2] <- [3] <- [4] <- [5] - F J - """ test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) @@ -621,26 +388,35 @@ def test_new_finalized_slot_is_not_justified_checkpoint_ancestor(spec, state): assert state.finalized_checkpoint != another_state.finalized_checkpoint assert state.current_justified_checkpoint != another_state.current_justified_checkpoint - # pre_store_justified_checkpoint_root = store.justified_checkpoint.root + pre_store_justified_checkpoint_root = store.justified_checkpoint.root + + # Apply blocks of `another_state` to `store` + for block in all_blocks: + # NOTE: Do not call `on_tick` here + yield from add_block(spec, store, block, test_steps) - # FIXME: pending on the `on_block`, `on_attestation` fix - # # Apply blocks of `another_state` to `store` - # for block in all_blocks: - # # NOTE: Do not call `on_tick` here - # yield from add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + ancestor_at_finalized_slot = spec.get_checkpoint_block( + store, + pre_store_justified_checkpoint_root, + store.finalized_checkpoint.epoch + ) + assert ancestor_at_finalized_slot != store.finalized_checkpoint.root - # finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - # ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) - # assert ancestor_at_finalized_slot != store.finalized_checkpoint.root + assert store.finalized_checkpoint == another_state.finalized_checkpoint - # assert store.finalized_checkpoint == another_state.finalized_checkpoint - # assert store.justified_checkpoint == another_state.current_justified_checkpoint + # NOTE: inconsistent justified/finalized checkpoints in this edge case. + # This can only happen when >1/3 validators are slashable, as this testcase requires that + # store.justified_checkpoint is higher than store.finalized_checkpoint and on a different branch. + # Ignoring this testcase for now. + assert store.justified_checkpoint != another_state.current_justified_checkpoint yield 'steps', test_steps +""" -@with_all_phases +@with_altair_and_later @spec_state_test +@with_presets([MINIMAL], reason="too slow") def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): """ J: Justified @@ -657,8 +433,8 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): test_steps = [] # Initialization store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) - yield 'anchor_state', state - yield 'anchor_block', anchor_block + yield "anchor_state", state + yield "anchor_block", anchor_block current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time on_tick_and_append_step(spec, store, current_time, test_steps) assert store.time == current_time @@ -667,15 +443,18 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): next_epoch(spec, state) state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, False, True, test_steps=test_steps) + spec, state, store, False, True, test_steps=test_steps + ) state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False, test_steps=test_steps) + spec, state, store, True, False, test_steps=test_steps + ) next_epoch(spec, state) for _ in range(2): state, store, _ = yield from apply_next_epoch_with_attestations( - spec, state, store, False, True, test_steps=test_steps) + spec, state, store, False, True, test_steps=test_steps + ) assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 @@ -686,9 +465,11 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): all_blocks = [] slot = spec.compute_start_slot_at_epoch(3) block_root = spec.get_block_root_at_slot(state, slot) - another_state = store.block_states[block_root].copy() + another_state = get_store_full_state(spec, store, block_root).copy() for _ in range(2): - _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, True) + _, signed_blocks, another_state = next_epoch_with_attestations( + spec, another_state, True, True + ) all_blocks += signed_blocks assert another_state.finalized_checkpoint.epoch == 3 @@ -696,15 +477,1129 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state): pre_store_justified_checkpoint_root = store.justified_checkpoint.root for block in all_blocks: - # FIXME: Once `on_block` and `on_attestation` logic is fixed, - # fix test case and remove allow_invalid_attestations flag - yield from tick_and_add_block(spec, store, block, test_steps, allow_invalid_attestations=True) + yield from tick_and_add_block(spec, store, block, test_steps) + payload_state_transition(spec, store, block.message) - finalized_slot = spec.compute_start_slot_at_epoch(store.finalized_checkpoint.epoch) - ancestor_at_finalized_slot = spec.get_ancestor(store, pre_store_justified_checkpoint_root, finalized_slot) + ancestor_at_finalized_slot = spec.get_checkpoint_block( + store, pre_store_justified_checkpoint_root, store.finalized_checkpoint.epoch + ) assert ancestor_at_finalized_slot == store.finalized_checkpoint.root assert store.finalized_checkpoint == another_state.finalized_checkpoint + + # NOTE: inconsistent justified/finalized checkpoints in this edge case assert store.justified_checkpoint != another_state.current_justified_checkpoint - yield 'steps', test_steps + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_proposer_boost(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + + # Build block that serves as head ONLY on timely arrival, and ONLY in that slot + state = genesis_state.copy() + next_slots(spec, state, 3) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Process block on timely arrival just before end of boost interval + time = ( + store.genesis_time + + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT + - 1 + ) + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert store.proposer_boost_root == spec.hash_tree_root(block) + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block), + slot=block.slot, + ) + assert spec.get_weight(store, node) > 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block)) > 0 + + # Ensure that boost is removed after slot is over + time = ( + store.genesis_time + + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT + ) + on_tick_and_append_step(spec, store, time, test_steps) + assert store.proposer_boost_root == spec.Root() + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block), + slot=block.slot, + ) + assert spec.get_weight(store, node) == 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block)) == 0 + + next_slots(spec, state, 3) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Process block on timely arrival at start of boost interval + time = store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert store.proposer_boost_root == spec.hash_tree_root(block) + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block), + slot=block.slot, + ) + assert spec.get_weight(store, node) > 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block)) > 0 + + # Ensure that boost is removed after slot is over + time = ( + store.genesis_time + + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT + ) + on_tick_and_append_step(spec, store, time, test_steps) + assert store.proposer_boost_root == spec.Root() + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block), + slot=block.slot, + ) + assert spec.get_weight(store, node) == 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block)) == 0 + + test_steps.append( + { + "checks": { + "proposer_boost_root": encode_hex(store.proposer_boost_root), + } + } + ) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_proposer_boost_root_same_slot_untimely_block(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + + # Build block that serves as head ONLY on timely arrival, and ONLY in that slot + state = genesis_state.copy() + next_slots(spec, state, 3) + block = build_empty_block_for_next_slot(spec, state) + signed_block = state_transition_and_sign_block(spec, state, block) + + # Process block on untimely arrival in the same slot + time = ( + store.genesis_time + + block.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT + ) + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + + assert store.proposer_boost_root == spec.Root() + + test_steps.append( + { + "checks": { + "proposer_boost_root": encode_hex(store.proposer_boost_root), + } + } + ) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +def test_proposer_boost_is_first_block(spec, state): + test_steps = [] + genesis_state = state.copy() + + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + + # Build block that serves as head ONLY on timely arrival, and ONLY in that slot + state = genesis_state.copy() + next_slots(spec, state, 3) + pre_state = state.copy() + block_a = build_empty_block_for_next_slot(spec, state) + signed_block_a = state_transition_and_sign_block(spec, state, block_a) + + # Process block on timely arrival just before end of boost interval + time = ( + store.genesis_time + + block_a.slot * spec.config.SECONDS_PER_SLOT + + spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT + - 1 + ) + on_tick_and_append_step(spec, store, time, test_steps) + yield from add_block(spec, store, signed_block_a, test_steps) + payload_state_transition(spec, store, signed_block_a.message) + # `proposer_boost_root` is now `block_a` + assert store.proposer_boost_root == spec.hash_tree_root(block_a) + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block_a), + slot=block_a.slot, + ) + assert spec.get_weight(store, node) > 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block_a)) > 0 + test_steps.append( + { + "checks": { + "proposer_boost_root": encode_hex(store.proposer_boost_root), + } + } + ) + + # make a different block at the same slot + state = pre_state.copy() + block_b = block_a.copy() + block_b.body.graffiti = b"\x34" * 32 + signed_block_b = state_transition_and_sign_block(spec, state, block_b) + yield from add_block(spec, store, signed_block_b, test_steps) + payload_state_transition(spec, store, signed_block_b.message) + # `proposer_boost_root` is still `block_a` + assert store.proposer_boost_root == spec.hash_tree_root(block_a) + if is_post_eip7732(spec): + node = spec.ChildNode( + root=spec.hash_tree_root(block_b), + slot=block_b.slot, + ) + assert spec.get_weight(store, node) == 0 + else: + assert spec.get_weight(store, spec.hash_tree_root(block_b)) == 0 + test_steps.append( + { + "checks": { + "proposer_boost_root": encode_hex(store.proposer_boost_root), + } + } + ) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justification_withholding(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + for _ in range(2): + next_epoch(spec, state) + + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(state) == 4 + + # ------------ + + # Create attacker's fork that can justify epoch 4 + # Do not apply attacker's blocks to store + attacker_state = state.copy() + attacker_signed_blocks = [] + + while not is_ready_to_justify(spec, attacker_state): + attacker_state, signed_blocks, attacker_state = next_slots_with_attestations( + spec, attacker_state, 1, True, False + ) + attacker_signed_blocks += signed_blocks + + assert attacker_state.finalized_checkpoint.epoch == 2 + assert attacker_state.current_justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(attacker_state) == 4 + + # ------------ + + # The honest fork sees all except the last block from attacker_signed_blocks + # Apply honest fork to store + honest_signed_blocks = attacker_signed_blocks[:-1] + assert len(honest_signed_blocks) > 0 + + for signed_block in honest_signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + + last_honest_block = honest_signed_blocks[-1].message + honest_state = get_store_full_state(spec, store, hash_tree_root(last_honest_block)).copy() + + assert honest_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert honest_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(honest_state) == 4 + + # Create & apply an honest block in epoch 5 that can justify epoch 4 + next_epoch(spec, honest_state) + assert spec.get_current_epoch(honest_state) == 5 + + honest_block = build_empty_block_for_next_slot(spec, honest_state) + honest_block.body.attestations = attacker_signed_blocks[-1].message.body.attestations + signed_block = state_transition_and_sign_block(spec, honest_state, honest_block) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + check_head_against_root(spec, store, hash_tree_root(honest_block)) + assert is_ready_to_justify(spec, honest_state) + + # ------------ + + # When the attacker's block is received, the honest block is still the head + # This relies on the honest block's LMD score increasing due to proposer boost + yield from tick_and_add_block(spec, store, attacker_signed_blocks[-1], test_steps) + payload_state_transition(spec, store, attacker_signed_blocks[-1].message) + assert store.finalized_checkpoint.epoch == 3 + assert store.justified_checkpoint.epoch == 4 + check_head_against_root(spec, store, hash_tree_root(honest_block)) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justification_withholding_reverse_order(spec, state): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + for _ in range(2): + next_epoch(spec, state) + + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(state) == 4 + + # ------------ + + # Create attacker's fork that can justify epoch 4 + attacker_state = state.copy() + attacker_signed_blocks = [] + + while not is_ready_to_justify(spec, attacker_state): + attacker_state, signed_blocks, attacker_state = next_slots_with_attestations( + spec, attacker_state, 1, True, False + ) + assert len(signed_blocks) == 1 + attacker_signed_blocks += signed_blocks + yield from tick_and_add_block(spec, store, signed_blocks[0], test_steps) + payload_state_transition(spec, store, signed_blocks[0].message) + + assert attacker_state.finalized_checkpoint.epoch == 2 + assert attacker_state.current_justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(attacker_state) == 4 + attackers_head = hash_tree_root(attacker_signed_blocks[-1].message) + check_head_against_root(spec, store, attackers_head) + + # ------------ + + # The honest fork sees all except the last block from attacker_signed_blocks + honest_signed_blocks = attacker_signed_blocks[:-1] + assert len(honest_signed_blocks) > 0 + + last_honest_block = honest_signed_blocks[-1].message + honest_state = get_store_full_state(spec, store, hash_tree_root(last_honest_block)).copy() + + assert honest_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert honest_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.get_current_epoch(honest_state) == 4 + + # Create an honest block in epoch 5 that can justify epoch 4 + next_epoch(spec, honest_state) + assert spec.get_current_epoch(honest_state) == 5 + + honest_block = build_empty_block_for_next_slot(spec, honest_state) + honest_block.body.attestations = attacker_signed_blocks[-1].message.body.attestations + signed_block = state_transition_and_sign_block(spec, honest_state, honest_block) + assert honest_state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + assert honest_state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert is_ready_to_justify(spec, honest_state) + + # When the honest block is received, the honest block becomes the head + # This relies on the honest block's LMD score increasing due to proposer boost + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert store.finalized_checkpoint.epoch == 3 + assert store.justified_checkpoint.epoch == 4 + check_head_against_root(spec, store, hash_tree_root(honest_block)) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justification_update_beginning_of_epoch(spec, state): + """ + Check that the store's justified checkpoint is updated when a block containing better justification is + revealed at the first slot of an epoch + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Create a block that has new justification information contained within it, but don't add to store yet + another_state = state.copy() + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, False) + assert spec.compute_epoch_at_slot(another_state.slot) == 5 + assert another_state.current_justified_checkpoint.epoch == 4 + + # Tick store to the start of the next epoch + slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + assert store.justified_checkpoint.epoch == 4 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justification_update_end_of_epoch(spec, state): + """ + Check that the store's justified checkpoint is updated when a block containing better justification is + revealed at the last slot of an epoch + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Create a block that has new justification information contained within it, but don't add to store yet + another_state = state.copy() + _, signed_blocks, another_state = next_epoch_with_attestations(spec, another_state, True, False) + assert spec.compute_epoch_at_slot(another_state.slot) == 5 + assert another_state.current_justified_checkpoint.epoch == 4 + + # Tick store to the last slot of the next epoch + slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + slot = slot + spec.SLOTS_PER_EPOCH - 1 + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + assert store.justified_checkpoint.epoch == 4 + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_incompatible_justification_update_start_of_epoch(spec, state): + """ + Check that the store's justified checkpoint is updated when a block containing better justification is + revealed at the start slot of an epoch, even when the better justified checkpoint is not a descendant of + the store's justified checkpoint + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + + # Copy the state to create a fork later + another_state = state.copy() + + # Fill epoch 4 and 5 + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 4 + + # Create a block that has new justification information contained within it, but don't add to store yet + next_epoch(spec, another_state) + signed_blocks = [] + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, False, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 6 + assert another_state.current_justified_checkpoint.epoch == 3 + assert another_state.finalized_checkpoint.epoch == 2 + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, True, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 7 + assert another_state.current_justified_checkpoint.epoch == 6 + assert another_state.finalized_checkpoint.epoch == 2 + last_block_root = another_state.latest_block_header.parent_root + + # Tick store to the last slot of the next epoch + slot = another_state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 8 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + finalized_checkpoint_block = spec.get_checkpoint_block( + store, + last_block_root, + state.finalized_checkpoint.epoch, + ) + assert finalized_checkpoint_block == state.finalized_checkpoint.root + justified_checkpoint_block = spec.get_checkpoint_block( + store, + last_block_root, + state.current_justified_checkpoint.epoch, + ) + assert justified_checkpoint_block != state.current_justified_checkpoint.root + assert store.finalized_checkpoint.epoch == 4 + assert store.justified_checkpoint.epoch == 6 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_incompatible_justification_update_end_of_epoch(spec, state): + """ + Check that the store's justified checkpoint is updated when a block containing better justification is + revealed at the last slot of an epoch, even when the better justified checkpoint is not a descendant of + the store's justified checkpoint + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 2 + + # Copy the state to create a fork later + another_state = state.copy() + + # Fill epoch 4 and 5 + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 4 + + # Create a block that has new justification information contained within it, but don't add to store yet + next_epoch(spec, another_state) + signed_blocks = [] + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, False, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 6 + assert another_state.current_justified_checkpoint.epoch == 3 + assert another_state.finalized_checkpoint.epoch == 2 + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, True, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 7 + assert another_state.current_justified_checkpoint.epoch == 6 + assert another_state.finalized_checkpoint.epoch == 2 + last_block_root = another_state.latest_block_header.parent_root + + # Tick store to the last slot of the next epoch + slot = another_state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + slot = slot + spec.SLOTS_PER_EPOCH - 1 + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 8 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + finalized_checkpoint_block = spec.get_checkpoint_block( + store, + last_block_root, + state.finalized_checkpoint.epoch, + ) + assert finalized_checkpoint_block == state.finalized_checkpoint.root + justified_checkpoint_block = spec.get_checkpoint_block( + store, + last_block_root, + state.current_justified_checkpoint.epoch, + ) + assert justified_checkpoint_block != state.current_justified_checkpoint.root + assert store.finalized_checkpoint.epoch == 4 + assert store.justified_checkpoint.epoch == 6 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justified_update_not_realized_finality(spec, state): + """ + Check that the store updates its justified checkpoint if a higher justified checkpoint is found that is + a descendant of the finalized checkpoint, but does not know about the finality + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # We'll make the current head block the finalized block + if is_post_eip7732(spec): + finalized_root = spec.get_head(store).root + else: + finalized_root = spec.get_head(store) + finalized_block = store.blocks[finalized_root] + assert spec.compute_epoch_at_slot(finalized_block.slot) == 4 + check_head_against_root(spec, store, finalized_root) + # Copy the post-state to use later + another_state = state.copy() + + # Create a fork that finalizes our block + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 4 + assert state.finalized_checkpoint.root == store.finalized_checkpoint.root == finalized_root + + # Create a fork for a better justification that is a descendant of the finalized block, + # but does not realize the finality. + # Do not add these blocks to the store yet + next_epoch(spec, another_state) + signed_blocks = [] + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, False, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 6 + assert another_state.current_justified_checkpoint.epoch == 3 + assert another_state.finalized_checkpoint.epoch == 2 + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, True, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 7 + assert another_state.current_justified_checkpoint.epoch == 6 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert store.justified_checkpoint.epoch == 6 + assert store.finalized_checkpoint.epoch == 4 + last_block = signed_blocks[-1] + last_block_root = last_block.message.hash_tree_root() + ancestor_at_finalized_slot = spec.get_ancestor(store, last_block_root, finalized_block.slot) + if is_post_eip7732(spec): + ancestor_at_finalized_slot = ancestor_at_finalized_slot.root + + assert ancestor_at_finalized_slot == store.finalized_checkpoint.root + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justified_update_monotonic(spec, state): + """ + Check that the store does not update it's justified checkpoint with lower justified checkpoints. + This testcase checks that the store's justified checkpoint remains the same even when we input a block that has: + - a higher finalized checkpoint than the store's finalized checkpoint, and + - a lower justified checkpoint than the store's justified checkpoint + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # We'll eventually make the current head block the finalized block + if is_post_eip7732(spec): + finalized_root = spec.get_head(store).root + else: + finalized_root = spec.get_head(store) + finalized_block = store.blocks[finalized_root] + assert spec.compute_epoch_at_slot(finalized_block.slot) == 4 + check_head_against_root(spec, store, finalized_root) + # Copy into another variable so we can use `state` later + another_state = state.copy() + + # Create a fork with justification that is a descendant of the finalized block + # Do not add these blocks to the store yet + next_epoch(spec, another_state) + signed_blocks = [] + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, False, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 6 + assert another_state.current_justified_checkpoint.epoch == 3 + assert another_state.finalized_checkpoint.epoch == 2 + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, True, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 7 + assert another_state.current_justified_checkpoint.epoch == 6 + assert another_state.finalized_checkpoint.epoch == 2 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + assert store.justified_checkpoint.epoch == 6 + assert store.finalized_checkpoint.epoch == 2 + last_block = signed_blocks[-1] + last_block_root = last_block.message.hash_tree_root() + ancestor_at_finalized_slot = spec.get_ancestor(store, last_block_root, finalized_block.slot) + if is_post_eip7732(spec): + ancestor_at_finalized_slot = ancestor_at_finalized_slot.root + assert ancestor_at_finalized_slot == finalized_root + + # Create a fork with lower justification that also finalizes our chosen block + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + assert state.current_justified_checkpoint.epoch == 5 + # Check that store's finalized checkpoint is updated + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 4 + # Check that store's justified checkpoint is not updated + assert store.justified_checkpoint.epoch == 6 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_justified_update_always_if_better(spec, state): + """ + Check that the store updates it's justified checkpoint with any higher justified checkpoint. + This testcase checks that the store's justified checkpoint is updated when we input a block that has: + - a lower finalized checkpoint than the store's finalized checkpoint, and + - a higher justified checkpoint than the store's justified checkpoint + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # We'll eventually make the current head block the finalized block + if is_post_eip7732(spec): + finalized_root = spec.get_head(store).root + else: + finalized_root = spec.get_head(store) + finalized_block = store.blocks[finalized_root] + assert spec.compute_epoch_at_slot(finalized_block.slot) == 4 + check_head_against_root(spec, store, finalized_root) + # Copy into another variable to use later + another_state = state.copy() + + # Create a fork with lower justification that also finalizes our chosen block + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 5 + assert state.finalized_checkpoint.epoch == store.finalized_checkpoint.epoch == 4 + + # Create a fork with higher justification that is a descendant of the finalized block + # Do not add these blocks to the store yet + next_epoch(spec, another_state) + signed_blocks = [] + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, False, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 6 + assert another_state.current_justified_checkpoint.epoch == 3 + assert another_state.finalized_checkpoint.epoch == 2 + _, signed_blocks_temp, another_state = next_epoch_with_attestations( + spec, another_state, True, False + ) + signed_blocks += signed_blocks_temp + assert spec.compute_epoch_at_slot(another_state.slot) == 7 + assert another_state.current_justified_checkpoint.epoch == 6 + assert another_state.finalized_checkpoint.epoch == 2 + + # Now add the blocks & check that justification update was triggered + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + assert store.justified_checkpoint.epoch == 6 + assert store.finalized_checkpoint.epoch == 4 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_pull_up_past_epoch_block(spec, state): + """ + Check that the store pulls-up a block from the past epoch to realize it's justification & finalization information + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Create a chain within epoch 4 that contains a justification for epoch 4 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) == 4 + + # Tick store to the next epoch + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Add the previously created chain to the store and check for updates + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert store.justified_checkpoint.epoch == 4 + assert store.finalized_checkpoint.epoch == 3 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_not_pull_up_current_epoch_block(spec, state): + """ + Check that the store does not pull-up a block from the current epoch if the previous epoch is not justified + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Skip to the next epoch + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(state.slot) == 5 + + # Create a chain within epoch 5 that contains a justification for epoch 5 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) == 5 + + # Add the previously created chain to the store and check that store does not apply pull-up updates + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets([MINIMAL], reason="too slow") +def test_pull_up_on_tick(spec, state): + """ + Check that the store pulls-up current epoch tips on the on_tick transition to the next epoch + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Skip to the next epoch + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(state.slot) == 5 + + # Create a chain within epoch 5 that contains a justification for epoch 5 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) == 5 + + # Add the previously created chain to the store and check that store does not apply pull-up updates, + # since the previous epoch was not justified + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert store.justified_checkpoint.epoch == 3 + assert store.finalized_checkpoint.epoch == 2 + + # Now tick the store to the next epoch and check that pull-up tip updates were applied + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(state.slot) == 6 + assert store.justified_checkpoint.epoch == 5 + # There's no new finality, so no finality updates expected + assert store.finalized_checkpoint.epoch == 3 + + yield "steps", test_steps diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_reorg.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_reorg.py new file mode 100644 index 0000000000..202d3df627 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_reorg.py @@ -0,0 +1,615 @@ +from eth2spec.test.context import ( + spec_state_test, + with_altair_and_later, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, + get_valid_attestations_at_slot, + state_transition_with_full_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block, + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + MINIMAL, +) +from eth2spec.test.helpers.fork_choice import ( + add_attestations, + apply_next_epoch_with_attestations, + check_head_against_root, + find_next_justifying_slot, + get_genesis_forkchoice_store_and_block, + get_store_full_state, + is_ready_to_justify, + on_tick_and_append_step, + payload_state_transition, + payload_state_transition_no_store, + tick_and_add_block, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, + state_transition_and_sign_block, + transition_to, +) + +TESTING_PRESETS = [MINIMAL] + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_simple_attempted_reorg_without_enough_ffg_votes(spec, state): + """ + [Case 1] + + { epoch 4 }{ epoch 5 } + [c4]<--[a]<--[-]<--[y] + ↑____[-]<--[z] + + At c4, c3 is the latest justified checkpoint (or something earlier) + + The block y doesn't have enough votes to justify c4. + The block z also doesn't have enough votes to justify c4. + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # create block_a, it needs 2 more full blocks to justify epoch 4 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) + for signed_block in signed_blocks[:-2]: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + if is_post_eip7732(spec): + head_root = spec.get_head(store).root + state = store.execution_payload_states[head_root].copy() + else: + head_root = spec.get_head(store) + state = store.block_states[head_root].copy() + + assert state.current_justified_checkpoint.epoch == 3 + next_slot(spec, state) + state_a = state.copy() + + # to test the "no withholding" situation, temporarily store the blocks in lists + signed_blocks_of_y = [] + signed_blocks_of_z = [] + + # add an empty block on chain y + block_y = build_empty_block_for_next_slot(spec, state) + signed_block_y = state_transition_and_sign_block(spec, state, block_y) + signed_blocks_of_y.append(signed_block_y) + payload_state_transition_no_store(spec, state, signed_block_y.message) + + # chain y has some on-chain attestations, but not enough to justify c4 + signed_block_y = state_transition_with_full_block(spec, state, True, True) + assert not is_ready_to_justify(spec, state) + signed_blocks_of_y.append(signed_block_y) + assert store.justified_checkpoint.epoch == 3 + + state = state_a.copy() + signed_block_z = None + # add one block on chain z, which is not enough to justify c4 + attestation = get_valid_attestation(spec, state, slot=state.slot, signed=True) + block_z = build_empty_block_for_next_slot(spec, state) + block_z.body.attestations = [attestation] + signed_block_z = state_transition_and_sign_block(spec, state, block_z) + signed_blocks_of_z.append(signed_block_z) + payload_state_transition_no_store(spec, state, signed_block_z.message) + + # add an empty block on chain z + block_z = build_empty_block_for_next_slot(spec, state) + signed_block_z = state_transition_and_sign_block(spec, state, block_z) + signed_blocks_of_z.append(signed_block_z) + + # ensure z couldn't justify c4 + assert not is_ready_to_justify(spec, state) + + # apply blocks to store + # (i) slot block_a.slot + 1 + signed_block_y = signed_blocks_of_y.pop(0) + yield from tick_and_add_block(spec, store, signed_block_y, test_steps) + payload_state_transition(spec, store, signed_block_y.message) + # apply block of chain `z` + signed_block_z = signed_blocks_of_z.pop(0) + yield from tick_and_add_block(spec, store, signed_block_z, test_steps) + payload_state_transition(spec, store, signed_block_z.message) + + # (ii) slot block_a.slot + 2 + # apply block of chain `z` + signed_block_z = signed_blocks_of_z.pop(0) + yield from tick_and_add_block(spec, store, signed_block_z, test_steps) + payload_state_transition(spec, store, signed_block_z.message) + # apply block of chain `y` + signed_block_y = signed_blocks_of_y.pop(0) + yield from tick_and_add_block(spec, store, signed_block_y, test_steps) + payload_state_transition(spec, store, signed_block_y.message) + # chain `y` remains the winner since it arrives earlier than `z` + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + assert len(signed_blocks_of_y) == len(signed_blocks_of_z) == 0 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + + # tick to the prior of the epoch boundary + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1 + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + # chain `y` reminds the winner + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + + # to next block + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + yield "steps", test_steps + + +def _run_delayed_justification(spec, state, attempted_reorg, is_justifying_previous_epoch): + """ """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 2 + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + if is_justifying_previous_epoch: + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, False, False, test_steps=test_steps + ) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 + else: + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + if is_justifying_previous_epoch: + # try to find the block that can justify epoch 3 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, False, True) + else: + # try to find the block that can justify epoch 4 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, True) + + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) + for signed_block in signed_blocks: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + if is_post_eip7732(spec): + state = store.execution_payload_states[spec.get_head(store).root].copy() + else: + state = store.block_states[spec.get_head(store)].copy() + if is_justifying_previous_epoch: + assert state.current_justified_checkpoint.epoch == 2 + else: + assert state.current_justified_checkpoint.epoch == 3 + + assert is_ready_to_justify(spec, state) + state_b = state.copy() + + # add chain y + if is_justifying_previous_epoch: + signed_block_y = state_transition_with_full_block(spec, state, False, True) + else: + signed_block_y = state_transition_with_full_block(spec, state, True, True) + yield from tick_and_add_block(spec, store, signed_block_y, test_steps) + payload_state_transition(spec, store, signed_block_y.message) + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + if is_justifying_previous_epoch: + assert store.justified_checkpoint.epoch == 2 + else: + assert store.justified_checkpoint.epoch == 3 + + # add attestations of y + temp_state = state.copy() + next_slot(spec, temp_state) + attestations_for_y = list( + get_valid_attestations_at_slot(temp_state, spec, signed_block_y.message.slot) + ) + current_time = temp_state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + yield from add_attestations(spec, store, attestations_for_y, test_steps) + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + + if attempted_reorg: + # add chain z + state = state_b.copy() + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1 + transition_to(spec, state, slot) + block_z = build_empty_block_for_next_slot(spec, state) + assert spec.compute_epoch_at_slot(block_z.slot) == 5 + signed_block_z = state_transition_and_sign_block(spec, state, block_z) + yield from tick_and_add_block(spec, store, signed_block_z, test_steps) + payload_state_transition(spec, store, signed_block_z.message) + else: + # next epoch + state = state_b.copy() + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + + # no reorg + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + if is_justifying_previous_epoch: + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + else: + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_simple_attempted_reorg_delayed_justification_current_epoch(spec, state): + """ + [Case 2] + + { epoch 4 }{ epoch 5 } + [c4]<--[b]<--[y] + ↑______________[z] + At c4, c3 is the latest justified checkpoint (or something earlier) + + block_b: the block that can justify c4. + z: the child of block of x at the first slot of epoch 5. + block z can reorg the chain from block y. + """ + yield from _run_delayed_justification( + spec, state, attempted_reorg=True, is_justifying_previous_epoch=False + ) + + +def _run_include_votes_of_another_empty_chain( + spec, state, enough_ffg, is_justifying_previous_epoch +): + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 2 + for _ in range(2): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + if is_justifying_previous_epoch: + # build chain with head in epoch 3 and justified checkpoint in epoch 2 + block_a = build_empty_block_for_next_slot(spec, state) + signed_block_a = state_transition_and_sign_block(spec, state, block_a) + yield from tick_and_add_block(spec, store, signed_block_a, test_steps) + payload_state_transition(spec, store, signed_block_a.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 3 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 + else: + # build chain with head in epoch 4 and justified checkpoint in epoch 3 + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + signed_block_a = state_transition_with_full_block(spec, state, True, True) + yield from tick_and_add_block(spec, store, signed_block_a, test_steps) + payload_state_transition(spec, store, signed_block_a.message) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + root_a = signed_block_a.message.hash_tree_root() + check_head_against_root(spec, store, root_a) + state = get_store_full_state(spec, store, root_a).copy() + state_a = state.copy() + + if is_justifying_previous_epoch: + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 3 + assert spec.compute_epoch_at_slot(state.slot) == 3 + assert state.current_justified_checkpoint.epoch == 2 + else: + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert spec.compute_epoch_at_slot(state.slot) == 4 + assert state.current_justified_checkpoint.epoch == 3 + + if is_justifying_previous_epoch: + # try to find the block that can justify epoch 3 by including only previous epoch attestations + _, justifying_slot = find_next_justifying_slot(spec, state, False, True) + assert spec.compute_epoch_at_slot(justifying_slot) == 4 + else: + # try to find the block that can justify epoch 4 by including current epoch attestations + _, justifying_slot = find_next_justifying_slot(spec, state, True, True) + assert spec.compute_epoch_at_slot(justifying_slot) == 4 + + last_slot_of_z = justifying_slot if enough_ffg else justifying_slot - 1 + last_slot_of_y = justifying_slot if is_justifying_previous_epoch else last_slot_of_z - 1 + + # to test the "no withholding" situation, temporarily store the blocks in lists + signed_blocks_of_y = [] + + # build an empty chain to the slot prior epoch boundary + states_of_empty_chain = [] + for slot in range(state.slot + 1, last_slot_of_y + 1): + block = build_empty_block(spec, state, slot=slot) + signed_block = state_transition_and_sign_block(spec, state, block) + payload_state_transition_no_store(spec, state, signed_block.message) + states_of_empty_chain.append(state.copy()) + signed_blocks_of_y.append(signed_block) + signed_block_y = signed_blocks_of_y[-1] + assert spec.compute_epoch_at_slot(signed_block_y.message.slot) == 4 + + # create 2/3 votes for the empty chain + attestations_for_y = [] + # target_is_current = not is_justifying_previous_epoch + attestations = list(get_valid_attestations_at_slot(state, spec, state_a.slot)) + attestations_for_y.append(attestations) + for state in states_of_empty_chain: + attestations = list(get_valid_attestations_at_slot(state, spec, state.slot)) + attestations_for_y.append(attestations) + + state = state_a.copy() + signed_block_z = None + for slot in range(state_a.slot + 1, last_slot_of_z + 1): + # apply chain y, the empty chain + if slot <= last_slot_of_y and len(signed_blocks_of_y) > 0: + signed_block_y = signed_blocks_of_y.pop(0) + assert signed_block_y.message.slot == slot + yield from tick_and_add_block(spec, store, signed_block_y, test_steps) + payload_state_transition(spec, store, signed_block_y.message) + + # apply chain z, a fork chain that includes these attestations_for_y + block = build_empty_block(spec, state, slot=slot) + if len(attestations_for_y) > 0 and ( + (not is_justifying_previous_epoch) + or (is_justifying_previous_epoch and attestations_for_y[0][0].data.slot == slot - 5) + ): + block.body.attestations = attestations_for_y.pop(0) + signed_block_z = state_transition_and_sign_block(spec, state, block) + payload_state_transition_no_store(spec, state, block) + if signed_block_y != signed_block_z: + yield from tick_and_add_block(spec, store, signed_block_z, test_steps) + payload_state_transition(spec, store, signed_block_z.message) + if is_ready_to_justify(spec, state): + break + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert spec.compute_epoch_at_slot(signed_block_y.message.slot) == 4 + assert spec.compute_epoch_at_slot(signed_block_z.message.slot) == 4 + + # y is not filtered out & wins the LMD competition, so y should be the head + y_voting_source_epoch = spec.get_voting_source( + store, signed_block_y.message.hash_tree_root() + ).epoch + if is_justifying_previous_epoch: + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 2 + assert y_voting_source_epoch == 2 + assert y_voting_source_epoch == store.justified_checkpoint.epoch + else: + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert y_voting_source_epoch == 3 + assert y_voting_source_epoch == store.justified_checkpoint.epoch + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + + if enough_ffg: + assert is_ready_to_justify(spec, state) + else: + assert not is_ready_to_justify(spec, state) + + # to next epoch + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + + y_voting_source_epoch = spec.get_voting_source( + store, signed_block_y.message.hash_tree_root() + ).epoch + if is_justifying_previous_epoch: + # y is filtered out & so z should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert y_voting_source_epoch == 2 + assert y_voting_source_epoch != store.justified_checkpoint.epoch + assert not ( + y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)) + ) + check_head_against_root(spec, store, signed_block_z.message.hash_tree_root()) + elif enough_ffg: + # y is not filtered out & wins the LMD competition, so y should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert y_voting_source_epoch == 3 + assert y_voting_source_epoch != store.justified_checkpoint.epoch + assert y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)) + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + else: + # y is not filtered out & wins the LMD competition, so y should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert y_voting_source_epoch == 3 + assert y_voting_source_epoch == store.justified_checkpoint.epoch + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + + # to next epoch + next_epoch(spec, state) + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + + y_voting_source_epoch = spec.get_voting_source( + store, signed_block_y.message.hash_tree_root() + ).epoch + if is_justifying_previous_epoch: + # y is filtered out & so z should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert y_voting_source_epoch == 2 + assert y_voting_source_epoch != store.justified_checkpoint.epoch + assert not ( + y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)) + ) + check_head_against_root(spec, store, signed_block_z.message.hash_tree_root()) + elif enough_ffg: + # y is filtered out & so z should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 4 + assert y_voting_source_epoch == 3 + assert y_voting_source_epoch != store.justified_checkpoint.epoch + assert not ( + y_voting_source_epoch + 2 >= spec.compute_epoch_at_slot(spec.get_current_slot(store)) + ) + check_head_against_root(spec, store, signed_block_z.message.hash_tree_root()) + else: + # y is not filtered out & wins the LMD competition, so y should be the head + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + assert y_voting_source_epoch == 3 + assert y_voting_source_epoch == store.justified_checkpoint.epoch + check_head_against_root(spec, store, signed_block_y.message.hash_tree_root()) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch(spec, state): + """ + [Case 3] + """ + yield from _run_include_votes_of_another_empty_chain( + spec, state, enough_ffg=True, is_justifying_previous_epoch=False + ) + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_include_votes_another_empty_chain_without_enough_ffg_votes_current_epoch(spec, state): + """ + [Case 4] + """ + yield from _run_include_votes_of_another_empty_chain( + spec, state, enough_ffg=False, is_justifying_previous_epoch=False + ) + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_delayed_justification_current_epoch(spec, state): + """ + [Case 5] + + To compare with ``test_simple_attempted_reorg_delayed_justification_current_epoch``, + this is the basic case if there is no chain z + + { epoch 4 }{ epoch 5 } + [c4]<--[b]<--[y] + + At c4, c3 is the latest justified checkpoint. + + block_b: the block that can justify c4. + """ + yield from _run_delayed_justification( + spec, state, attempted_reorg=False, is_justifying_previous_epoch=False + ) + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_delayed_justification_previous_epoch(spec, state): + """ + [Case 6] + + Similar to ``test_delayed_justification_current_epoch``, + but includes attestations during epoch N to justify checkpoint N-1. + + { epoch 3 }{ epoch 4 }{ epoch 5 } + [c3]<---------------[c4]---[b]<---------------------------------[y] + + """ + yield from _run_delayed_justification( + spec, state, attempted_reorg=False, is_justifying_previous_epoch=True + ) + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_simple_attempted_reorg_delayed_justification_previous_epoch(spec, state): + """ + [Case 7] + + Similar to ``test_simple_attempted_reorg_delayed_justification_current_epoch``, + but includes attestations during epoch N to justify checkpoint N-1. + + { epoch 3 }{ epoch 4 }{ epoch 5 } + [c3]<---------------[c4]<--[b]<--[y] + ↑______________[z] + + At c4, c2 is the latest justified checkpoint. + + block_b: the block that can justify c3. + z: the child of block of x at the first slot of epoch 5. + block z can reorg the chain from block y. + """ + yield from _run_delayed_justification( + spec, state, attempted_reorg=True, is_justifying_previous_epoch=True + ) + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_include_votes_another_empty_chain_with_enough_ffg_votes_previous_epoch(spec, state): + """ + [Case 8] + + Similar to ``test_include_votes_another_empty_chain_with_enough_ffg_votes_current_epoch``, + but includes attestations during epoch N to justify checkpoint N-1. + + """ + yield from _run_include_votes_of_another_empty_chain( + spec, state, enough_ffg=True, is_justifying_previous_epoch=True + ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_withholding.py b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_withholding.py new file mode 100644 index 0000000000..3468b0e336 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/fork_choice/test_withholding.py @@ -0,0 +1,230 @@ +from eth2spec.test.context import ( + spec_state_test, + with_altair_and_later, + with_presets, +) +from eth2spec.test.helpers.attestations import ( + state_transition_with_full_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.constants import ( + MINIMAL, +) +from eth2spec.test.helpers.fork_choice import ( + apply_next_epoch_with_attestations, + check_head_against_root, + find_next_justifying_slot, + get_genesis_forkchoice_store_and_block, + get_store_full_state, + on_tick_and_append_step, + payload_state_transition, + payload_state_transition_no_store, + tick_and_add_block, +) +from eth2spec.test.helpers.forks import is_post_eip7732 +from eth2spec.test.helpers.state import ( + next_epoch, + state_transition_and_sign_block, +) + +TESTING_PRESETS = [MINIMAL] + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_withholding_attack(spec, state): + """ """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Create the attack block that includes justifying attestations for epoch 4 + # This block is withheld & revealed only in epoch 5 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, False) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) + assert len(signed_blocks) > 1 + signed_attack_block = signed_blocks[-1] + for signed_block in signed_blocks[:-1]: + current_root = signed_block.message.hash_tree_root() + yield from tick_and_add_block(spec, store, signed_block, test_steps) + payload_state_transition(spec, store, signed_block.message) + check_head_against_root(spec, store, current_root) + head_root = signed_blocks[-2].message.hash_tree_root() + check_head_against_root(spec, store, head_root) + assert spec.compute_epoch_at_slot(state.slot) == 4 + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + state = get_store_full_state(spec, store, head_root).copy() + + # Create an honest chain in epoch 5 that includes the justifying attestations from the attack block + next_epoch(spec, state) + assert spec.compute_epoch_at_slot(state.slot) == 5 + assert state.current_justified_checkpoint.epoch == 3 + # Create two blocks in the honest chain with full attestations, and add to the store + honest_state = state.copy() + for _ in range(2): + signed_block = state_transition_with_full_block(spec, honest_state, True, False) + yield from tick_and_add_block(spec, store, signed_block, test_steps) + honest_state = payload_state_transition(spec, store, signed_block.message).copy() + # Create final block in the honest chain that includes the justifying attestations from the attack block + honest_block = build_empty_block_for_next_slot(spec, honest_state) + honest_block.body.attestations = signed_attack_block.message.body.attestations + signed_honest_block = state_transition_and_sign_block(spec, honest_state, honest_block) + # Add the honest block to the store + yield from tick_and_add_block(spec, store, signed_honest_block, test_steps) + payload_state_transition(spec, store, signed_honest_block.message) + check_head_against_root(spec, store, signed_honest_block.message.hash_tree_root()) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Tick to the next slot so proposer boost is not a factor in choosing the head + current_time = (honest_block.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + check_head_against_root(spec, store, signed_honest_block.message.hash_tree_root()) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Upon revealing the withheld attack block, the honest block should still be the head + yield from tick_and_add_block(spec, store, signed_attack_block, test_steps) + check_head_against_root(spec, store, signed_honest_block.message.hash_tree_root()) + # As a side effect of the pull-up logic, the attack block is pulled up and store.justified_checkpoint is updated + assert store.justified_checkpoint.epoch == 4 + + # Even after going to the next epoch, the honest block should remain the head + slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + check_head_against_root(spec, store, signed_honest_block.message.hash_tree_root()) + + yield "steps", test_steps + + +@with_altair_and_later +@spec_state_test +@with_presets(TESTING_PRESETS, reason="too slow") +def test_withholding_attack_unviable_honest_chain(spec, state): + """ + Checks that the withholding attack succeeds for one epoch if the honest chain has a voting source beyond + two epochs ago. + """ + test_steps = [] + # Initialization + store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state) + yield "anchor_state", state + yield "anchor_block", anchor_block + current_time = state.slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert store.time == current_time + + next_epoch(spec, state) + on_tick_and_append_step( + spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, test_steps + ) + + # Fill epoch 1 to 3 + for _ in range(3): + state, store, _ = yield from apply_next_epoch_with_attestations( + spec, state, store, True, True, test_steps=test_steps + ) + + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 4 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + next_epoch(spec, state) + assert spec.compute_epoch_at_slot(state.slot) == 5 + + # Create the attack block that includes justifying attestations for epoch 5 + # This block is withheld & revealed only in epoch 6 + signed_blocks, justifying_slot = find_next_justifying_slot(spec, state, True, False) + assert spec.compute_epoch_at_slot(justifying_slot) == spec.get_current_epoch(state) + assert len(signed_blocks) > 1 + signed_attack_block = signed_blocks[-1] + for signed_block in signed_blocks[:-1]: + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + state = get_store_full_state(spec, store, signed_block.message.hash_tree_root()).copy() + assert spec.compute_epoch_at_slot(state.slot) == 5 + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 5 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Create an honest chain in epoch 6 that includes the justifying attestations from the attack block + next_epoch(spec, state) + assert spec.compute_epoch_at_slot(state.slot) == 6 + assert state.current_justified_checkpoint.epoch == 3 + # Create two blocks in the honest chain with full attestations, and add to the store + for _ in range(2): + signed_block = state_transition_with_full_block(spec, state, True, False) + payload_state_transition_no_store(spec, state, signed_block.message) + assert state.current_justified_checkpoint.epoch == 3 + yield from tick_and_add_block(spec, store, signed_block, test_steps) + check_head_against_root(spec, store, signed_block.message.hash_tree_root()) + payload_state_transition(spec, store, signed_block.message) + # Create final block in the honest chain that includes the justifying attestations from the attack block + honest_block = build_empty_block_for_next_slot(spec, state) + honest_block.body.attestations = signed_attack_block.message.body.attestations + signed_honest_block = state_transition_and_sign_block(spec, state, honest_block) + honest_block_root = signed_honest_block.message.hash_tree_root() + assert state.current_justified_checkpoint.epoch == 3 + # Add the honest block to the store + yield from tick_and_add_block(spec, store, signed_honest_block, test_steps) + payload_state_transition(spec, store, signed_honest_block.message) + current_epoch = spec.compute_epoch_at_slot(spec.get_current_slot(store)) + assert current_epoch == 6 + # assert store.voting_source[honest_block_root].epoch == 3 + check_head_against_root(spec, store, honest_block_root) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Tick to the next slot so proposer boost is not a factor in choosing the head + current_time = (honest_block.slot + 1) * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + check_head_against_root(spec, store, honest_block_root) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 6 + assert state.current_justified_checkpoint.epoch == store.justified_checkpoint.epoch == 3 + + # Upon revealing the withheld attack block, it should become the head + # Except in EIP-7732 in which it's parent becomes head because of the + # attestations during the attacker's block's committee. + yield from tick_and_add_block(spec, store, signed_attack_block, test_steps) + payload_state_transition(spec, store, signed_attack_block.message) + # The attack block is pulled up and store.justified_checkpoint is updated + assert store.justified_checkpoint.epoch == 5 + if is_post_eip7732(spec): + attack_block_root = signed_attack_block.message.parent_root + else: + attack_block_root = signed_attack_block.message.hash_tree_root() + check_head_against_root(spec, store, attack_block_root) + + # After going to the next epoch, the honest block should become the head + slot = spec.get_current_slot(store) + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) + current_time = slot * spec.config.SECONDS_PER_SLOT + store.genesis_time + on_tick_and_append_step(spec, store, current_time, test_steps) + assert spec.compute_epoch_at_slot(spec.get_current_slot(store)) == 7 + # assert store.voting_source[honest_block_root].epoch == 5 + check_head_against_root(spec, store, honest_block_root) + + yield "steps", test_steps 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 53dd3760ad..b6d2b7ebb3 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -1,15 +1,19 @@ from eth2spec.test.context import ( - is_post_altair, + PHASE0, single_phase, spec_test, + with_phases, with_presets, - with_all_phases, ) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.deposits import ( prepare_full_genesis_deposits, prepare_random_genesis_deposits, ) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_electra, +) def get_post_altair_description(spec): @@ -17,19 +21,22 @@ def get_post_altair_description(spec): def eth1_init_data(eth1_block_hash, eth1_timestamp): - yield 'eth1', { - 'eth1_block_hash': '0x' + eth1_block_hash.hex(), - 'eth1_timestamp': int(eth1_timestamp), - } + yield ( + "eth1", + { + "eth1_block_hash": "0x" + eth1_block_hash.hex(), + "eth1_timestamp": int(eth1_timestamp), + }, + ) -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") def test_initialize_beacon_state_from_eth1(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT deposits, deposit_root, _ = prepare_full_genesis_deposits( @@ -39,11 +46,11 @@ def test_initialize_beacon_state_from_eth1(spec): signed=True, ) - eth1_block_hash = b'\x12' * 32 + 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 + yield "deposits", deposits # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) @@ -56,37 +63,45 @@ def test_initialize_beacon_state_from_eth1(spec): assert spec.get_total_active_balance(state) == deposit_count * spec.MAX_EFFECTIVE_BALANCE # yield state - yield 'state', state + yield "state", state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") def test_initialize_beacon_state_some_small_balances(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) + + if is_post_electra(spec): + max_effective_balance = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + else: + max_effective_balance = spec.MAX_EFFECTIVE_BALANCE main_deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( - spec, spec.MAX_EFFECTIVE_BALANCE, - deposit_count=main_deposit_count, signed=True, + spec, + max_effective_balance, + deposit_count=main_deposit_count, + signed=True, ) # For deposits above, and for another deposit_count, add a balance of EFFECTIVE_BALANCE_INCREMENT small_deposit_count = main_deposit_count * 2 small_deposits, deposit_root, _ = prepare_full_genesis_deposits( - spec, spec.MIN_DEPOSIT_AMOUNT, + spec, + spec.MIN_DEPOSIT_AMOUNT, deposit_count=small_deposit_count, signed=True, deposit_data_list=deposit_data_list, ) deposits = main_deposits + small_deposits - eth1_block_hash = b'\x12' * 32 + 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 + yield "deposits", deposits # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) @@ -97,30 +112,35 @@ def test_initialize_beacon_state_some_small_balances(spec): assert state.eth1_data.deposit_count == len(deposits) assert state.eth1_data.block_hash == eth1_block_hash # only main deposits participate to the active balance + # NOTE: they are pre-ELECTRA deposits with BLS_WITHDRAWAL_PREFIX, + # so `MAX_EFFECTIVE_BALANCE` is used assert spec.get_total_active_balance(state) == main_deposit_count * spec.MAX_EFFECTIVE_BALANCE # yield state - yield 'state', state + yield "state", state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") def test_initialize_beacon_state_one_topup_activation(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) # Submit all but one deposit as MAX_EFFECTIVE_BALANCE main_deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( - spec, spec.MAX_EFFECTIVE_BALANCE, - deposit_count=main_deposit_count, signed=True, + spec, + spec.MAX_EFFECTIVE_BALANCE, + deposit_count=main_deposit_count, + signed=True, ) # Submit last pubkey deposit as MAX_EFFECTIVE_BALANCE - MIN_DEPOSIT_AMOUNT partial_deposits, _, deposit_data_list = prepare_full_genesis_deposits( - spec, spec.MAX_EFFECTIVE_BALANCE - spec.MIN_DEPOSIT_AMOUNT, + spec, + spec.MAX_EFFECTIVE_BALANCE - spec.MIN_DEPOSIT_AMOUNT, deposit_count=1, min_pubkey_index=main_deposit_count, signed=True, @@ -129,7 +149,8 @@ def test_initialize_beacon_state_one_topup_activation(spec): # Top up thelast pubkey deposit as MIN_DEPOSIT_AMOUNT to complete the deposit top_up_deposits, _, _ = prepare_full_genesis_deposits( - spec, spec.MIN_DEPOSIT_AMOUNT, + spec, + spec.MIN_DEPOSIT_AMOUNT, deposit_count=1, min_pubkey_index=main_deposit_count, signed=True, @@ -138,27 +159,27 @@ def test_initialize_beacon_state_one_topup_activation(spec): deposits = main_deposits + partial_deposits + top_up_deposits - eth1_block_hash = b'\x13' * 32 + eth1_block_hash = b"\x13" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits + yield "deposits", deposits # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) assert spec.is_valid_genesis_state(state) # yield state - yield 'state', state + yield "state", state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") def test_initialize_beacon_state_random_invalid_genesis(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) # Make a bunch of random deposits deposits, _, deposit_data_list = prepare_random_genesis_deposits( @@ -166,26 +187,26 @@ def test_initialize_beacon_state_random_invalid_genesis(spec): deposit_count=20, max_pubkey_index=10, ) - eth1_block_hash = b'\x14' * 32 + eth1_block_hash = b"\x14" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME + 1 yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits + yield "deposits", deposits # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) assert not spec.is_valid_genesis_state(state) - yield 'state', state + yield "state", state -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") def test_initialize_beacon_state_random_valid_genesis(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) # Make a bunch of random deposits random_deposits, _, deposit_data_list = prepare_random_genesis_deposits( @@ -201,18 +222,18 @@ def test_initialize_beacon_state_random_valid_genesis(spec): spec.MAX_EFFECTIVE_BALANCE, deposit_count=spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, signed=True, - deposit_data_list=deposit_data_list + deposit_data_list=deposit_data_list, ) deposits = random_deposits + full_deposits - eth1_block_hash = b'\x15' * 32 + eth1_block_hash = b"\x15" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME + 2 yield from eth1_init_data(eth1_block_hash, eth1_timestamp) - yield 'deposits', deposits + yield "deposits", deposits # initialize beacon_state state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) assert spec.is_valid_genesis_state(state) - yield 'state', state + yield "state", state 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 e17e72a4e5..092adb1897 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -1,14 +1,17 @@ from eth2spec.test.context import ( - is_post_altair, - spec_test, + PHASE0, single_phase, + spec_test, + with_phases, with_presets, - with_all_phases, ) from eth2spec.test.helpers.constants import MINIMAL from eth2spec.test.helpers.deposits import ( prepare_full_genesis_deposits, ) +from eth2spec.test.helpers.forks import ( + is_post_altair, +) def get_post_altair_description(spec): @@ -24,7 +27,7 @@ def create_valid_beacon_state(spec): signed=True, ) - eth1_block_hash = b'\x12' * 32 + eth1_block_hash = b"\x12" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME return spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) @@ -35,32 +38,32 @@ def run_is_valid_genesis_state(spec, state, valid=True): - genesis ('state') - is_valid ('is_valid') """ - yield 'genesis', state + yield "genesis", state is_valid = spec.is_valid_genesis_state(state) - yield 'is_valid', is_valid + yield "is_valid", is_valid assert is_valid == valid -@with_all_phases +@with_phases([PHASE0]) @spec_test @single_phase @with_presets([MINIMAL], reason="too slow") -def test_is_valid_genesis_state_true(spec): +def test_full_genesis_deposits(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) state = create_valid_beacon_state(spec) - yield from run_is_valid_genesis_state(spec, state, valid=True) + 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") -def test_is_valid_genesis_state_false_invalid_timestamp(spec): +def test_invalid_invalid_timestamp(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) state = create_valid_beacon_state(spec) state.genesis_time = spec.config.MIN_GENESIS_TIME - 1 @@ -68,27 +71,27 @@ def test_is_valid_genesis_state_false_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") -def test_is_valid_genesis_state_true_more_balance(spec): +def test_extra_balance(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) state = create_valid_beacon_state(spec) state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE + 1 - yield from run_is_valid_genesis_state(spec, state, valid=True) + 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") -def test_is_valid_genesis_state_true_one_more_validator(spec): +def test_one_more_validator(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1 deposits, _, _ = prepare_full_genesis_deposits( @@ -98,20 +101,20 @@ def test_is_valid_genesis_state_true_one_more_validator(spec): signed=True, ) - eth1_block_hash = b'\x12' * 32 + eth1_block_hash = b"\x12" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) - yield from run_is_valid_genesis_state(spec, state, valid=True) + 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") -def test_is_valid_genesis_state_false_not_enough_validator(spec): +def test_invalid_not_enough_validator_count(spec): if is_post_altair(spec): - yield 'description', 'meta', get_post_altair_description(spec) + yield "description", "meta", get_post_altair_description(spec) deposit_count = spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 deposits, _, _ = prepare_full_genesis_deposits( @@ -121,7 +124,7 @@ def test_is_valid_genesis_state_false_not_enough_validator(spec): signed=True, ) - eth1_block_hash = b'\x12' * 32 + eth1_block_hash = b"\x12" * 32 eth1_timestamp = spec.config.MIN_GENESIS_TIME state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) diff --git a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py index 89a457bed9..0df30d6acd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/random/test_random.py @@ -4,19 +4,17 @@ See the README for that generator for more information. """ -from eth2spec.test.helpers.constants import PHASE0 from eth2spec.test.context import ( + always_bls, misc_balances_in_default_range_with_many_validators, - with_phases, - zero_activation_threshold, only_generator, -) -from eth2spec.test.context import ( - always_bls, + single_phase, spec_test, with_custom_state, - single_phase, + with_phases, + zero_activation_threshold, ) +from eth2spec.test.helpers.constants import PHASE0 from eth2spec.test.utils.randomized_block_tests import ( run_generated_randomized_test, ) @@ -26,7 +24,7 @@ @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -40,7 +38,53 @@ def test_randomized_0(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -52,7 +96,7 @@ def test_randomized_0(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -66,7 +110,53 @@ def test_randomized_1(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -78,7 +168,7 @@ def test_randomized_1(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -92,7 +182,53 @@ def test_randomized_2(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -104,7 +240,7 @@ def test_randomized_2(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -118,7 +254,53 @@ def test_randomized_3(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -130,7 +312,7 @@ def test_randomized_3(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -144,7 +326,53 @@ def test_randomized_4(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -156,7 +384,7 @@ def test_randomized_4(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -170,7 +398,53 @@ def test_randomized_5(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -182,7 +456,7 @@ def test_randomized_5(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -196,7 +470,53 @@ def test_randomized_6(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -208,7 +528,7 @@ def test_randomized_6(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -222,7 +542,53 @@ def test_randomized_7(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'validation': 'validate_is_not_leaking', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "validation": "validate_is_not_leaking", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -234,7 +600,7 @@ def test_randomized_7(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -248,7 +614,53 @@ def test_randomized_8(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -260,7 +672,7 @@ def test_randomized_8(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -274,7 +686,53 @@ def test_randomized_9(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -286,7 +744,7 @@ def test_randomized_9(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -300,7 +758,53 @@ def test_randomized_10(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -312,7 +816,7 @@ def test_randomized_10(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -326,7 +830,53 @@ def test_randomized_11(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:last_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -338,7 +888,7 @@ def test_randomized_11(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -352,7 +902,53 @@ def test_randomized_12(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'last_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "last_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -364,7 +960,7 @@ def test_randomized_12(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -378,7 +974,53 @@ def test_randomized_13(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:random_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'random_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "random_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -390,7 +1032,7 @@ def test_randomized_13(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -404,7 +1046,53 @@ def test_randomized_14(spec, state): # epochs:0,slots:0,with-block:no_block # epochs:0,slots:penultimate_slot_in_epoch,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 0, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 'penultimate_slot_in_epoch', 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 0, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": "penultimate_slot_in_epoch", + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, @@ -416,7 +1104,7 @@ def test_randomized_14(spec, state): @with_phases([PHASE0]) @with_custom_state( balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold + threshold_fn=zero_activation_threshold, ) @spec_test @single_phase @@ -430,7 +1118,53 @@ def test_randomized_15(spec, state): # epochs:1,slots:0,with-block:no_block # epochs:0,slots:0,with-block:no_block # epochs:0,slots:0,with-block:random_block - scenario = {'transitions': [{'epochs_to_skip': 'epochs_until_leak', 'validation': 'validate_is_leaking', 'slots_to_skip': 0, 'block_producer': 'no_block'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}, {'epochs_to_skip': 1, 'slots_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'slots_to_skip': 0, 'epochs_to_skip': 0, 'block_producer': 'no_block', 'validation': 'no_op_validation'}, {'block_producer': 'random_block', 'epochs_to_skip': 0, 'slots_to_skip': 0, 'validation': 'no_op_validation'}], 'state_randomizer': 'randomize_state'} # noqa: E501 + scenario = { + "transitions": [ + { + "epochs_to_skip": "epochs_until_leak", + "validation": "validate_is_leaking", + "slots_to_skip": 0, + "block_producer": "no_block", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + { + "epochs_to_skip": 1, + "slots_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "slots_to_skip": 0, + "epochs_to_skip": 0, + "block_producer": "no_block", + "validation": "no_op_validation", + }, + { + "block_producer": "random_block", + "epochs_to_skip": 0, + "slots_to_skip": 0, + "validation": "no_op_validation", + }, + ], + "state_randomizer": "randomize_state", + } # noqa: E501 yield from run_generated_randomized_test( spec, state, diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py index 511849f8f2..2fbfa2b697 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_basic.py @@ -1,6 +1,6 @@ -from eth2spec.test.context import with_all_phases, with_phases, spec_state_test -from eth2spec.test.helpers.constants import PHASE0 import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.test.context import spec_state_test, with_all_phases, with_phases +from eth2spec.test.helpers.constants import PHASE0 @with_all_phases @@ -66,7 +66,9 @@ def test_some_very_low_effective_balances_that_attested(spec, state): @with_all_phases @spec_state_test def test_some_very_low_effective_balances_that_did_not_attest(spec, state): - yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest(spec, state) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( + spec, state + ) # @@ -80,7 +82,8 @@ def test_some_very_low_effective_balances_that_did_not_attest(spec, state): @spec_state_test def test_full_half_correct_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=True, correct_head=False, fraction_incorrect=0.5, @@ -91,7 +94,8 @@ def test_full_half_correct_target_incorrect_head(spec, state): @spec_state_test def test_full_correct_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=True, correct_head=False, fraction_incorrect=1.0, @@ -102,7 +106,8 @@ def test_full_correct_target_incorrect_head(spec, state): @spec_state_test def test_full_half_incorrect_target_incorrect_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=False, correct_head=False, fraction_incorrect=0.5, @@ -113,7 +118,8 @@ def test_full_half_incorrect_target_incorrect_head(spec, state): @spec_state_test def test_full_half_incorrect_target_correct_head(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=False, correct_head=True, fraction_incorrect=0.5, diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py index 178b4ff77b..b1fbe7b326 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_leak.py @@ -1,7 +1,7 @@ -from eth2spec.test.context import with_all_phases, with_phases, spec_state_test +import eth2spec.test.helpers.rewards as rewards_helpers +from eth2spec.test.context import spec_state_test, with_all_phases, with_phases from eth2spec.test.helpers.constants import PHASE0 from eth2spec.test.helpers.rewards import leaking -import eth2spec.test.helpers.rewards as rewards_helpers @with_all_phases @@ -78,7 +78,9 @@ def test_some_very_low_effective_balances_that_attested_leak(spec, state): @spec_state_test @leaking() def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): - yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest(spec, state) + yield from rewards_helpers.run_test_some_very_low_effective_balances_that_did_not_attest( + spec, state + ) # @@ -93,7 +95,8 @@ def test_some_very_low_effective_balances_that_did_not_attest_leak(spec, state): @leaking() def test_full_half_correct_target_incorrect_head_leak(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=True, correct_head=False, fraction_incorrect=0.5, @@ -105,7 +108,8 @@ def test_full_half_correct_target_incorrect_head_leak(spec, state): @leaking() def test_full_correct_target_incorrect_head_leak(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=True, correct_head=False, fraction_incorrect=1.0, @@ -117,7 +121,8 @@ def test_full_correct_target_incorrect_head_leak(spec, state): @leaking() def test_full_half_incorrect_target_incorrect_head_leak(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=False, correct_head=False, fraction_incorrect=0.5, @@ -129,7 +134,8 @@ def test_full_half_incorrect_target_incorrect_head_leak(spec, state): @leaking() def test_full_half_incorrect_target_correct_head_leak(spec, state): yield from rewards_helpers.run_test_full_fraction_incorrect( - spec, state, + spec, + state, correct_target=False, correct_head=True, fraction_incorrect=0.5, diff --git a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py index e6112557ac..7e1d5c1741 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py +++ b/tests/core/pyspec/eth2spec/test/phase0/rewards/test_random.py @@ -1,18 +1,19 @@ from random import Random +import eth2spec.test.helpers.rewards as rewards_helpers from eth2spec.test.context import ( - with_all_phases, - spec_test, + low_balances, + misc_balances, + single_phase, spec_state_test, + spec_test, + with_all_phases, with_custom_state, - single_phase, - low_balances, misc_balances, ) -import eth2spec.test.helpers.rewards as rewards_helpers from eth2spec.test.helpers.random import ( - randomize_state, patch_state_to_non_leaking, randomize_attestation_participation, + randomize_state, ) from eth2spec.test.helpers.state import has_active_balance_differential, next_epoch from eth2spec.test.helpers.voluntary_exits import get_unslashed_exited_validators @@ -74,7 +75,9 @@ def test_full_random_low_balances_1(spec, state): @with_all_phases -@with_custom_state(balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=misc_balances, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @spec_test @single_phase def test_full_random_misc_balances(spec, state): @@ -121,7 +124,9 @@ def test_full_random_without_leak_and_current_exit_0(spec, state): # patch exited validators to exit in the current epoch validator = state.validators[index] validator.exit_epoch = current_epoch - validator.withdrawable_epoch = current_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + validator.withdrawable_epoch = ( + current_epoch + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + ) # re-randomize attestation participation for the current epoch randomize_attestation_participation(spec, state, rng) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 6866a86ae7..994b370b09 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -1,51 +1,73 @@ from random import Random -from eth2spec.utils import bls -from eth2spec.test.helpers.state import ( - get_balance, state_transition_and_sign_block, - next_slot, next_epoch, next_epoch_via_block, +from eth2spec.test.context import ( + always_bls, + dump_skipping_message, + expect_assertion_error, + large_validator_set, + single_phase, + spec_state_test, + spec_test, + with_all_phases, + with_custom_state, + with_phases, + with_presets, +) +from eth2spec.test.helpers.attestations import get_valid_attestation +from eth2spec.test.helpers.attester_slashings import ( + get_indexed_attestation_participants, + get_max_attester_slashings, + get_valid_attester_slashing, + get_valid_attester_slashing_by_indices, ) from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, build_empty_block, + build_empty_block, + build_empty_block_for_next_slot, sign_block, transition_unsigned_block, ) -from eth2spec.test.helpers.keys import pubkeys -from eth2spec.test.helpers.attester_slashings import ( - get_valid_attester_slashing_by_indices, - get_valid_attester_slashing, - get_indexed_attestation_participants, -) -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect -from eth2spec.test.helpers.attestations import get_valid_attestation +from eth2spec.test.helpers.constants import MINIMAL, PHASE0 from eth2spec.test.helpers.deposits import prepare_state_and_deposit -from eth2spec.test.helpers.execution_payload import build_empty_execution_payload -from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits +from eth2spec.test.helpers.execution_payload import ( + build_empty_execution_payload, + build_empty_signed_execution_payload_header, + compute_el_block_hash, + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.forks import ( + is_post_altair, + is_post_bellatrix, + is_post_capella, + is_post_eip7732, + is_post_electra, +) +from eth2spec.test.helpers.keys import pubkeys from eth2spec.test.helpers.multi_operations import ( run_slash_and_exit, run_test_full_random_operations, ) +from eth2spec.test.helpers.proposer_slashings import ( + check_proposer_slashing_effect, + get_valid_proposer_slashing, +) +from eth2spec.test.helpers.state import ( + get_balance, + next_epoch, + next_epoch_via_block, + next_slot, + state_transition_and_sign_block, +) from eth2spec.test.helpers.sync_committee import ( compute_committee_indices, compute_sync_committee_participant_reward_and_penalty, ) -from eth2spec.test.helpers.constants import PHASE0, MINIMAL -from eth2spec.test.context import ( - spec_test, spec_state_test, dump_skipping_message, - with_phases, with_all_phases, single_phase, - expect_assertion_error, always_bls, - disable_process_reveal_deadlines, - with_presets, - with_custom_state, - large_validator_set, - is_post_altair, - is_post_merge, -) +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits +from eth2spec.utils import bls @with_all_phases @spec_state_test -def test_prev_slot_block_transition(spec, state): +def test_invalid_prev_slot_block_transition(spec, state): # Go to clean slot spec.process_slots(state, state.slot + 1) # Make a block for it @@ -54,32 +76,32 @@ def test_prev_slot_block_transition(spec, state): # Transition to next slot, above block will not be invalid on top of new state. spec.process_slots(state, state.slot + 1) - yield 'pre', state + yield "pre", state # State is beyond block slot, but the block can still be realistic when invalid. # Try the transition, and update the state root to where it is halted. Then sign with the supposed proposer. expect_assertion_error(lambda: transition_unsigned_block(spec, state, block)) block.state_root = state.hash_tree_root() signed_block = sign_block(spec, state, block, proposer_index=proposer_index) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @spec_state_test -def test_same_slot_block_transition(spec, state): +def test_invalid_same_slot_block_transition(spec, state): # Same slot on top of pre-state, but move out of slot 0 first. spec.process_slots(state, state.slot + 1) block = build_empty_block(spec, state, slot=state.slot) - yield 'pre', state + yield "pre", state assert state.slot == block.slot signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @@ -89,14 +111,14 @@ def test_empty_block_transition(spec, state): pre_eth1_votes = len(state.eth1_data_votes) pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert len(state.eth1_data_votes) == pre_eth1_votes + 1 assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root @@ -104,24 +126,28 @@ def test_empty_block_transition(spec, state): @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @single_phase def test_empty_block_transition_large_validator_set(spec, state): pre_slot = state.slot pre_eth1_votes = len(state.eth1_data_votes) pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state)) - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert len(state.eth1_data_votes) == pre_eth1_votes + 1 assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root @@ -143,6 +169,9 @@ def process_and_sign_block_without_header_validations(spec, state, block): state_root=spec.Bytes32(), body_root=block.body.hash_tree_root(), ) + if is_post_bellatrix(spec) and not is_post_eip7732(spec): + if spec.is_execution_enabled(state, block.body): + spec.process_execution_payload(state, block.body, spec.EXECUTION_ENGINE) # Perform rest of process_block transitions spec.process_randao(state, block.body) @@ -150,11 +179,8 @@ def process_and_sign_block_without_header_validations(spec, state, block): spec.process_operations(state, block.body) if is_post_altair(spec): spec.process_sync_aggregate(state, block.body.sync_aggregate) - if is_post_merge(spec): - if spec.is_execution_enabled(state, block.body): - spec.process_execution_payload(state, block.body.execution_payload, spec.EXECUTION_ENGINE) - # Insert post-state rot + # Insert post-state root block.state_root = state.hash_tree_root() # Sign block @@ -163,10 +189,10 @@ def process_and_sign_block_without_header_validations(spec, state, block): @with_phases([PHASE0]) @spec_state_test -def test_proposal_for_genesis_slot(spec, state): +def test_invalid_proposal_for_genesis_slot(spec, state): assert state.slot == spec.GENESIS_SLOT - yield 'pre', state + yield "pre", state block = build_empty_block(spec, state, spec.GENESIS_SLOT) block.parent_root = state.latest_block_header.hash_tree_root() @@ -174,20 +200,22 @@ def test_proposal_for_genesis_slot(spec, state): # Show that normal path through transition fails failed_state = state.copy() expect_assertion_error( - lambda: spec.state_transition(failed_state, spec.SignedBeaconBlock(message=block), validate_result=False) + lambda: spec.state_transition( + failed_state, spec.SignedBeaconBlock(message=block), validate_result=False + ) ) # Artificially bypass the restriction in the state transition to transition and sign block for test vectors signed_block = process_and_sign_block_without_header_validations(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @spec_state_test -def test_parent_from_same_slot(spec, state): - yield 'pre', state +def test_invalid_parent_from_same_slot(spec, state): + yield "pre", state parent_block = build_empty_block_for_next_slot(spec, state) signed_parent_block = state_transition_and_sign_block(spec, state, parent_block) @@ -195,27 +223,43 @@ def test_parent_from_same_slot(spec, state): child_block = parent_block.copy() child_block.parent_root = state.latest_block_header.hash_tree_root() - if is_post_merge(spec): - randao_mix = spec.compute_randao_mix(state, child_block.body.randao_reveal) - child_block.body.execution_payload = build_empty_execution_payload(spec, state, randao_mix) + if is_post_eip7732(spec): + child_block.body.signed_execution_payload_header = ( + build_empty_signed_execution_payload_header(spec, state) + ) + elif is_post_bellatrix(spec): + child_block.body.execution_payload = build_empty_execution_payload(spec, state) + + child_block.parent_root = state.latest_block_header.hash_tree_root() + if is_post_eip7732(spec): + payload = build_empty_execution_payload(spec, state) + child_block.body.signed_execution_payload_header.message.block_hash = compute_el_block_hash( + spec, payload, state + ) + elif is_post_bellatrix(spec): + child_block.body.execution_payload.block_hash = compute_el_block_hash_for_block( + spec, child_block + ) # Show that normal path through transition fails failed_state = state.copy() expect_assertion_error( - lambda: spec.state_transition(failed_state, spec.SignedBeaconBlock(message=child_block), validate_result=False) + lambda: spec.state_transition( + failed_state, spec.SignedBeaconBlock(message=child_block), validate_result=False + ) ) # Artificially bypass the restriction in the state transition to transition and sign block for test vectors signed_child_block = process_and_sign_block_without_header_validations(spec, state, child_block) - yield 'blocks', [signed_parent_block, signed_child_block] - yield 'post', None + yield "blocks", [signed_parent_block, signed_child_block] + yield "post", None @with_all_phases @spec_state_test -def test_invalid_state_root(spec, state): - yield 'pre', state +def test_invalid_incorrect_state_root(spec, state): + yield "pre", state block = build_empty_block_for_next_slot(spec, state) block.state_root = b"\xaa" * 32 @@ -223,48 +267,49 @@ def test_invalid_state_root(spec, state): expect_assertion_error(lambda: spec.state_transition(state, signed_block)) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @spec_state_test @always_bls -def test_zero_block_sig(spec, state): - yield 'pre', state +def test_invalid_all_zeroed_sig(spec, state): + yield "pre", state block = build_empty_block_for_next_slot(spec, state) invalid_signed_block = spec.SignedBeaconBlock(message=block) expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block)) - yield 'blocks', [invalid_signed_block] - yield 'post', None + yield "blocks", [invalid_signed_block] + yield "post", None @with_all_phases @spec_state_test @always_bls -def test_invalid_block_sig(spec, state): - yield 'pre', state +def test_invalid_incorrect_block_sig(spec, state): + yield "pre", state block = build_empty_block_for_next_slot(spec, state) - domain = spec.get_domain(state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot)) + domain = spec.get_domain( + state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot) + ) signing_root = spec.compute_signing_root(block, domain) invalid_signed_block = spec.SignedBeaconBlock( - message=block, - signature=bls.Sign(123456, signing_root) + message=block, signature=bls.Sign(123456, signing_root) ) expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block)) - yield 'blocks', [invalid_signed_block] - yield 'post', None + yield "blocks", [invalid_signed_block] + yield "post", None @with_all_phases @spec_state_test @always_bls -def test_invalid_proposer_index_sig_from_expected_proposer(spec, state): - yield 'pre', state +def test_invalid_incorrect_proposer_index_sig_from_expected_proposer(spec, state): + yield "pre", state block = build_empty_block_for_next_slot(spec, state) expect_proposer_index = block.proposer_index @@ -278,15 +323,15 @@ def test_invalid_proposer_index_sig_from_expected_proposer(spec, state): expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block)) - yield 'blocks', [invalid_signed_block] - yield 'post', None + yield "blocks", [invalid_signed_block] + yield "post", None @with_all_phases @spec_state_test @always_bls -def test_invalid_proposer_index_sig_from_proposer_index(spec, state): - yield 'pre', state +def test_invalid_incorrect_proposer_index_sig_from_proposer_index(spec, state): + yield "pre", state block = build_empty_block_for_next_slot(spec, state) @@ -299,22 +344,22 @@ def test_invalid_proposer_index_sig_from_proposer_index(spec, state): expect_assertion_error(lambda: spec.state_transition(state, invalid_signed_block)) - yield 'blocks', [invalid_signed_block] - yield 'post', None + yield "blocks", [invalid_signed_block] + yield "post", None @with_all_phases @spec_state_test def test_skipped_slots(spec, state): pre_slot = state.slot - yield 'pre', state + yield "pre", state block = build_empty_block(spec, state, state.slot + 4) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.slot == block.slot assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32() @@ -326,14 +371,14 @@ def test_skipped_slots(spec, state): @spec_state_test def test_empty_epoch_transition(spec, state): pre_slot = state.slot - yield 'pre', state + yield "pre", state block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.slot == block.slot for slot in range(pre_slot, state.slot): @@ -341,21 +386,25 @@ def test_empty_epoch_transition(spec, state): @with_all_phases -@with_presets([MINIMAL], - reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated") +@with_presets( + [MINIMAL], + reason="mainnet config leads to larger validator set than limit of public/private keys pre-generated", +) @spec_test -@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE) +@with_custom_state( + balances_fn=large_validator_set, threshold_fn=lambda spec: spec.config.EJECTION_BALANCE +) @single_phase def test_empty_epoch_transition_large_validator_set(spec, state): pre_slot = state.slot - yield 'pre', state + yield "pre", state block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.slot == block.slot for slot in range(pre_slot, state.slot): @@ -366,19 +415,21 @@ def test_empty_epoch_transition_large_validator_set(spec, state): @spec_state_test def test_empty_epoch_transition_not_finalizing(spec, state): if spec.SLOTS_PER_EPOCH > 8: - return dump_skipping_message("Skip mainnet config for saving time." - " Minimal config suffice to cover the target-of-test.") + return dump_skipping_message( + "Skip mainnet config for saving time." + " Minimal config suffice to cover the target-of-test." + ) # copy for later balance lookups. pre_balances = list(state.balances) - yield 'pre', state + yield "pre", state spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH * 5)) block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.slot == block.slot assert state.finalized_checkpoint.epoch < spec.get_current_epoch(state) - 4 @@ -389,13 +440,14 @@ def test_empty_epoch_transition_not_finalizing(spec, state): @with_all_phases @spec_state_test def test_proposer_self_slashing(spec, state): - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) assert not state.validators[block.proposer_index].slashed proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True) + spec, state, slashed_index=block.proposer_index, signed_1=True, signed_2=True + ) block.body.proposer_slashings.append(proposer_slashing) # The header is processed *before* the block body: @@ -404,8 +456,8 @@ def test_proposer_self_slashing(spec, state): # The proposer slashed themselves. assert state.validators[block.proposer_index].slashed - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state @with_all_phases @@ -418,7 +470,7 @@ def test_proposer_slashing(spec, state): assert not state.validators[slashed_index].slashed - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -428,51 +480,61 @@ def test_proposer_slashing(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state check_proposer_slashing_effect(spec, pre_state, state, slashed_index, block) @with_all_phases @spec_state_test -def test_double_same_proposer_slashings_same_block(spec, state): +def test_invalid_duplicate_proposer_slashings_same_block(spec, state): proposer_slashing = get_valid_proposer_slashing(spec, state, signed_1=True, signed_2=True) slashed_index = proposer_slashing.signed_header_1.message.proposer_index assert not state.validators[slashed_index].slashed - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) block.body.proposer_slashings = [proposer_slashing, proposer_slashing] signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @spec_state_test -def test_double_similar_proposer_slashings_same_block(spec, state): +def test_invalid_similar_proposer_slashings_same_block(spec, state): slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # Same validator, but different slashable offences in the same block - proposer_slashing_1 = get_valid_proposer_slashing(spec, state, random_root=b'\xaa' * 32, - slashed_index=slashed_index, - signed_1=True, signed_2=True) - proposer_slashing_2 = get_valid_proposer_slashing(spec, state, random_root=b'\xbb' * 32, - slashed_index=slashed_index, - signed_1=True, signed_2=True) + proposer_slashing_1 = get_valid_proposer_slashing( + spec, + state, + random_root=b"\xaa" * 32, + slashed_index=slashed_index, + signed_1=True, + signed_2=True, + ) + proposer_slashing_2 = get_valid_proposer_slashing( + spec, + state, + random_root=b"\xbb" * 32, + slashed_index=slashed_index, + signed_1=True, + signed_2=True, + ) assert not state.validators[slashed_index].slashed - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) block.body.proposer_slashings = [proposer_slashing_1, proposer_slashing_2] signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @@ -486,12 +548,12 @@ def test_multiple_different_proposer_slashings_same_block(spec, state): slashed_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] assert not state.validators[slashed_index].slashed - proposer_slashing = get_valid_proposer_slashing(spec, state, - slashed_index=slashed_index, - signed_1=True, signed_2=True) + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slashed_index, signed_1=True, signed_2=True + ) proposer_slashings.append(proposer_slashing) - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -501,8 +563,8 @@ def test_multiple_different_proposer_slashings_same_block(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state for proposer_slashing in proposer_slashings: slashed_index = proposer_slashing.signed_header_1.message.proposer_index @@ -534,7 +596,7 @@ def test_attester_slashing(spec, state): assert not any(state.validators[i].slashed for i in slashed_indices) - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -544,17 +606,19 @@ def test_attester_slashing(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state check_attester_slashing_effect(spec, pre_state, state, slashed_indices) @with_all_phases @spec_state_test -def test_duplicate_attester_slashing(spec, state): - if spec.MAX_ATTESTER_SLASHINGS < 2: - return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block") +def test_invalid_duplicate_attester_slashing_same_block(spec, state): + if get_max_attester_slashings(spec) < 2: + return dump_skipping_message( + "Skip test if config cannot handle multiple AttesterSlashings per block" + ) attester_slashing = get_valid_attester_slashing(spec, state, signed_1=True, signed_2=True) attester_slashings = [attester_slashing, attester_slashing.copy()] @@ -562,7 +626,7 @@ def test_duplicate_attester_slashing(spec, state): assert not any(state.validators[i].slashed for i in slashed_indices) - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -572,17 +636,17 @@ def test_duplicate_attester_slashing(spec, state): signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None -# TODO All AttesterSlashing tests should be adopted for SHARDING and later but helper support is not yet there - @with_all_phases @spec_state_test def test_multiple_attester_slashings_no_overlap(spec, state): - if spec.MAX_ATTESTER_SLASHINGS < 2: - return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block") + if get_max_attester_slashings(spec) < 2: + return dump_skipping_message( + "Skip test if config cannot handle multiple AttesterSlashings per block" + ) # copy for later balance lookups. pre_state = state.copy() @@ -591,18 +655,24 @@ def test_multiple_attester_slashings_no_overlap(spec, state): half_length = len(full_indices) // 2 attester_slashing_1 = get_valid_attester_slashing_by_indices( - spec, state, - full_indices[:half_length], signed_1=True, signed_2=True, + spec, + state, + full_indices[:half_length], + signed_1=True, + signed_2=True, ) attester_slashing_2 = get_valid_attester_slashing_by_indices( - spec, state, - full_indices[half_length:], signed_1=True, signed_2=True, + spec, + state, + full_indices[half_length:], + signed_1=True, + signed_2=True, ) attester_slashings = [attester_slashing_1, attester_slashing_2] assert not any(state.validators[i].slashed for i in full_indices) - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -612,8 +682,8 @@ def test_multiple_attester_slashings_no_overlap(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state check_attester_slashing_effect(spec, pre_state, state, full_indices) @@ -621,8 +691,10 @@ def test_multiple_attester_slashings_no_overlap(spec, state): @with_all_phases @spec_state_test def test_multiple_attester_slashings_partial_overlap(spec, state): - if spec.MAX_ATTESTER_SLASHINGS < 2: - return dump_skipping_message("Skip test if config cannot handle multiple AttesterSlashings per block") + if get_max_attester_slashings(spec) < 2: + return dump_skipping_message( + "Skip test if config cannot handle multiple AttesterSlashings per block" + ) # copy for later balance lookups. pre_state = state.copy() @@ -631,18 +703,24 @@ def test_multiple_attester_slashings_partial_overlap(spec, state): one_third_length = len(full_indices) // 3 attester_slashing_1 = get_valid_attester_slashing_by_indices( - spec, state, - full_indices[:one_third_length * 2], signed_1=True, signed_2=True, + spec, + state, + full_indices[: one_third_length * 2], + signed_1=True, + signed_2=True, ) attester_slashing_2 = get_valid_attester_slashing_by_indices( - spec, state, - full_indices[one_third_length:], signed_1=True, signed_2=True, + spec, + state, + full_indices[one_third_length:], + signed_1=True, + signed_2=True, ) attester_slashings = [attester_slashing_1, attester_slashing_2] assert not any(state.validators[i].slashed for i in full_indices) - yield 'pre', state + yield "pre", state # # Add to state via block transition @@ -652,8 +730,8 @@ def test_multiple_attester_slashings_partial_overlap(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state check_attester_slashing_effect(spec, pre_state, state, full_indices) @@ -672,11 +750,13 @@ def test_proposer_after_inactive_index(spec, state): proposer_index = spec.get_beacon_proposer_index(state) if proposer_index > inactive_index: # found a proposer that has a higher index than a disabled validator - yield 'pre', state + yield "pre", state # test if the proposer can be recognized correctly after the inactive validator - signed_block = state_transition_and_sign_block(spec, state, build_empty_block_for_next_slot(spec, state)) - yield 'blocks', [signed_block] - yield 'post', state + signed_block = state_transition_and_sign_block( + spec, state, build_empty_block_for_next_slot(spec, state) + ) + yield "blocks", [signed_block] + yield "post", state break next_slot(spec, state) @@ -699,27 +779,29 @@ def test_high_proposer_index(spec, state): proposer_index = spec.get_beacon_proposer_index(state) if proposer_index >= active_count: # found a proposer that has a higher index than the active validator count - yield 'pre', state + yield "pre", state # test if the proposer can be recognized correctly, even while it has a high index. - signed_block = state_transition_and_sign_block(spec, state, build_empty_block_for_next_slot(spec, state)) - yield 'blocks', [signed_block] - yield 'post', state + signed_block = state_transition_and_sign_block( + spec, state, build_empty_block_for_next_slot(spec, state) + ) + yield "blocks", [signed_block] + yield "post", state break next_slot(spec, state) @with_all_phases @spec_state_test -def test_expected_deposit_in_block(spec, state): +def test_invalid_only_increase_deposit_count(spec, state): # Make the state expect a deposit, then don't provide it. state.eth1_data.deposit_count += 1 - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) - yield 'blocks', [signed_block] - yield 'post', None + yield "blocks", [signed_block] + yield "post", None @with_all_phases @@ -732,21 +814,47 @@ def test_deposit_in_block(spec, state): amount = spec.MAX_EFFECTIVE_BALANCE deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) block.body.deposits.append(deposit) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state + + if is_post_electra(spec): + balance = state.pending_deposits[0].amount + else: + balance = get_balance(state, validator_index) assert len(state.validators) == initial_registry_len + 1 assert len(state.balances) == initial_balances_len + 1 - assert get_balance(state, validator_index) == spec.MAX_EFFECTIVE_BALANCE + assert balance == spec.MAX_EFFECTIVE_BALANCE assert state.validators[validator_index].pubkey == pubkeys[validator_index] +@with_all_phases +@spec_state_test +def test_invalid_duplicate_deposit_same_block(spec, state): + validator_index = len(state.validators) + amount = spec.MAX_EFFECTIVE_BALANCE + deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True) + + yield "pre", state + + block = build_empty_block_for_next_slot(spec, state) + + # The same deposit of the same validator + for _ in range(2): + block.body.deposits.append(deposit) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=True) + + yield "blocks", [signed_block] + yield "post", None + + @with_all_phases @spec_state_test def test_deposit_top_up(spec, state): @@ -759,15 +867,15 @@ def test_deposit_top_up(spec, state): validator_pre_balance = get_balance(state, validator_index) pre_state = state.copy() - yield 'pre', pre_state + yield "pre", pre_state block = build_empty_block_for_next_slot(spec, state) block.body.deposits.append(deposit) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert len(state.validators) == initial_registry_len assert len(state.balances) == initial_balances_len @@ -775,17 +883,23 @@ def test_deposit_top_up(spec, state): # Altair introduces sync committee (sm) reward and penalty sync_committee_reward = sync_committee_penalty = 0 if is_post_altair(spec): - committee_indices = compute_committee_indices(spec, state, state.current_sync_committee) + committee_indices = compute_committee_indices(state, state.current_sync_committee) committee_bits = block.body.sync_aggregate.sync_committee_bits - sync_committee_reward, sync_committee_penalty = compute_sync_committee_participant_reward_and_penalty( - spec, - pre_state, - validator_index, - committee_indices, - committee_bits, + sync_committee_reward, sync_committee_penalty = ( + compute_sync_committee_participant_reward_and_penalty( + spec, + pre_state, + validator_index, + committee_indices, + committee_bits, + ) ) - assert get_balance(state, validator_index) == ( + balance = get_balance(state, validator_index) + if is_post_electra(spec): + balance += state.pending_deposits[0].amount + + assert balance == ( validator_pre_balance + amount + sync_committee_reward - sync_committee_penalty ) @@ -795,13 +909,13 @@ def test_deposit_top_up(spec, state): def test_attestation(spec, state): next_epoch(spec, state) - yield 'pre', state + yield "pre", state - attestation_block = build_empty_block(spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY) + attestation_block = build_empty_block( + spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + ) index = 0 - # if spec.fork == SHARDING: - # TODO add shard data to block to vote on attestation = get_valid_attestation(spec, state, index=index, signed=True) @@ -817,31 +931,84 @@ def test_attestation(spec, state): # Epoch transition should move to previous_epoch_attestations pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations) else: - pre_current_epoch_participation_root = spec.hash_tree_root(state.current_epoch_participation) + pre_current_epoch_participation_root = spec.hash_tree_root( + state.current_epoch_participation + ) epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block) - yield 'blocks', [signed_attestation_block, signed_epoch_block] - yield 'post', state + yield "blocks", [signed_attestation_block, signed_epoch_block] + yield "post", state if not is_post_altair(spec): assert len(state.current_epoch_attestations) == 0 - assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root + assert ( + spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root + ) else: for index in range(len(state.validators)): assert state.current_epoch_participation[index] == spec.ParticipationFlags(0b0000_0000) - assert spec.hash_tree_root(state.previous_epoch_participation) == pre_current_epoch_participation_root + assert ( + spec.hash_tree_root(state.previous_epoch_participation) + == pre_current_epoch_participation_root + ) -# After SHARDING is enabled, a committee is computed for SHARD_COMMITTEE_PERIOD slots ago, -# exceeding the minimal-config randao mixes memory size. -# Applies to all voluntary-exit sanity block tests. -# TODO: when integrating SHARDING tests, voluntary-exit tests may need to change. +@with_all_phases +@spec_state_test +def test_duplicate_attestation_same_block(spec, state): + next_epoch(spec, state) + + yield "pre", state + + attestation_block = build_empty_block( + spec, state, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + ) + + index = 0 + + attestation = get_valid_attestation(spec, state, index=index, signed=True) + + if not is_post_altair(spec): + pre_current_attestations_len = len(state.current_epoch_attestations) + + # Add to state via block transition + for _ in range(2): + attestation_block.body.attestations.append(attestation) + signed_attestation_block = state_transition_and_sign_block(spec, state, attestation_block) + + if not is_post_altair(spec): + assert len(state.current_epoch_attestations) == pre_current_attestations_len + 2 + # Epoch transition should move to previous_epoch_attestations + pre_current_attestations_root = spec.hash_tree_root(state.current_epoch_attestations) + else: + pre_current_epoch_participation_root = spec.hash_tree_root( + state.current_epoch_participation + ) + + epoch_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) + signed_epoch_block = state_transition_and_sign_block(spec, state, epoch_block) + + yield "blocks", [signed_attestation_block, signed_epoch_block] + yield "post", state + + if not is_post_altair(spec): + assert len(state.current_epoch_attestations) == 0 + assert ( + spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root + ) + else: + for index in range(len(state.validators)): + assert state.current_epoch_participation[index] == spec.ParticipationFlags(0b0000_0000) + assert ( + spec.hash_tree_root(state.previous_epoch_participation) + == pre_current_epoch_participation_root + ) + @with_all_phases @spec_state_test -@disable_process_reveal_deadlines def test_voluntary_exit(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] @@ -849,7 +1016,7 @@ def test_voluntary_exit(spec, state): state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH signed_exits = prepare_signed_exits(spec, state, [validator_index]) - yield 'pre', state + yield "pre", state # Add to state via block transition initiate_exit_block = build_empty_block_for_next_slot(spec, state) @@ -862,15 +1029,15 @@ def test_voluntary_exit(spec, state): exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_exit_block = state_transition_and_sign_block(spec, state, exit_block) - yield 'blocks', [signed_initiate_exit_block, signed_exit_block] - yield 'post', state + yield "blocks", [signed_initiate_exit_block, signed_exit_block] + yield "post", state assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH @with_all_phases @spec_state_test -def test_double_validator_exit_same_block(spec, state): +def test_invalid_duplicate_validator_exit_same_block(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit @@ -878,30 +1045,30 @@ def test_double_validator_exit_same_block(spec, state): # Same index tries to exit twice, but should only be able to do so once. signed_exits = prepare_signed_exits(spec, state, [validator_index, validator_index]) - yield 'pre', state + yield "pre", state # Add to state via block transition initiate_exit_block = build_empty_block_for_next_slot(spec, state) initiate_exit_block.body.voluntary_exits = signed_exits - signed_initiate_exit_block = state_transition_and_sign_block(spec, state, initiate_exit_block, expect_fail=True) + signed_initiate_exit_block = state_transition_and_sign_block( + spec, state, initiate_exit_block, expect_fail=True + ) - yield 'blocks', [signed_initiate_exit_block] - yield 'post', None + yield "blocks", [signed_initiate_exit_block] + yield "post", None @with_all_phases @spec_state_test -@disable_process_reveal_deadlines def test_multiple_different_validator_exits_same_block(spec, state): validator_indices = [ - spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] - for i in range(3) + spec.get_active_validator_indices(state, spec.get_current_epoch(state))[i] for i in range(3) ] # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit state.slot += spec.config.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH signed_exits = prepare_signed_exits(spec, state, validator_indices) - yield 'pre', state + yield "pre", state # Add to state via block transition initiate_exit_block = build_empty_block_for_next_slot(spec, state) @@ -915,8 +1082,8 @@ def test_multiple_different_validator_exits_same_block(spec, state): exit_block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_exit_block = state_transition_and_sign_block(spec, state, exit_block) - yield 'blocks', [signed_initiate_exit_block, signed_exit_block] - yield 'post', state + yield "blocks", [signed_initiate_exit_block, signed_exit_block] + yield "post", state for index in validator_indices: assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH @@ -924,7 +1091,6 @@ def test_multiple_different_validator_exits_same_block(spec, state): @with_all_phases @spec_state_test -@disable_process_reveal_deadlines def test_slash_and_exit_same_index(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] yield from run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) @@ -932,7 +1098,6 @@ def test_slash_and_exit_same_index(spec, state): @with_all_phases @spec_state_test -@disable_process_reveal_deadlines def test_slash_and_exit_diff_index(spec, state): slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] @@ -950,14 +1115,14 @@ def test_balance_driven_status_transitions(spec, state): # set validator balance to below ejection threshold state.validators[validator_index].effective_balance = spec.config.EJECTION_BALANCE - yield 'pre', state + yield "pre", state # trigger epoch transition block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.validators[validator_index].exit_epoch < spec.FAR_FUTURE_EPOCH @@ -969,19 +1134,32 @@ def test_balance_driven_status_transitions(spec, state): @always_bls def test_historical_batch(spec, state): state.slot += spec.SLOTS_PER_HISTORICAL_ROOT - (state.slot % spec.SLOTS_PER_HISTORICAL_ROOT) - 1 - pre_historical_roots_len = len(state.historical_roots) + pre_historical_roots = state.historical_roots.copy() + + if is_post_capella(spec): + pre_historical_summaries = state.historical_summaries.copy() - yield 'pre', state + yield "pre", state block = build_empty_block_for_next_slot(spec, state) signed_block = state_transition_and_sign_block(spec, state, block) - yield 'blocks', [signed_block] - yield 'post', state + yield "blocks", [signed_block] + yield "post", state assert state.slot == block.slot - assert spec.get_current_epoch(state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) == 0 - assert len(state.historical_roots) == pre_historical_roots_len + 1 + assert ( + spec.get_current_epoch(state) % (spec.SLOTS_PER_HISTORICAL_ROOT // spec.SLOTS_PER_EPOCH) + == 0 + ) + + # check history update + if is_post_capella(spec): + # Frozen `historical_roots` + assert state.historical_roots == pre_historical_roots + assert len(state.historical_summaries) == len(pre_historical_summaries) + 1 + else: + assert len(state.historical_roots) == len(pre_historical_roots) + 1 @with_all_phases @@ -992,11 +1170,11 @@ def test_eth1_data_votes_consensus(spec, state): offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1) state_transition_and_sign_block(spec, state, offset_block) - yield 'pre', state + yield "pre", state - a = b'\xaa' * 32 - b = b'\xbb' * 32 - c = b'\xcc' * 32 + a = b"\xaa" * 32 + b = b"\xbb" * 32 + c = b"\xcc" * 32 blocks = [] @@ -1016,8 +1194,8 @@ def test_eth1_data_votes_consensus(spec, state): signed_block = state_transition_and_sign_block(spec, state, block) blocks.append(signed_block) - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state assert state.eth1_data.block_hash == a assert state.slot % voting_period_slots == 0 @@ -1035,10 +1213,10 @@ def test_eth1_data_votes_no_consensus(spec, state): offset_block = build_empty_block(spec, state, slot=voting_period_slots - 1) state_transition_and_sign_block(spec, state, offset_block) - yield 'pre', state + yield "pre", state - a = b'\xaa' * 32 - b = b'\xbb' * 32 + a = b"\xaa" * 32 + b = b"\xbb" * 32 blocks = [] @@ -1052,8 +1230,8 @@ def test_eth1_data_votes_no_consensus(spec, state): assert len(state.eth1_data_votes) == voting_period_slots assert state.eth1_data.block_hash == pre_eth1_hash - yield 'blocks', blocks - yield 'post', state + yield "blocks", blocks + yield "post", state @with_all_phases diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py index 198ada6b90..b5d317551b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_slots.py @@ -1,5 +1,11 @@ -from eth2spec.test.helpers.state import get_state_root -from eth2spec.test.context import spec_state_test, with_all_phases +from eth2spec.test.context import ( + spec_state_test, + with_all_phases, +) +from eth2spec.test.helpers.forks import ( + is_post_capella, +) +from eth2spec.test.helpers.state import get_state_root, next_epoch, next_slot, transition_to @with_all_phases @@ -7,13 +13,13 @@ def test_slots_1(spec, state): pre_slot = state.slot pre_root = state.hash_tree_root() - yield 'pre', state + yield "pre", state slots = 1 - yield 'slots', int(slots) + yield "slots", int(slots) spec.process_slots(state, state.slot + slots) - yield 'post', state + yield "post", state assert state.slot == pre_slot + 1 assert get_state_root(spec, state, pre_slot) == pre_root @@ -21,31 +27,31 @@ def test_slots_1(spec, state): @with_all_phases @spec_state_test def test_slots_2(spec, state): - yield 'pre', state + yield "pre", state slots = 2 - yield 'slots', int(slots) + yield "slots", int(slots) spec.process_slots(state, state.slot + slots) - yield 'post', state + yield "post", state @with_all_phases @spec_state_test def test_empty_epoch(spec, state): - yield 'pre', state + yield "pre", state slots = spec.SLOTS_PER_EPOCH - yield 'slots', int(slots) + yield "slots", int(slots) spec.process_slots(state, state.slot + slots) - yield 'post', state + yield "post", state @with_all_phases @spec_state_test def test_double_empty_epoch(spec, state): - yield 'pre', state + yield "pre", state slots = spec.SLOTS_PER_EPOCH * 2 - yield 'slots', int(slots) + yield "slots", int(slots) spec.process_slots(state, state.slot + slots) - yield 'post', state + yield "post", state @with_all_phases @@ -53,8 +59,77 @@ def test_double_empty_epoch(spec, state): def test_over_epoch_boundary(spec, state): if spec.SLOTS_PER_EPOCH > 1: spec.process_slots(state, state.slot + (spec.SLOTS_PER_EPOCH // 2)) - yield 'pre', state + yield "pre", state slots = spec.SLOTS_PER_EPOCH - yield 'slots', int(slots) + yield "slots", int(slots) spec.process_slots(state, state.slot + slots) - yield 'post', state + yield "post", state + + +@with_all_phases +@spec_state_test +def test_historical_accumulator(spec, state): + pre_historical_roots = state.historical_roots.copy() + + if is_post_capella(spec): + pre_historical_summaries = state.historical_summaries.copy() + + yield "pre", state + slots = spec.SLOTS_PER_HISTORICAL_ROOT + yield "slots", int(slots) + spec.process_slots(state, state.slot + slots) + yield "post", state + + # check history update + if is_post_capella(spec): + # Frozen `historical_roots` + assert state.historical_roots == pre_historical_roots + assert len(state.historical_summaries) == len(pre_historical_summaries) + 1 + else: + assert len(state.historical_roots) == len(pre_historical_roots) + 1 + + +@with_all_phases +@spec_state_test +def test_balance_change_affects_proposer(spec, state): + # Brute-force an instance where a validator's balance change prevents it from proposing. + # We must brute-force this because sometimes the balance change doesn't make a difference. + # Give this approach 100 attempts to find such a case. + for _ in range(100): + original_state = state.copy() + + # Get the proposer of the first slot in the next epoch + next_epoch_state = state.copy() + next_epoch(spec, next_epoch_state) + proposer_next_epoch = spec.get_beacon_proposer_index(next_epoch_state) + + # Reduce the validator's balance, making it less likely to propose + # The validator's effective balance will be updated during epoch processing + spec.decrease_balance(state, proposer_next_epoch, 10 * spec.EFFECTIVE_BALANCE_INCREMENT) + + # Check if the proposer changed as a result of the balance change + tmp_state = state.copy() + next_epoch(spec, tmp_state) + if proposer_next_epoch != spec.get_beacon_proposer_index(tmp_state): + # Use this state + break + else: + # Try another state + state = original_state.copy() + next_epoch(spec, state) + + # Transition to the last slot of the current epoch + slot = state.slot + spec.SLOTS_PER_EPOCH - (state.slot % spec.SLOTS_PER_EPOCH) - 1 + transition_to(spec, state, slot) + + yield "pre", state + yield "slots", 1 + + # Transition to the next epoch + next_slot(spec, state) + + yield "post", state + + # Verify that the proposer changed because of the balance change + proposer_next_epoch_after_change = spec.get_beacon_proposer_index(state) + assert proposer_next_epoch != proposer_next_epoch_after_change diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py index 9007a778f1..866f27ede8 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_attestation.py @@ -1,9 +1,15 @@ -from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE -from eth2spec.test.helpers.state import transition_to, state_transition_and_sign_block, next_epoch, next_slot +from eth2spec.test.helpers.block import build_empty_block_for_next_slot +from eth2spec.test.helpers.constants import ALL_PHASES from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store +from eth2spec.test.helpers.forks import is_post_eip7732, is_post_electra +from eth2spec.test.helpers.state import ( + next_epoch, + next_slot, + state_transition_and_sign_block, + transition_to, +) def run_on_attestation(spec, state, store, attestation, valid=True): @@ -19,16 +25,18 @@ def run_on_attestation(spec, state, store, attestation, valid=True): spec.on_attestation(store, attestation) sample_index = indexed_attestation.attesting_indices[0] - if spec.fork in (PHASE0, ALTAIR, MERGE): + if is_post_eip7732(spec): + latest_message = spec.LatestMessage( + slot=attestation.data.slot, + root=attestation.data.beacon_block_root, + ) + elif spec.fork in ALL_PHASES: latest_message = spec.LatestMessage( epoch=attestation.data.target.epoch, root=attestation.data.beacon_block_root, ) - # elif spec.fork == SHARDING: TODO: check if vote count for shard blob increased as expected - assert ( - store.latest_messages[sample_index] == latest_message - ) + assert store.latest_messages[sample_index] == latest_message @with_all_phases @@ -142,7 +150,9 @@ def test_on_attestation_inconsistent_target_and_head(spec, state): epoch = spec.compute_epoch_at_slot(attestation.data.slot) # Set attestation target to be from chain 2 - attestation.data.target = spec.Checkpoint(epoch=epoch, root=spec.get_block_root(target_state_2, epoch)) + attestation.data.target = spec.Checkpoint( + epoch=epoch, root=spec.get_block_root(target_state_2, epoch) + ) sign_attestation(spec, state, attestation) assert attestation.data.target.epoch == spec.GENESIS_EPOCH + 1 @@ -325,6 +335,9 @@ def test_on_attestation_invalid_attestation(spec, state): attestation = get_valid_attestation(spec, state, slot=block.slot, signed=True) # make invalid by using an invalid committee index - attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH + if is_post_electra(spec): + attestation.committee_bits = spec.Bitvector[spec.MAX_COMMITTEES_PER_SLOT]() + else: + attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH run_on_attestation(spec, state, store, attestation, False) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py deleted file mode 100644 index 92382c884b..0000000000 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_block.py +++ /dev/null @@ -1,87 +0,0 @@ -from copy import deepcopy - -from eth2spec.utils.ssz.ssz_impl import hash_tree_root -from eth2spec.test.context import ( - spec_state_test, - with_all_phases, -) -from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, -) -from eth2spec.test.helpers.fork_choice import ( - get_genesis_forkchoice_store, - run_on_block, - apply_next_epoch_with_attestations, -) -from eth2spec.test.helpers.state import ( - next_epoch, - state_transition_and_sign_block, -) - - -@with_all_phases -@spec_state_test -def test_on_block_outside_safe_slots_and_multiple_better_justified(spec, state): - """ - NOTE: test_new_justified_is_later_than_store_justified also tests best_justified_checkpoint - """ - # Initialization - store = get_genesis_forkchoice_store(spec, state) - - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - state, store, last_signed_block = yield from apply_next_epoch_with_attestations( - spec, state, store, True, False) - last_block_root = hash_tree_root(last_signed_block.message) - - # NOTE: Mock fictitious justified checkpoint in store - store.justified_checkpoint = spec.Checkpoint( - epoch=spec.compute_epoch_at_slot(last_signed_block.message.slot), - root=spec.Root("0x4a55535449464945440000000000000000000000000000000000000000000000") - ) - - next_epoch(spec, state) - spec.on_tick(store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT) - - # Create new higher justified checkpoint not in branch of store's justified checkpoint - just_block = build_empty_block_for_next_slot(spec, state) - store.blocks[just_block.hash_tree_root()] = just_block - - # Step time past safe slots - spec.on_tick(store, store.time + spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED * spec.config.SECONDS_PER_SLOT) - assert spec.get_current_slot(store) % spec.SLOTS_PER_EPOCH >= spec.SAFE_SLOTS_TO_UPDATE_JUSTIFIED - - previously_finalized = store.finalized_checkpoint - previously_justified = store.justified_checkpoint - - # Add a series of new blocks with "better" justifications - best_justified_checkpoint = spec.Checkpoint(epoch=0) - for i in range(3, 0, -1): - # Mutate store - just_state = store.block_states[last_block_root] - new_justified = spec.Checkpoint( - epoch=previously_justified.epoch + i, - root=just_block.hash_tree_root(), - ) - if new_justified.epoch > best_justified_checkpoint.epoch: - best_justified_checkpoint = new_justified - - just_state.current_justified_checkpoint = new_justified - - block = build_empty_block_for_next_slot(spec, just_state) - signed_block = state_transition_and_sign_block(spec, deepcopy(just_state), block) - - # NOTE: Mock store so that the modified state could be accessed - parent_block = store.blocks[last_block_root].copy() - parent_block.state_root = just_state.hash_tree_root() - store.blocks[block.parent_root] = parent_block - store.block_states[block.parent_root] = just_state.copy() - assert block.parent_root in store.blocks.keys() - assert block.parent_root in store.block_states.keys() - - run_on_block(spec, store, signed_block) - - assert store.finalized_checkpoint == previously_finalized - assert store.justified_checkpoint == previously_justified - # ensure the best from the series was stored - assert store.best_justified_checkpoint == best_justified_checkpoint diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py index 0d9f6ddf54..16fba7e717 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/fork_choice/test_on_tick.py @@ -1,8 +1,8 @@ -from eth2spec.test.context import with_all_phases, spec_state_test -from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store +from eth2spec.test.context import spec_state_test, with_all_phases from eth2spec.test.helpers.block import ( build_empty_block_for_next_slot, ) +from eth2spec.test.helpers.fork_choice import get_genesis_forkchoice_store from eth2spec.test.helpers.state import ( next_epoch, state_transition_and_sign_block, @@ -18,7 +18,6 @@ def run_on_tick(spec, store, time, new_justified_checkpoint=False): assert store.time == time if new_justified_checkpoint: - assert store.justified_checkpoint == store.best_justified_checkpoint assert store.justified_checkpoint.epoch > previous_justified_checkpoint.epoch assert store.justified_checkpoint.root != previous_justified_checkpoint.root else: @@ -32,12 +31,12 @@ def test_basic(spec, state): run_on_tick(spec, store, store.time + 1) +""" @with_all_phases @spec_state_test def test_update_justified_single_on_store_finalized_chain(spec, state): store = get_genesis_forkchoice_store(spec, state) - # [Mock store.best_justified_checkpoint] # Create a block at epoch 1 next_epoch(spec, state) block = build_empty_block_for_next_slot(spec, state) @@ -58,8 +57,6 @@ def test_update_justified_single_on_store_finalized_chain(spec, state): state_transition_and_sign_block(spec, state, block) store.blocks[block.hash_tree_root()] = block store.block_states[block.hash_tree_root()] = state - # Mock store.best_justified_checkpoint - store.best_justified_checkpoint = state.current_justified_checkpoint.copy() run_on_tick( spec, @@ -67,6 +64,7 @@ def test_update_justified_single_on_store_finalized_chain(spec, state): store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, new_justified_checkpoint=True ) +""" @with_all_phases @@ -79,7 +77,7 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state): # Create a block at epoch 1 next_epoch(spec, state) block = build_empty_block_for_next_slot(spec, state) - block.body.graffiti = b'\x11' * 32 + block.body.graffiti = b"\x11" * 32 state_transition_and_sign_block(spec, state, block) store.blocks[block.hash_tree_root()] = block.copy() store.block_states[block.hash_tree_root()] = state.copy() @@ -89,12 +87,11 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state): root=block.hash_tree_root(), ) - # [Mock store.best_justified_checkpoint] # Create a block at epoch 1 state = init_state.copy() next_epoch(spec, state) block = build_empty_block_for_next_slot(spec, state) - block.body.graffiti = b'\x22' * 32 + block.body.graffiti = b"\x22" * 32 state_transition_and_sign_block(spec, state, block) store.blocks[block.hash_tree_root()] = block.copy() store.block_states[block.hash_tree_root()] = state.copy() @@ -112,79 +109,9 @@ def test_update_justified_single_not_on_store_finalized_chain(spec, state): state_transition_and_sign_block(spec, state, block) store.blocks[block.hash_tree_root()] = block.copy() store.block_states[block.hash_tree_root()] = state.copy() - # Mock store.best_justified_checkpoint - store.best_justified_checkpoint = state.current_justified_checkpoint.copy() run_on_tick( spec, store, store.genesis_time + state.slot * spec.config.SECONDS_PER_SLOT, ) - - -@with_all_phases -@spec_state_test -def test_no_update_same_slot_at_epoch_boundary(spec, state): - store = get_genesis_forkchoice_store(spec, state) - seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - - store.best_justified_checkpoint = spec.Checkpoint( - epoch=store.justified_checkpoint.epoch + 1, - root=b'\x55' * 32, - ) - - # set store time to already be at epoch boundary - store.time = seconds_per_epoch - - run_on_tick(spec, store, store.time + 1) - - -@with_all_phases -@spec_state_test -def test_no_update_not_epoch_boundary(spec, state): - store = get_genesis_forkchoice_store(spec, state) - - store.best_justified_checkpoint = spec.Checkpoint( - epoch=store.justified_checkpoint.epoch + 1, - root=b'\x55' * 32, - ) - - run_on_tick(spec, store, store.time + spec.config.SECONDS_PER_SLOT) - - -@with_all_phases -@spec_state_test -def test_no_update_new_justified_equal_epoch(spec, state): - store = get_genesis_forkchoice_store(spec, state) - seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - - store.best_justified_checkpoint = spec.Checkpoint( - epoch=store.justified_checkpoint.epoch + 1, - root=b'\x55' * 32, - ) - - store.justified_checkpoint = spec.Checkpoint( - epoch=store.best_justified_checkpoint.epoch, - root=b'\44' * 32, - ) - - run_on_tick(spec, store, store.time + seconds_per_epoch) - - -@with_all_phases -@spec_state_test -def test_no_update_new_justified_later_epoch(spec, state): - store = get_genesis_forkchoice_store(spec, state) - seconds_per_epoch = spec.config.SECONDS_PER_SLOT * spec.SLOTS_PER_EPOCH - - store.best_justified_checkpoint = spec.Checkpoint( - epoch=store.justified_checkpoint.epoch + 1, - root=b'\x55' * 32, - ) - - store.justified_checkpoint = spec.Checkpoint( - epoch=store.best_justified_checkpoint.epoch + 1, - root=b'\44' * 32, - ) - - run_on_tick(spec, store, store.time + seconds_per_epoch) diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/math/__init__.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/math/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/math/test_integer_squareroot.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/math/test_integer_squareroot.py new file mode 100644 index 0000000000..43013c3654 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/math/test_integer_squareroot.py @@ -0,0 +1,30 @@ +import random +from math import isqrt + +from eth2spec.test.context import ( + single_phase, + spec_test, + with_all_phases, +) + + +@with_all_phases +@spec_test +@single_phase +def test_integer_squareroot(spec): + values = [0, 100, 2**64 - 2, 2**64 - 1] + for n in values: + uint64_n = spec.uint64(n) + assert spec.integer_squareroot(uint64_n) == isqrt(n) + + rng = random.Random(5566) + for _ in range(10): + n = rng.randint(0, 2**64 - 1) + uint64_n = spec.uint64(n) + assert spec.integer_squareroot(uint64_n) == isqrt(n) + + try: + spec.integer_squareroot(spec.uint64(2**64)) + assert False + except ValueError: + pass diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py index b39b011b4f..ffa16581bc 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/test_config_invariants.py @@ -1,9 +1,13 @@ from eth2spec.test.context import ( spec_state_test, with_all_phases, +) +from eth2spec.test.helpers.constants import UINT64_MAX +from eth2spec.test.helpers.forks import ( is_post_altair, + is_post_bellatrix, + is_post_electra, ) -from eth2spec.test.helpers.constants import MAX_UINT_64 def check_bound(value, lower_bound, upper_bound): @@ -14,46 +18,52 @@ def check_bound(value, lower_bound, upper_bound): @with_all_phases @spec_state_test def test_validators(spec, state): - check_bound(spec.VALIDATOR_REGISTRY_LIMIT, 1, MAX_UINT_64) - check_bound(spec.MAX_COMMITTEES_PER_SLOT, 1, MAX_UINT_64) - check_bound(spec.TARGET_COMMITTEE_SIZE, 1, MAX_UINT_64) + check_bound(spec.VALIDATOR_REGISTRY_LIMIT, 1, UINT64_MAX) + check_bound(spec.MAX_COMMITTEES_PER_SLOT, 1, UINT64_MAX) + check_bound(spec.TARGET_COMMITTEE_SIZE, 1, UINT64_MAX) # Note: can be less if you assume stricters bounds on validator set based on total ETH supply maximum_validators_per_committee = ( - spec.VALIDATOR_REGISTRY_LIMIT - // spec.SLOTS_PER_EPOCH - // spec.MAX_COMMITTEES_PER_SLOT + spec.VALIDATOR_REGISTRY_LIMIT // spec.SLOTS_PER_EPOCH // spec.MAX_COMMITTEES_PER_SLOT ) check_bound(spec.MAX_VALIDATORS_PER_COMMITTEE, 1, maximum_validators_per_committee) check_bound(spec.config.MIN_PER_EPOCH_CHURN_LIMIT, 1, spec.VALIDATOR_REGISTRY_LIMIT) check_bound(spec.config.CHURN_LIMIT_QUOTIENT, 1, spec.VALIDATOR_REGISTRY_LIMIT) - check_bound(spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, spec.TARGET_COMMITTEE_SIZE, MAX_UINT_64) + check_bound( + spec.config.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, spec.TARGET_COMMITTEE_SIZE, UINT64_MAX + ) @with_all_phases @spec_state_test def test_balances(spec, state): assert spec.MAX_EFFECTIVE_BALANCE % spec.EFFECTIVE_BALANCE_INCREMENT == 0 - check_bound(spec.MIN_DEPOSIT_AMOUNT, 1, MAX_UINT_64) - check_bound(spec.MAX_EFFECTIVE_BALANCE, spec.MIN_DEPOSIT_AMOUNT, MAX_UINT_64) - check_bound(spec.MAX_EFFECTIVE_BALANCE, spec.EFFECTIVE_BALANCE_INCREMENT, MAX_UINT_64) + check_bound(spec.MIN_DEPOSIT_AMOUNT, 1, UINT64_MAX) + check_bound(spec.MAX_EFFECTIVE_BALANCE, spec.MIN_DEPOSIT_AMOUNT, UINT64_MAX) + check_bound(spec.MAX_EFFECTIVE_BALANCE, spec.EFFECTIVE_BALANCE_INCREMENT, UINT64_MAX) @with_all_phases @spec_state_test def test_hysteresis_quotient(spec, state): - check_bound(spec.HYSTERESIS_QUOTIENT, 1, MAX_UINT_64) + check_bound(spec.HYSTERESIS_QUOTIENT, 1, UINT64_MAX) check_bound(spec.HYSTERESIS_DOWNWARD_MULTIPLIER, 1, spec.HYSTERESIS_QUOTIENT) - check_bound(spec.HYSTERESIS_UPWARD_MULTIPLIER, spec.HYSTERESIS_QUOTIENT, MAX_UINT_64) + check_bound(spec.HYSTERESIS_UPWARD_MULTIPLIER, spec.HYSTERESIS_QUOTIENT, UINT64_MAX) @with_all_phases @spec_state_test def test_incentives(spec, state): # Ensure no ETH is minted in slash_validator - if is_post_altair(spec): + if is_post_bellatrix(spec): + assert spec.MIN_SLASHING_PENALTY_QUOTIENT_BELLATRIX <= spec.WHISTLEBLOWER_REWARD_QUOTIENT + elif is_post_altair(spec): assert spec.MIN_SLASHING_PENALTY_QUOTIENT_ALTAIR <= spec.WHISTLEBLOWER_REWARD_QUOTIENT + elif is_post_electra(spec): + assert ( + spec.MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA <= spec.WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA + ) else: assert spec.MIN_SLASHING_PENALTY_QUOTIENT <= spec.WHISTLEBLOWER_REWARD_QUOTIENT @@ -64,11 +74,27 @@ def test_time(spec, state): assert spec.SLOTS_PER_EPOCH <= spec.SLOTS_PER_HISTORICAL_ROOT assert spec.MIN_SEED_LOOKAHEAD < spec.MAX_SEED_LOOKAHEAD assert spec.SLOTS_PER_HISTORICAL_ROOT % spec.SLOTS_PER_EPOCH == 0 - check_bound(spec.SLOTS_PER_HISTORICAL_ROOT, spec.SLOTS_PER_EPOCH, MAX_UINT_64) + check_bound(spec.SLOTS_PER_HISTORICAL_ROOT, spec.SLOTS_PER_EPOCH, UINT64_MAX) check_bound(spec.MIN_ATTESTATION_INCLUSION_DELAY, 1, spec.SLOTS_PER_EPOCH) @with_all_phases @spec_state_test def test_networking(spec, state): - assert spec.RANDOM_SUBNETS_PER_VALIDATOR <= spec.ATTESTATION_SUBNET_COUNT + assert spec.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS == ( + spec.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY + spec.config.CHURN_LIMIT_QUOTIENT // 2 + ) + assert spec.config.ATTESTATION_SUBNET_PREFIX_BITS == ( + spec.ceillog2(spec.config.ATTESTATION_SUBNET_COUNT) + + spec.config.ATTESTATION_SUBNET_EXTRA_BITS + ) + assert spec.config.SUBNETS_PER_NODE <= spec.config.ATTESTATION_SUBNET_COUNT + node_id_length = spec.NodeID(1).type_byte_length() # in bytes + assert node_id_length * 8 == spec.NODE_ID_BITS # in bits + + +@with_all_phases +@spec_state_test +def test_fork_choice(spec, state): + assert spec.INTERVALS_PER_SLOT < spec.config.SECONDS_PER_SLOT + assert spec.config.PROPOSER_SCORE_BOOST <= 100 diff --git a/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py b/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py index cf7ef392f1..97b175a04f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py +++ b/tests/core/pyspec/eth2spec/test/phase0/unittests/validator/test_validator_unittest.py @@ -1,10 +1,17 @@ +import random + from eth2spec.test.context import ( + always_bls, + single_phase, spec_state_test, - always_bls, with_phases, with_all_phases, + spec_test, + with_all_phases, + with_all_phases_from_to, + with_phases, ) -from eth2spec.test.helpers.constants import PHASE0 from eth2spec.test.helpers.attestations import build_attestation_data, get_valid_attestation from eth2spec.test.helpers.block import build_empty_block +from eth2spec.test.helpers.constants import FULU, PHASE0 from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.state import next_epoch @@ -12,7 +19,9 @@ from eth2spec.utils.ssz.ssz_typing import Bitlist -def run_get_signature_test(spec, state, obj, domain, get_signature_fn, privkey, pubkey, signing_ssz_object=None): +def run_get_signature_test( + spec, state, obj, domain, get_signature_fn, privkey, pubkey, signing_ssz_object=None +): if signing_ssz_object is None: signing_ssz_object = obj signature = get_signature_fn(state, obj, privkey) @@ -28,7 +37,6 @@ def run_get_committee_assignment(spec, state, epoch, validator_index, valid=True assert committee == spec.get_beacon_committee(state, slot, committee_index) assert committee_index < spec.get_committee_count_per_slot(state, epoch) assert validator_index in committee - assert valid except AssertionError: assert not valid else: @@ -42,7 +50,8 @@ def run_is_candidate_block(spec, eth1_block, period_start, success=True): def get_min_new_period_epochs(spec): return ( (spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE * 2) # to seconds - // spec.config.SECONDS_PER_SLOT // spec.SLOTS_PER_EPOCH + // spec.config.SECONDS_PER_SLOT + // spec.SLOTS_PER_EPOCH ) @@ -194,14 +203,17 @@ def test_get_eth1_vote_consensus_vote(spec, state): state.eth1_data_votes = () block_1 = spec.Eth1Block( - timestamp=period_start - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE - 1, + timestamp=period_start + - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE + - 1, deposit_count=state.eth1_data.deposit_count, - deposit_root=b'\x04' * 32, + deposit_root=b"\x04" * 32, ) block_2 = spec.Eth1Block( - timestamp=period_start - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, + timestamp=period_start + - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, deposit_count=state.eth1_data.deposit_count + 1, - deposit_root=b'\x05' * 32, + deposit_root=b"\x05" * 32, ) eth1_chain = [block_1, block_2] eth1_data_votes = [] @@ -230,14 +242,17 @@ def test_get_eth1_vote_tie(spec, state): state.eth1_data_votes = () block_1 = spec.Eth1Block( - timestamp=period_start - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE - 1, + timestamp=period_start + - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE + - 1, deposit_count=state.eth1_data.deposit_count, - deposit_root=b'\x04' * 32, + deposit_root=b"\x04" * 32, ) block_2 = spec.Eth1Block( - timestamp=period_start - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, + timestamp=period_start + - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, deposit_count=state.eth1_data.deposit_count + 1, - deposit_root=b'\x05' * 32, + deposit_root=b"\x05" * 32, ) eth1_chain = [block_1, block_2] eth1_data_votes = [] @@ -269,9 +284,10 @@ def test_get_eth1_vote_chain_in_past(spec, state): state.eth1_data_votes = () block_1 = spec.Eth1Block( - timestamp=period_start - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, + timestamp=period_start + - spec.config.SECONDS_PER_ETH1_BLOCK * spec.config.ETH1_FOLLOW_DISTANCE, deposit_count=state.eth1_data.deposit_count - 1, # Chain prior to current eth1data - deposit_root=b'\x42' * 32, + deposit_root=b"\x42" * 32, ) eth1_chain = [block_1] eth1_data_votes = [] @@ -307,7 +323,9 @@ def test_get_block_signature(spec, state): privkey = privkeys[0] pubkey = pubkeys[0] block = build_empty_block(spec, state) - domain = spec.get_domain(state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot)) + domain = spec.get_domain( + state, spec.DOMAIN_BEACON_PROPOSER, spec.compute_epoch_at_slot(block.slot) + ) run_get_signature_test( spec=spec, state=state, @@ -319,14 +337,19 @@ def test_get_block_signature(spec, state): ) -@with_all_phases +@with_all_phases_from_to(from_phase=PHASE0, to_phase=FULU) @spec_state_test def test_compute_fork_digest(spec, state): - actual_fork_digest = spec.compute_fork_digest(state.fork.current_version, state.genesis_validators_root) + actual_fork_digest = spec.compute_fork_digest( + state.fork.current_version, state.genesis_validators_root + ) expected_fork_data_root = spec.hash_tree_root( - spec.ForkData(current_version=state.fork.current_version, - genesis_validators_root=state.genesis_validators_root)) + spec.ForkData( + current_version=state.fork.current_version, + genesis_validators_root=state.genesis_validators_root, + ) + ) expected_fork_digest = spec.ForkDigest(expected_fork_data_root[:4]) assert actual_fork_digest == expected_fork_digest @@ -360,12 +383,18 @@ def test_get_attestation_signature_phase0(spec, state): def test_compute_subnet_for_attestation(spec, state): for committee_idx in range(spec.MAX_COMMITTEES_PER_SLOT): for slot in range(state.slot, state.slot + spec.SLOTS_PER_EPOCH): - committees_per_slot = spec.get_committee_count_per_slot(state, spec.compute_epoch_at_slot(slot)) - actual_subnet_id = spec.compute_subnet_for_attestation(committees_per_slot, slot, committee_idx) + committees_per_slot = spec.get_committee_count_per_slot( + state, spec.compute_epoch_at_slot(slot) + ) + actual_subnet_id = spec.compute_subnet_for_attestation( + committees_per_slot, slot, committee_idx + ) slots_since_epoch_start = slot % spec.SLOTS_PER_EPOCH committees_since_epoch_start = committees_per_slot * slots_since_epoch_start - expected_subnet_id = (committees_since_epoch_start + committee_idx) % spec.ATTESTATION_SUBNET_COUNT + expected_subnet_id = ( + committees_since_epoch_start + committee_idx + ) % spec.config.ATTESTATION_SUBNET_COUNT assert actual_subnet_id == expected_subnet_id @@ -434,7 +463,9 @@ def test_get_aggregate_signature(spec, state): spec.Attestation( data=attestation_data, aggregation_bits=bits, - signature=spec.get_attestation_signature(state, attestation_data, privkeys[validator_index]), + signature=spec.get_attestation_signature( + state, attestation_data, privkeys[validator_index] + ), ) ) attesting_pubkeys.append(state.validators[validator_index].pubkey) @@ -455,7 +486,9 @@ def test_get_aggregate_and_proof(spec, state): aggregate_and_proof = spec.get_aggregate_and_proof(state, aggregator_index, aggregate, privkey) assert aggregate_and_proof.aggregator_index == aggregator_index assert aggregate_and_proof.aggregate == aggregate - assert aggregate_and_proof.selection_proof == spec.get_slot_signature(state, aggregate.data.slot, privkey) + assert aggregate_and_proof.selection_proof == spec.get_slot_signature( + state, aggregate.data.slot, privkey + ) @with_all_phases @@ -465,8 +498,12 @@ def test_get_aggregate_and_proof_signature(spec, state): privkey = privkeys[0] pubkey = pubkeys[0] aggregate = get_mock_aggregate(spec) - aggregate_and_proof = spec.get_aggregate_and_proof(state, spec.ValidatorIndex(1), aggregate, privkey) - domain = spec.get_domain(state, spec.DOMAIN_AGGREGATE_AND_PROOF, spec.compute_epoch_at_slot(aggregate.data.slot)) + aggregate_and_proof = spec.get_aggregate_and_proof( + state, spec.ValidatorIndex(1), aggregate, privkey + ) + domain = spec.get_domain( + state, spec.DOMAIN_AGGREGATE_AND_PROOF, spec.compute_epoch_at_slot(aggregate.data.slot) + ) run_get_signature_test( spec=spec, state=state, @@ -476,3 +513,34 @@ def test_get_aggregate_and_proof_signature(spec, state): privkey=privkey, pubkey=pubkey, ) + + +def run_compute_subscribed_subnets_arguments(spec, rng=random.Random(1111)): + node_id = rng.randint(0, 2**256 - 1) + epoch = rng.randint(0, 2**64 - 1) + subnets = spec.compute_subscribed_subnets(node_id, epoch) + assert len(subnets) == spec.config.SUBNETS_PER_NODE + + +@with_all_phases +@spec_test +@single_phase +def test_compute_subscribed_subnets_random_1(spec): + rng = random.Random(1111) + run_compute_subscribed_subnets_arguments(spec, rng) + + +@with_all_phases +@spec_test +@single_phase +def test_compute_subscribed_subnets_random_2(spec): + rng = random.Random(2222) + run_compute_subscribed_subnets_arguments(spec, rng) + + +@with_all_phases +@spec_test +@single_phase +def test_compute_subscribed_subnets_random_3(spec): + rng = random.Random(3333) + run_compute_subscribed_subnets_arguments(spec, rng) diff --git a/tests/core/pyspec/eth2spec/test/sharding/unittests/test_get_start_shard.py b/tests/core/pyspec/eth2spec/test/sharding/unittests/test_get_start_shard.py deleted file mode 100644 index c38f28b2b1..0000000000 --- a/tests/core/pyspec/eth2spec/test/sharding/unittests/test_get_start_shard.py +++ /dev/null @@ -1,89 +0,0 @@ -from eth2spec.test.context import ( - with_phases, - spec_state_test, -) -from eth2spec.test.helpers.constants import SHARDING -from eth2spec.test.helpers.state import next_epoch - - -@with_phases([SHARDING]) -@spec_state_test -def test_get_committee_count_delta(spec, state): - assert spec.get_committee_count_delta(state, 0, 0) == 0 - assert spec.get_committee_count_per_slot(state, 0) != 0 - assert spec.get_committee_count_delta(state, 0, 1) == spec.get_committee_count_per_slot(state, 0) - assert spec.get_committee_count_delta(state, 1, 2) == spec.get_committee_count_per_slot(state, 0) - assert spec.get_committee_count_delta(state, 0, 2) == spec.get_committee_count_per_slot(state, 0) * 2 - assert spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) == ( - spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH - ) - assert spec.get_committee_count_delta(state, 0, 2 * spec.SLOTS_PER_EPOCH) == ( - spec.get_committee_count_per_slot(state, 0) * spec.SLOTS_PER_EPOCH - + spec.get_committee_count_per_slot(state, 1) * spec.SLOTS_PER_EPOCH - ) - - -@with_phases([SHARDING]) -@spec_state_test -def test_get_start_shard_current_epoch_start(spec, state): - assert state.current_epoch_start_shard == 0 - next_epoch(spec, state) - active_shard_count = spec.get_active_shard_count(state) - assert state.current_epoch_start_shard == ( - spec.get_committee_count_delta(state, 0, spec.SLOTS_PER_EPOCH) % active_shard_count - ) - current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) - - slot = current_epoch_start_slot - start_shard = spec.get_start_shard(state, slot) - assert start_shard == state.current_epoch_start_shard - - -@with_phases([SHARDING]) -@spec_state_test -def test_get_start_shard_next_slot(spec, state): - next_epoch(spec, state) - active_shard_count = spec.get_active_shard_count(state) - current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) - - slot = current_epoch_start_slot + 1 - start_shard = spec.get_start_shard(state, slot) - - current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) - expected_start_shard = ( - state.current_epoch_start_shard - + spec.get_committee_count_delta(state, start_slot=current_epoch_start_slot, stop_slot=slot) - ) % active_shard_count - assert start_shard == expected_start_shard - - -@with_phases([SHARDING]) -@spec_state_test -def test_get_start_shard_previous_slot(spec, state): - next_epoch(spec, state) - active_shard_count = spec.get_active_shard_count(state) - current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) - - slot = current_epoch_start_slot - 1 - start_shard = spec.get_start_shard(state, slot) - - current_epoch_start_slot = spec.compute_start_slot_at_epoch(spec.get_current_epoch(state)) - expected_start_shard = ( - state.current_epoch_start_shard - + spec.MAX_COMMITTEES_PER_SLOT * spec.SLOTS_PER_EPOCH * active_shard_count - - spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot) - ) % active_shard_count - assert start_shard == expected_start_shard - - -@with_phases([SHARDING]) -@spec_state_test -def test_get_start_shard_far_past_epoch(spec, state): - initial_epoch = spec.get_current_epoch(state) - initial_start_slot = spec.compute_start_slot_at_epoch(initial_epoch) - initial_start_shard = state.current_epoch_start_shard - - for _ in range(spec.MAX_SHARDS + 2): - next_epoch(spec, state) - - assert spec.get_start_shard(state, initial_start_slot) == initial_start_shard diff --git a/tests/core/pyspec/eth2spec/test/utils/__init__.py b/tests/core/pyspec/eth2spec/test/utils/__init__.py index f6b2a8a44b..cead6efbd7 100644 --- a/tests/core/pyspec/eth2spec/test/utils/__init__.py +++ b/tests/core/pyspec/eth2spec/test/utils/__init__.py @@ -1,12 +1,9 @@ from .utils import ( vector_test, with_meta_tags, - build_transition_test, ) - __all__ = [ # avoid "unused import" lint error "vector_test", "with_meta_tags", - "build_transition_test", ] diff --git a/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py b/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py new file mode 100644 index 0000000000..d926b8a774 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/utils/kzg_tests.py @@ -0,0 +1,185 @@ +from eth_utils import ( + encode_hex, + int_to_big_endian, +) + +from eth2spec.fulu import spec +from eth2spec.utils import bls + +############################################################################### +# Helper functions +############################################################################### + + +def bls_add_one(x): + """ + Adds "one" (actually bls.G1()) to a compressed group element. + Useful to compute definitely incorrect proofs. + """ + return bls.G1_to_bytes48(bls.add(bls.bytes48_to_G1(x), bls.G1())) + + +def field_element_bytes(x: int): + assert x < spec.BLS_MODULUS + return int.to_bytes(x, 32, spec.KZG_ENDIANNESS) + + +def field_element_bytes_unchecked(x: int): + return int.to_bytes(x, 32, spec.KZG_ENDIANNESS) + + +def encode_hex_list(a): + return [encode_hex(x) for x in a] + + +def int_to_hex(n: int, byte_length: int = None) -> str: + byte_value = int_to_big_endian(n) + if byte_length: + byte_value = byte_value.rjust(byte_length, b"\x00") + return encode_hex(byte_value) + + +def evaluate_blob_at(blob, z): + return field_element_bytes( + int( + spec.evaluate_polynomial_in_evaluation_form( + spec.blob_to_polynomial(blob), spec.bytes_to_bls_field(z) + ) + ) + ) + + +############################################################################### +# Global variables +############################################################################### + +BLS_MODULUS_BYTES = spec.BLS_MODULUS.to_bytes(32, spec.KZG_ENDIANNESS) + +# Field Elements + +FE_VALID1 = field_element_bytes(0) +FE_VALID2 = field_element_bytes(1) +FE_VALID3 = field_element_bytes(2) +FE_VALID4 = field_element_bytes(pow(5, 1235, spec.BLS_MODULUS)) +FE_VALID5 = field_element_bytes(spec.BLS_MODULUS - 1) +FE_VALID6 = field_element_bytes(int(spec.compute_roots_of_unity(spec.FIELD_ELEMENTS_PER_BLOB)[1])) +VALID_FIELD_ELEMENTS = [FE_VALID1, FE_VALID2, FE_VALID3, FE_VALID4, FE_VALID5, FE_VALID6] + +FE_INVALID_EQUAL_TO_MODULUS = field_element_bytes_unchecked(spec.BLS_MODULUS) +FE_INVALID_MODULUS_PLUS_ONE = field_element_bytes_unchecked(spec.BLS_MODULUS + 1) +FE_INVALID_UINT256_MAX = field_element_bytes_unchecked(2**256 - 1) +FE_INVALID_UINT256_MID = field_element_bytes_unchecked(2**256 - 2**128) +FE_INVALID_LENGTH_PLUS_ONE = VALID_FIELD_ELEMENTS[0] + b"\x00" +FE_INVALID_LENGTH_MINUS_ONE = VALID_FIELD_ELEMENTS[0][:-1] +INVALID_FIELD_ELEMENTS = [ + FE_INVALID_EQUAL_TO_MODULUS, + FE_INVALID_MODULUS_PLUS_ONE, + FE_INVALID_UINT256_MAX, + FE_INVALID_UINT256_MID, + FE_INVALID_LENGTH_PLUS_ONE, + FE_INVALID_LENGTH_MINUS_ONE, +] + +# Blobs + +BLOB_ALL_ZEROS = spec.Blob() +BLOB_ALL_TWOS = spec.Blob(b"".join([field_element_bytes(2) for n in range(4096)])) +BLOB_RANDOM_VALID1 = spec.Blob( + b"".join([field_element_bytes(pow(2, n + 256, spec.BLS_MODULUS)) for n in range(4096)]) +) +BLOB_RANDOM_VALID2 = spec.Blob( + b"".join([field_element_bytes(pow(3, n + 256, spec.BLS_MODULUS)) for n in range(4096)]) +) +BLOB_RANDOM_VALID3 = spec.Blob( + b"".join([field_element_bytes(pow(5, n + 256, spec.BLS_MODULUS)) for n in range(4096)]) +) +BLOB_ALL_MODULUS_MINUS_ONE = spec.Blob( + b"".join([field_element_bytes(spec.BLS_MODULUS - 1) for n in range(4096)]) +) +BLOB_ALMOST_ZERO = spec.Blob( + b"".join([field_element_bytes(1 if n == 3211 else 0) for n in range(4096)]) +) + +BLOB_INVALID = spec.Blob(b"\xff" * 4096 * 32) +BLOB_INVALID_CLOSE = spec.Blob( + b"".join([BLS_MODULUS_BYTES if n == 2111 else field_element_bytes(0) for n in range(4096)]) +) +BLOB_INVALID_LENGTH_PLUS_ONE = BLOB_RANDOM_VALID1 + b"\x00" +BLOB_INVALID_LENGTH_MINUS_ONE = BLOB_RANDOM_VALID1[:-1] + +VALID_BLOBS = [ + BLOB_ALL_ZEROS, + BLOB_ALL_TWOS, + BLOB_RANDOM_VALID1, + BLOB_RANDOM_VALID2, + BLOB_RANDOM_VALID3, + BLOB_ALL_MODULUS_MINUS_ONE, + BLOB_ALMOST_ZERO, +] +INVALID_BLOBS = [ + BLOB_INVALID, + BLOB_INVALID_CLOSE, + BLOB_INVALID_LENGTH_PLUS_ONE, + BLOB_INVALID_LENGTH_MINUS_ONE, +] + +# Points + +G1 = bls.G1_to_bytes48(bls.G1()) +G1_INVALID_TOO_FEW_BYTES = G1[:-1] +G1_INVALID_TOO_MANY_BYTES = G1 + b"\x00" +G1_INVALID_P1_NOT_IN_G1 = bytes.fromhex( + "8123456789abcdef0123456789abcdef0123456789abcdef" + + "0123456789abcdef0123456789abcdef0123456789abcdef" +) +G1_INVALID_P1_NOT_ON_CURVE = bytes.fromhex( + "8123456789abcdef0123456789abcdef0123456789abcdef" + + "0123456789abcdef0123456789abcdef0123456789abcde0" +) +INVALID_G1_POINTS = [ + G1_INVALID_TOO_FEW_BYTES, + G1_INVALID_TOO_MANY_BYTES, + G1_INVALID_P1_NOT_IN_G1, + G1_INVALID_P1_NOT_ON_CURVE, +] + +# Individual Cells + +CELL_RANDOM_VALID1 = b"".join( + [ + field_element_bytes(pow(2, n + 256, spec.BLS_MODULUS)) + for n in range(spec.FIELD_ELEMENTS_PER_CELL) + ] +) +CELL_RANDOM_VALID2 = b"".join( + [ + field_element_bytes(pow(3, n + 256, spec.BLS_MODULUS)) + for n in range(spec.FIELD_ELEMENTS_PER_CELL) + ] +) +CELL_RANDOM_VALID3 = b"".join( + [ + field_element_bytes(pow(5, n + 256, spec.BLS_MODULUS)) + for n in range(spec.FIELD_ELEMENTS_PER_CELL) + ] +) + +CELL_ALL_MAX_VALUE = b"".join( + [field_element_bytes_unchecked(2**256 - 1) for n in range(spec.FIELD_ELEMENTS_PER_CELL)] +) +CELL_ONE_INVALID_FIELD = b"".join( + [ + field_element_bytes_unchecked(spec.BLS_MODULUS) if n == 7 else field_element_bytes(0) + for n in range(spec.FIELD_ELEMENTS_PER_CELL) + ] +) +CELL_INVALID_TOO_FEW_BYTES = CELL_RANDOM_VALID1[:-1] +CELL_INVALID_TOO_MANY_BYTES = CELL_RANDOM_VALID2 + b"\x00" + +VALID_INDIVIDUAL_RANDOM_CELL_BYTES = [CELL_RANDOM_VALID1, CELL_RANDOM_VALID2, CELL_RANDOM_VALID3] +INVALID_INDIVIDUAL_CELL_BYTES = [ + CELL_ALL_MAX_VALUE, + CELL_ONE_INVALID_FIELD, + CELL_INVALID_TOO_FEW_BYTES, + CELL_INVALID_TOO_MANY_BYTES, +] 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 02a5464f70..9970d9d759 100644 --- a/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py +++ b/tests/core/pyspec/eth2spec/test/utils/randomized_block_tests.py @@ -4,25 +4,34 @@ import sys import warnings +from collections.abc import Callable from random import Random -from typing import Callable +from eth2spec.test.helpers.blob import ( + get_sample_blob_tx, +) +from eth2spec.test.helpers.execution_payload import ( + build_randomized_execution_payload, + compute_el_block_hash_for_block, +) +from eth2spec.test.helpers.inactivity_scores import ( + randomize_inactivity_scores, +) from eth2spec.test.helpers.multi_operations import ( build_random_block_from_state_for_next_slot, + get_random_bls_to_execution_changes, + get_random_execution_requests, get_random_sync_aggregate, prepare_state_and_get_random_deposits, ) -from eth2spec.test.helpers.inactivity_scores import ( - randomize_inactivity_scores, -) from eth2spec.test.helpers.random import ( - randomize_state as randomize_state_helper, patch_state_to_non_leaking, + randomize_state as randomize_state_helper, ) from eth2spec.test.helpers.state import ( - next_slot, - next_epoch, ensure_state_has_validators_across_lifecycle, + next_epoch, + next_slot, state_transition_and_sign_block, ) @@ -43,7 +52,9 @@ def _randomize_deposit_state(spec, state, stats): deposits = [] if block_count > 0: num_deposits = rng.randrange(1, block_count * spec.MAX_DEPOSITS) - deposits = prepare_state_and_get_random_deposits(spec, state, rng, num_deposits=num_deposits) + deposits = prepare_state_and_get_random_deposits( + spec, state, rng, num_deposits=num_deposits + ) return { "deposits": deposits, } @@ -55,14 +66,63 @@ def randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): return scenario_state -def randomize_state_altair(spec, state, stats): - scenario_state = randomize_state(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1) +def randomize_state_altair(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state( + spec, state, stats, exit_fraction=exit_fraction, slash_fraction=slash_fraction + ) randomize_inactivity_scores(spec, state) return scenario_state +def randomize_state_bellatrix(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_altair( + spec, state, stats, exit_fraction=exit_fraction, slash_fraction=slash_fraction + ) + # TODO: randomize execution payload, merge status, etc. + return scenario_state + + +def randomize_state_capella(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_bellatrix( + spec, state, stats, exit_fraction=exit_fraction, slash_fraction=slash_fraction + ) + # TODO: randomize withdrawals + return scenario_state + + +def randomize_state_deneb(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_capella( + spec, state, stats, exit_fraction=exit_fraction, slash_fraction=slash_fraction + ) + # TODO: randomize execution payload + return scenario_state + + +def randomize_state_electra(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_deneb( + spec, + state, + stats, + exit_fraction=exit_fraction, + slash_fraction=slash_fraction, + ) + return scenario_state + + +def randomize_state_fulu(spec, state, stats, exit_fraction=0.1, slash_fraction=0.1): + scenario_state = randomize_state_electra( + spec, + state, + stats, + exit_fraction=exit_fraction, + slash_fraction=slash_fraction, + ) + return scenario_state + + # epochs + def epochs_until_leak(spec): """ State is "leaking" if the current epoch is at least @@ -77,6 +137,7 @@ def epochs_for_shard_committee_period(spec): # slots + def last_slot_in_epoch(spec): return spec.SLOTS_PER_EPOCH - 1 @@ -91,6 +152,7 @@ def penultimate_slot_in_epoch(spec): # blocks + def no_block(_spec, _pre_state, _signed_blocks, _scenario_state): return None @@ -121,7 +183,7 @@ def _warn_if_empty_operations(block): def _pull_deposits_from_scenario_state(spec, scenario_state, existing_block_count): all_deposits = scenario_state.get("deposits", []) start = existing_block_count * spec.MAX_DEPOSITS - return all_deposits[start:start + spec.MAX_DEPOSITS] + return all_deposits[start : start + spec.MAX_DEPOSITS] def random_block(spec, state, signed_blocks, scenario_state): @@ -149,21 +211,23 @@ def random_block(spec, state, signed_blocks, scenario_state): next_slot(spec, state) next_slot(spec, temp_state) else: - deposits_for_block = _pull_deposits_from_scenario_state(spec, scenario_state, len(signed_blocks)) - block = build_random_block_from_state_for_next_slot(spec, state, deposits=deposits_for_block) + deposits_for_block = _pull_deposits_from_scenario_state( + spec, scenario_state, len(signed_blocks) + ) + block = build_random_block_from_state_for_next_slot( + spec, state, deposits=deposits_for_block + ) _warn_if_empty_operations(block) return block - else: - raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") + raise AssertionError("could not find a block with an unslashed proposer, check ``state`` input") SYNC_AGGREGATE_PARTICIPATION_BUCKETS = 4 -def random_block_altair_with_cycling_sync_committee_participation(spec, - state, - signed_blocks, - scenario_state): +def random_block_altair_with_cycling_sync_committee_participation( + spec, state, signed_blocks, scenario_state +): block = random_block(spec, state, signed_blocks, scenario_state) block_index = len(signed_blocks) % SYNC_AGGREGATE_PARTICIPATION_BUCKETS fraction_missed = block_index * (1 / SYNC_AGGREGATE_PARTICIPATION_BUCKETS) @@ -179,8 +243,56 @@ def random_block_altair_with_cycling_sync_committee_participation(spec, return block +def random_block_bellatrix(spec, state, signed_blocks, scenario_state, rng=Random(3456)): + block = random_block_altair_with_cycling_sync_committee_participation( + spec, state, signed_blocks, scenario_state + ) + # build execution_payload at the next slot + state = state.copy() + next_slot(spec, state) + block.body.execution_payload = build_randomized_execution_payload(spec, state, rng=rng) + return block + + +def random_block_capella(spec, state, signed_blocks, scenario_state, rng=Random(3456)): + block = random_block_bellatrix(spec, state, signed_blocks, scenario_state, rng=rng) + block.body.bls_to_execution_changes = get_random_bls_to_execution_changes( + spec, state, num_address_changes=rng.randint(1, spec.MAX_BLS_TO_EXECUTION_CHANGES) + ) + return block + + +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_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) + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + block.body.blob_kzg_commitments = blob_kzg_commitments + + return block + + +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) + block.body.execution_payload.block_hash = compute_el_block_hash_for_block(spec, block) + + return block + + +def random_block_fulu(spec, state, signed_blocks, scenario_state, rng=Random(3456)): + block = random_block_electra(spec, state, signed_blocks, scenario_state, rng=rng) + + return block + + # validations + def no_op_validation(_spec, _state): return True @@ -195,6 +307,7 @@ def validate_is_not_leaking(spec, state): # transitions + def with_validation(transition, validation): if isinstance(transition, Callable): transition = transition() @@ -258,6 +371,7 @@ def _randomized_scenario_setup(state_randomizer): how many blocks will be produced. This data can be useful to construct a valid pre-state and so is provided at the setup stage. """ + def _skip_epochs(epoch_producer): def f(spec, state, _stats): """ @@ -267,6 +381,7 @@ def f(spec, state, _stats): epochs_to_skip = epoch_producer(spec) slots_to_skip = epochs_to_skip * spec.SLOTS_PER_EPOCH state.slot += slots_to_skip + return f def _simulate_honest_execution(spec, state, _stats): @@ -285,6 +400,7 @@ def _simulate_honest_execution(spec, state, _stats): (state_randomizer, ensure_state_has_validators_across_lifecycle), ) + # Run the generated tests: @@ -309,8 +425,7 @@ def _iter_temporal(spec, description): numeric = _resolve_ref(description) if isinstance(numeric, Callable): numeric = numeric(spec) - for i in range(numeric): - yield i + yield from range(numeric) def _compute_statistics(scenario): diff --git a/tests/core/pyspec/eth2spec/test/utils/utils.py b/tests/core/pyspec/eth2spec/test/utils/utils.py index 61fc75040e..1e289dd41a 100644 --- a/tests/core/pyspec/eth2spec/test/utils/utils.py +++ b/tests/core/pyspec/eth2spec/test/utils/utils.py @@ -1,7 +1,7 @@ -import inspect -from typing import Dict, Any -from eth2spec.utils.ssz.ssz_typing import View +from typing import Any + from eth2spec.utils.ssz.ssz_impl import serialize +from eth2spec.utils.ssz.ssz_typing import View def vector_test(description: str = None): @@ -14,18 +14,19 @@ def vector_test(description: str = None): :param description: Optional description for the test to add to the metadata. :return: Decorator. """ + def runner(fn): # this wraps the function, to yield type-annotated entries of data. # Valid types are: # - "meta": all key-values with this type can be collected by the generator, to put somewhere together. + # - "cfg": spec config dictionary # - "ssz": raw SSZ bytes # - "data": a python structure to be encoded by the user. def entry(*args, **kw): - def generator_mode(): if description is not None: # description can be explicit - yield 'description', 'meta', description + yield "description", "meta", description # transform the yielded data, and add type annotations for data in fn(*args, **kw): @@ -39,25 +40,27 @@ def generator_mode(): if value is None: continue if isinstance(value, View): - yield key, 'ssz', serialize(value) + yield key, "ssz", serialize(value) elif isinstance(value, bytes): - yield key, 'ssz', value - elif isinstance(value, list) and all([isinstance(el, (View, bytes)) for el in value]): + yield key, "ssz", value + elif isinstance(value, list) and all( + [isinstance(el, View | bytes) for el in value] + ): for i, el in enumerate(value): if isinstance(el, View): - yield f'{key}_{i}', 'ssz', serialize(el) + yield f"{key}_{i}", "ssz", serialize(el) elif isinstance(el, bytes): - yield f'{key}_{i}', 'ssz', el - yield f'{key}_count', 'meta', len(value) + yield f"{key}_{i}", "ssz", el + yield f"{key}_count", "meta", len(value) else: # Not a ssz value. # The data will now just be yielded as any python data, # something that should be encodable by the generator runner. - yield key, 'data', value + yield key, "data", value # check generator mode, may be None/else. # "pop" removes it, so it is not passed to the inner function. - if kw.pop('generator_mode', False) is True: + if kw.pop("generator_mode", False) is True: # return the yielding function as a generator object. # Don't yield in this function itself, that would make pytest skip over it. return generator_mode() @@ -74,13 +77,14 @@ def generator_mode(): return runner -def with_meta_tags(tags: Dict[str, Any]): +def with_meta_tags(tags: dict[str, Any]): """ Decorator factory, yields meta tags (key, value) pairs to the output of the function. Useful to build test-vector annotations with. :param tags: dict of tags :return: Decorator. """ + def runner(fn): def entry(*args, **kw): yielded_any = False @@ -91,53 +95,8 @@ def entry(*args, **kw): # As a pytest, we do not want to be yielding anything (unsupported by pytest) if yielded_any: for k, v in tags.items(): - yield k, 'meta', v - return entry - return runner - - -def build_transition_test(fn, pre_fork_name, post_fork_name, fork_epoch=None): - """ - Handles the inner plumbing to generate `transition_test`s. - See that decorator in `context.py` for more information. - """ - def _adapter(*args, **kwargs): - post_spec = kwargs["phases"][post_fork_name] - - pre_fork_counter = 0 - - def pre_tag(obj): - nonlocal pre_fork_counter - pre_fork_counter += 1 - return obj - - def post_tag(obj): - return obj + yield k, "meta", v - yield "post_fork", "meta", post_fork_name - - has_fork_epoch = False - if fork_epoch: - kwargs["fork_epoch"] = fork_epoch - has_fork_epoch = True - yield "fork_epoch", "meta", fork_epoch - - # massage args to handle an optional custom state using - # `with_custom_state` decorator - expected_args = inspect.getfullargspec(fn) - if "phases" not in expected_args.kwonlyargs: - kwargs.pop("phases", None) - - for part in fn(*args, - post_spec=post_spec, - pre_tag=pre_tag, - post_tag=post_tag, - **kwargs): - if part[0] == "fork_epoch": - has_fork_epoch = True - yield part - assert has_fork_epoch + return entry - if pre_fork_counter > 0: - yield "fork_block", "meta", pre_fork_counter - 1 - return _adapter + return runner diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 9211e0ff0f..f080bf53cb 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -1,16 +1,83 @@ -from py_ecc.bls import G2ProofOfPossession as py_ecc_bls -from py_ecc.bls.g2_primatives import signature_to_G2 as _signature_to_G2 import milagro_bls_binding as milagro_bls # noqa: F401 for BLS switching option +import py_arkworks_bls12381 as arkworks_bls # noqa: F401 for BLS switching option +from py_arkworks_bls12381 import ( + G1Point as arkworks_G1, + G2Point as arkworks_G2, + GT as arkworks_GT, + Scalar as arkworks_Scalar, +) +from py_ecc.bls import G2ProofOfPossession as py_ecc_bls +from py_ecc.bls.g2_primitives import ( # noqa: F401 + curve_order as BLS_MODULUS, + G1_to_pubkey as py_ecc_G1_to_bytes48, + G2_to_signature as py_ecc_G2_to_bytes96, + pubkey_to_G1 as py_ecc_bytes48_to_G1, + signature_to_G2 as _signature_to_G2, + signature_to_G2 as py_ecc_bytes96_to_G2, +) +from py_ecc.optimized_bls12_381 import ( # noqa: F401 + add as py_ecc_add, + final_exponentiate as py_ecc_final_exponentiate, + FQ, + FQ2, + FQ12 as py_ecc_GT, + G1 as py_ecc_G1, + G2 as py_ecc_G2, + multiply as py_ecc_mul, + neg as py_ecc_neg, + pairing as py_ecc_pairing, + Z1 as py_ecc_Z1, + Z2 as py_ecc_Z2, +) +from py_ecc.utils import prime_field_inv as py_ecc_prime_field_inv + + +class py_ecc_Scalar(FQ): + field_modulus = BLS_MODULUS + + def __init__(self, value): + """ + Force underlying value to be a native integer. + """ + super().__init__(int(value)) + + def pow(self, exp): + """ + Raises the self to the power of the given exponent. + """ + return self ** int(exp) + + def inverse(self): + """ + Computes the modular inverse of self. + """ + return py_ecc_Scalar(py_ecc_prime_field_inv(self.n, self.field_modulus)) + + +class fastest_bls: + G1 = arkworks_G1 + G2 = arkworks_G2 + Scalar = arkworks_Scalar + GT = arkworks_GT + _AggregatePKs = milagro_bls._AggregatePKs + Sign = milagro_bls.Sign + Verify = milagro_bls.Verify + Aggregate = milagro_bls.Aggregate + AggregateVerify = milagro_bls.AggregateVerify + FastAggregateVerify = milagro_bls.FastAggregateVerify + SkToPk = milagro_bls.SkToPk + # Flag to make BLS active or not. Used for testing, do not ignore BLS in production unless you know what you are doing. bls_active = True -# To change bls implementation, default to PyECC for correctness. Milagro is a good faster alternative. -bls = py_ecc_bls +# Default to fastest_bls +bls = fastest_bls +Scalar = fastest_bls.Scalar -STUB_SIGNATURE = b'\x11' * 96 -STUB_PUBKEY = b'\x22' * 48 -G2_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 95 +STUB_SIGNATURE = b"\x11" * 96 +STUB_PUBKEY = b"\x22" * 48 +G2_POINT_AT_INFINITY = b"\xc0" + b"\x00" * 95 STUB_COORDINATES = _signature_to_G2(G2_POINT_AT_INFINITY) @@ -20,6 +87,18 @@ def use_milagro(): """ global bls bls = milagro_bls + global Scalar + Scalar = py_ecc_Scalar + + +def use_arkworks(): + """ + Shortcut to use Arkworks as BLS library + """ + global bls + bls = arkworks_bls + global Scalar + Scalar = arkworks_Scalar def use_py_ecc(): @@ -28,26 +107,44 @@ def use_py_ecc(): """ global bls bls = py_ecc_bls + global Scalar + Scalar = py_ecc_Scalar + + +def use_fastest(): + """ + Shortcut to use Milagro for signatures and Arkworks for other BLS operations + """ + global bls + bls = fastest_bls + global Scalar + Scalar = fastest_bls.Scalar def only_with_bls(alt_return=None): """ Decorator factory to make a function only run when BLS is active. Otherwise return the default. """ + def runner(fn): def entry(*args, **kw): if bls_active: return fn(*args, **kw) else: return alt_return + return entry + return runner @only_with_bls(alt_return=True) def Verify(PK, message, signature): try: - result = bls.Verify(PK, message, signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.Verify(PK, message, signature) + else: + result = bls.Verify(PK, message, signature) except Exception: result = False finally: @@ -57,7 +154,10 @@ def Verify(PK, message, signature): @only_with_bls(alt_return=True) def AggregateVerify(pubkeys, messages, signature): try: - result = bls.AggregateVerify(list(pubkeys), list(messages), signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.AggregateVerify(list(pubkeys), list(messages), signature) + else: + result = bls.AggregateVerify(list(pubkeys), list(messages), signature) except Exception: result = False finally: @@ -67,7 +167,10 @@ def AggregateVerify(pubkeys, messages, signature): @only_with_bls(alt_return=True) def FastAggregateVerify(pubkeys, message, signature): try: - result = bls.FastAggregateVerify(list(pubkeys), message, signature) + if bls == arkworks_bls: # no signature API in arkworks + result = py_ecc_bls.FastAggregateVerify(list(pubkeys), message, signature) + else: + result = bls.FastAggregateVerify(list(pubkeys), message, signature) except Exception: result = False finally: @@ -76,15 +179,19 @@ def FastAggregateVerify(pubkeys, message, signature): @only_with_bls(alt_return=STUB_SIGNATURE) def Aggregate(signatures): + if bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.Aggregate(signatures) return bls.Aggregate(signatures) @only_with_bls(alt_return=STUB_SIGNATURE) def Sign(SK, message): - if bls == py_ecc_bls: + if bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.Sign(SK, message) + elif bls == py_ecc_bls: return bls.Sign(SK, message) else: - return bls.Sign(SK.to_bytes(32, 'big'), message) + return bls.Sign(SK.to_bytes(32, "big"), message) @only_with_bls(alt_return=STUB_COORDINATES) @@ -100,12 +207,191 @@ def AggregatePKs(pubkeys): # milagro_bls._AggregatePKs checks KeyValidate internally pass + if bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls._AggregatePKs(list(pubkeys)) + return bls._AggregatePKs(list(pubkeys)) @only_with_bls(alt_return=STUB_SIGNATURE) def SkToPk(SK): - if bls == py_ecc_bls: - return bls.SkToPk(SK) + if bls == py_ecc_bls or bls == arkworks_bls: # no signature API in arkworks + return py_ecc_bls.SkToPk(SK) else: - return bls.SkToPk(SK.to_bytes(32, 'big')) + return bls.SkToPk(SK.to_bytes(32, "big")) + + +def pairing_check(values): + if bls == arkworks_bls or bls == fastest_bls: + p_q_1, p_q_2 = values + g1s = [p_q_1[0], p_q_2[0]] + g2s = [p_q_1[1], p_q_2[1]] + return arkworks_GT.multi_pairing(g1s, g2s) == arkworks_GT.one() + else: + p_q_1, p_q_2 = values + final_exponentiation = py_ecc_final_exponentiate( + py_ecc_pairing(p_q_1[1], p_q_1[0], final_exponentiate=False) + * py_ecc_pairing(p_q_2[1], p_q_2[0], final_exponentiate=False) + ) + return final_exponentiation == py_ecc_GT.one() + + +def add(lhs, rhs): + """ + Performs point addition of `lhs` and `rhs`. + The points can either be in G1 or G2. + """ + if bls == arkworks_bls or bls == fastest_bls: + return lhs + rhs + return py_ecc_add(lhs, rhs) + + +def multiply(point, scalar): + """ + Performs Scalar multiplication between + `point` and `scalar`. + `point` can either be in G1 or G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + if not isinstance(scalar, arkworks_Scalar): + return point * arkworks_Scalar(int(scalar)) + return point * scalar + return py_ecc_mul(point, int(scalar)) + + +def multi_exp(points, scalars): + """ + Performs a multi-scalar multiplication between + `points` and `scalars`. + `points` can either be in G1 or G2. + """ + # Since this method accepts either G1 or G2, we need to know + # the type of the point to return. Hence, we need at least one point. + if not points or not scalars: + raise Exception("Cannot call multi_exp with zero points or zero scalars") + + if bls == arkworks_bls or bls == fastest_bls: + # If using py_ecc Scalars, convert to arkworks Scalars. + if not isinstance(scalars[0], arkworks_Scalar): + scalars = [arkworks_Scalar(int(s)) for s in scalars] + + # Check if we need to perform a G1 or G2 multiexp + if isinstance(points[0], arkworks_G1): + return arkworks_G1.multiexp_unchecked(points, scalars) + elif isinstance(points[0], arkworks_G2): + return arkworks_G2.multiexp_unchecked(points, scalars) + else: + raise Exception("Invalid point type") + + result = None + if isinstance(points[0][0], FQ): + result = Z1() + elif isinstance(points[0][0], FQ2): + result = Z2() + else: + raise Exception("Invalid point type") + + for point, scalar in zip(points, scalars): + result = add(result, multiply(point, scalar)) + return result + + +def neg(point): + """ + Returns the point negation of `point` + `point` can either be in G1 or G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + return -point + return py_ecc_neg(point) + + +def Z1(): + """ + Returns the identity point in G1 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1.identity() + return py_ecc_Z1 + + +def Z2(): + """ + Returns the identity point in G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G2.identity() + return py_ecc_Z2 + + +def G1(): + """ + Returns the chosen generator point in G1 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1() + return py_ecc_G1 + + +def G2(): + """ + Returns the chosen generator point in G2 + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G2() + return py_ecc_G2 + + +def G1_to_bytes48(point): + """ + Serializes a point in G1. + Returns a bytearray of size 48 as + we use the compressed format + """ + if bls == arkworks_bls or bls == fastest_bls: + return bytes(point.to_compressed_bytes()) + return py_ecc_G1_to_bytes48(point) + + +def G2_to_bytes96(point): + """ + Serializes a point in G2. + Returns a bytearray of size 96 as + we use the compressed format + """ + if bls == arkworks_bls or bls == fastest_bls: + return bytes(point.to_compressed_bytes()) + return py_ecc_G2_to_bytes96(point) + + +def bytes48_to_G1(bytes48): + """ + Deserializes a purported compressed serialized + point in G1. + - No subgroup checks are performed + - If the bytearray is not a valid serialization + of a point in G1, then this method will raise + an exception + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G1.from_compressed_bytes_unchecked(bytes48) + return py_ecc_bytes48_to_G1(bytes48) + + +def bytes96_to_G2(bytes96): + """ + Deserializes a purported compressed serialized + point in G2. + - No subgroup checks are performed + - If the bytearray is not a valid serialization + of a point in G2, then this method will raise + an exception + """ + if bls == arkworks_bls or bls == fastest_bls: + return arkworks_G2.from_compressed_bytes_unchecked(bytes96) + return py_ecc_bytes96_to_G2(bytes96) + + +@only_with_bls(alt_return=True) +def KeyValidate(pubkey): + return py_ecc_bls.KeyValidate(pubkey) diff --git a/tests/core/pyspec/eth2spec/utils/hash_function.py b/tests/core/pyspec/eth2spec/utils/hash_function.py index 470cb1da91..6c145eb3b7 100644 --- a/tests/core/pyspec/eth2spec/utils/hash_function.py +++ b/tests/core/pyspec/eth2spec/utils/hash_function.py @@ -1,9 +1,9 @@ from hashlib import sha256 + from remerkleable.byte_arrays import Bytes32 -from typing import Union -ZERO_BYTES32 = b'\x00' * 32 +ZERO_BYTES32 = b"\x00" * 32 -def hash(x: Union[bytes, bytearray, memoryview]) -> Bytes32: +def hash(x: bytes | bytearray | memoryview) -> Bytes32: return Bytes32(sha256(x).digest()) diff --git a/tests/core/pyspec/eth2spec/utils/kzg.py b/tests/core/pyspec/eth2spec/utils/kzg.py new file mode 100644 index 0000000000..2ac588fb71 --- /dev/null +++ b/tests/core/pyspec/eth2spec/utils/kzg.py @@ -0,0 +1,125 @@ +# Ref: +# - https://github.com/ethereum/research/blob/8f084630528ba33d92b2bc05edf5338dd193c6f1/trusted_setup/trusted_setup.py +# - https://github.com/asn-d6/kzgverify +import json +import os +from collections.abc import Sequence +from pathlib import Path + +from eth_utils import encode_hex +from py_ecc.typing import ( + Optimized_Point3D, +) + +from eth2spec.utils import bls +from eth2spec.utils.bls import ( + BLS_MODULUS, +) + +PRIMITIVE_ROOT_OF_UNITY = 7 + + +def generate_setup( + generator: Optimized_Point3D, secret: int, length: int +) -> tuple[Optimized_Point3D]: + """ + Generate trusted setup of ``generator`` in ``length``. + """ + result = [generator] + for _ in range(1, length): + result.append(bls.multiply(result[-1], secret)) + return tuple(result) + + +def fft( + vals: Sequence[Optimized_Point3D], modulus: int, domain: int +) -> Sequence[Optimized_Point3D]: + """ + FFT for group elements + """ + if len(vals) == 1: + return vals + L = fft(vals[::2], modulus, domain[::2]) + R = fft(vals[1::2], modulus, domain[::2]) + o = [0] * len(vals) + for i, (x, y) in enumerate(zip(L, R)): + y_times_root = bls.multiply(y, domain[i]) + o[i] = bls.add(x, y_times_root) + o[i + len(L)] = bls.add(x, bls.neg(y_times_root)) + return o + + +def compute_root_of_unity(length: int) -> int: + """ + Generate a w such that ``w**length = 1``. + """ + assert (BLS_MODULUS - 1) % length == 0 + return pow(PRIMITIVE_ROOT_OF_UNITY, (BLS_MODULUS - 1) // length, BLS_MODULUS) + + +def compute_roots_of_unity(field_elements_per_blob: int) -> tuple[int]: + """ + Compute a list of roots of unity for a given order. + The order must divide the BLS multiplicative group order, i.e. BLS_MODULUS - 1 + """ + field_elements_per_blob = int(field_elements_per_blob) # to non-SSZ int + assert (BLS_MODULUS - 1) % field_elements_per_blob == 0 + root_of_unity = compute_root_of_unity(length=field_elements_per_blob) + + roots = [] + current_root_of_unity = 1 + for _ in range(field_elements_per_blob): + roots.append(current_root_of_unity) + current_root_of_unity = current_root_of_unity * root_of_unity % BLS_MODULUS + return tuple(roots) + + +def get_lagrange(setup: Sequence[Optimized_Point3D]) -> tuple[bytes]: + """ + Convert a G1 or G2 portion of a setup into the Lagrange basis. + """ + root_of_unity = compute_root_of_unity(len(setup)) + assert pow(root_of_unity, len(setup), BLS_MODULUS) == 1 + domain = [pow(root_of_unity, i, BLS_MODULUS) for i in range(len(setup))] + # TODO: introduce an IFFT function for simplicity + fft_output = fft(setup, BLS_MODULUS, domain) + inv_length = pow(len(setup), BLS_MODULUS - 2, BLS_MODULUS) + return tuple( + bls.G1_to_bytes48(bls.multiply(fft_output[-i], inv_length)) for i in range(len(fft_output)) + ) + + +def dump_kzg_trusted_setup_files( + secret: int, g1_length: int, g2_length: int, output_dir: str +) -> None: + bls.use_fastest() + + setup_g1 = generate_setup(bls.G1(), secret, g1_length) + setup_g2 = generate_setup(bls.G2(), secret, g2_length) + setup_g1_lagrange = get_lagrange(setup_g1) + roots_of_unity = compute_roots_of_unity(g1_length) + + serialized_setup_g1 = [encode_hex(bls.G1_to_bytes48(p)) for p in setup_g1] + serialized_setup_g2 = [encode_hex(bls.G2_to_bytes96(p)) for p in setup_g2] + serialized_setup_g1_lagrange = [encode_hex(x) for x in setup_g1_lagrange] + + output_dir_path = Path(output_dir) + + if not os.path.exists(output_dir_path): + os.makedirs(output_dir_path) + print("Created directory: ", output_dir_path) + + file_path = output_dir_path / "testing_trusted_setups.json" + + with open(file_path, "w+") as f: + json.dump( + { + "setup_G1": serialized_setup_g1, + "setup_G2": serialized_setup_g2, + "setup_G1_lagrange": serialized_setup_g1_lagrange, + "roots_of_unity": roots_of_unity, + }, + f, + ) + + print(f"Generated trusted setup file: {file_path}\n") diff --git a/tests/core/pyspec/eth2spec/utils/merkle_minimal.py b/tests/core/pyspec/eth2spec/utils/merkle_minimal.py index aae7ff5c06..1a889c02f0 100644 --- a/tests/core/pyspec/eth2spec/utils/merkle_minimal.py +++ b/tests/core/pyspec/eth2spec/utils/merkle_minimal.py @@ -1,8 +1,8 @@ -from eth2spec.utils.hash_function import hash from math import log2 +from eth2spec.utils.hash_function import hash -ZERO_BYTES32 = b'\x00' * 32 +ZERO_BYTES32 = b"\x00" * 32 zerohashes = [ZERO_BYTES32] for layer in range(1, 100): @@ -66,7 +66,9 @@ def merge(h, i): while True: if i & (1 << j) == 0: if i == count and j < depth: - h = hash(h + zerohashes[j]) # keep going if we are complementing the void to the next power of 2 + h = hash( + h + zerohashes[j] + ) # keep going if we are complementing the void to the next power of 2 else: break else: diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py index 65808038ea..f15b54b2da 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_impl.py @@ -1,14 +1,26 @@ from typing import TypeVar from remerkleable.basic import uint -from remerkleable.core import View from remerkleable.byte_arrays import Bytes32 +from remerkleable.core import Type, View -def serialize(obj: View) -> bytes: +def ssz_serialize(obj: View) -> bytes: return obj.encode_bytes() +def serialize(obj: View) -> bytes: + return ssz_serialize(obj) + + +def ssz_deserialize(typ: Type[View], data: bytes) -> View: + return typ.decode_bytes(data) + + +def deserialize(typ: Type[View], data: bytes) -> View: + return ssz_deserialize(typ, data) + + def hash_tree_root(obj: View) -> Bytes32: return Bytes32(obj.get_backing().merkle_root()) @@ -17,7 +29,7 @@ def uint_to_bytes(n: uint) -> bytes: return serialize(n) -V = TypeVar('V', bound=View) +V = TypeVar("V", bound=View) # Helper method for typing copies, and avoiding a example_input.copy() method call, instead of copy(example_input) diff --git a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py index 5a1b61d0be..b4dd7c9710 100644 --- a/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py +++ b/tests/core/pyspec/eth2spec/utils/ssz/ssz_typing.py @@ -1,12 +1,31 @@ -# flake8: noqa -# Ignore linter: This module makes importing SSZ types easy, and hides away the underlying library from the spec. +# ruff: noqa: F401 -from remerkleable.complex import Container, Vector, List +from remerkleable.basic import ( + bit, + boolean, + byte, + uint, + uint8, + uint16, + uint32, + uint64, + uint128, + uint256, +) +from remerkleable.bitfields import Bitlist, Bitvector +from remerkleable.byte_arrays import ( + ByteList, + Bytes1, + Bytes4, + Bytes8, + Bytes32, + Bytes48, + Bytes96, + ByteVector, +) +from remerkleable.complex import Container, List, Vector +from remerkleable.core import BasicView, Path, View from remerkleable.union import Union -from remerkleable.basic import boolean, bit, uint, byte, uint8, uint16, uint32, uint64, uint128, uint256 -from remerkleable.bitfields import Bitvector, Bitlist -from remerkleable.byte_arrays import ByteVector, Bytes1, Bytes4, Bytes8, Bytes32, Bytes48, Bytes96, ByteList -from remerkleable.core import BasicView, View, Path - Bytes20 = ByteVector[20] # type: ignore +Bytes31 = ByteVector[31] # type: ignore diff --git a/tests/core/pyspec/eth2spec/utils/test_merkle_minimal.py b/tests/core/pyspec/eth2spec/utils/test_merkle_minimal.py index 3746ea6ca3..97c56374a5 100644 --- a/tests/core/pyspec/eth2spec/utils/test_merkle_minimal.py +++ b/tests/core/pyspec/eth2spec/utils/test_merkle_minimal.py @@ -1,6 +1,7 @@ import pytest -from .merkle_minimal import zerohashes, merkleize_chunks, get_merkle_root + from .hash_function import hash +from .merkle_minimal import get_merkle_root, merkleize_chunks, zerohashes def h(a: bytes, b: bytes) -> bytes: @@ -9,7 +10,7 @@ def h(a: bytes, b: bytes) -> bytes: def e(v: int) -> bytes: # prefix with 0xfff... to make it non-zero - return b'\xff' * 28 + v.to_bytes(length=4, byteorder='little') + return b"\xff" * 28 + v.to_bytes(length=4, byteorder="little") def z(i: int) -> bytes: @@ -56,12 +57,19 @@ def z(i: int) -> bytes: (6, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(z(0), z(0)))), z(3))), (7, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), z(0)))), z(3))), (8, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), z(3))), - (9, 16, h(h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), h(h(h(e(8), z(0)), z(1)), z(2)))), + ( + 9, + 16, + h( + h(h(h(e(0), e(1)), h(e(2), e(3))), h(h(e(4), e(5)), h(e(6), e(7)))), + h(h(h(e(8), z(0)), z(1)), z(2)), + ), + ), ] @pytest.mark.parametrize( - 'count,limit,value', + "count,limit,value", cases, ) def test_merkleize_chunks_and_get_merkle_root(count, limit, value): diff --git a/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py index e1d59fa8c3..aca064632b 100644 --- a/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py +++ b/tests/core/pyspec/eth2spec/utils/test_merkle_proof_util.py @@ -1,9 +1,9 @@ import pytest - # Note: these functions are extract from merkle-proofs.md (deprecated), # the tests are temporary to show correctness while the document is still there. + def get_power_of_two_ceil(x: int) -> int: if x <= 1: return 1 @@ -23,16 +23,34 @@ def get_power_of_two_floor(x: int) -> int: power_of_two_ceil_cases = [ - (0, 1), (1, 1), (2, 2), (3, 4), (4, 4), (5, 8), (6, 8), (7, 8), (8, 8), (9, 16), + (0, 1), + (1, 1), + (2, 2), + (3, 4), + (4, 4), + (5, 8), + (6, 8), + (7, 8), + (8, 8), + (9, 16), ] power_of_two_floor_cases = [ - (0, 1), (1, 1), (2, 2), (3, 2), (4, 4), (5, 4), (6, 4), (7, 4), (8, 8), (9, 8), + (0, 1), + (1, 1), + (2, 2), + (3, 2), + (4, 4), + (5, 4), + (6, 4), + (7, 4), + (8, 8), + (9, 8), ] @pytest.mark.parametrize( - 'value,expected', + "value,expected", power_of_two_ceil_cases, ) def test_get_power_of_two_ceil(value, expected): @@ -40,7 +58,7 @@ def test_get_power_of_two_ceil(value, expected): @pytest.mark.parametrize( - 'value,expected', + "value,expected", power_of_two_floor_cases, ) def test_get_power_of_two_floor(value, expected): diff --git a/tests/formats/README.md b/tests/formats/README.md index e4f2bcb29d..c7a11b19af 100644 --- a/tests/formats/README.md +++ b/tests/formats/README.md @@ -1,41 +1,46 @@ # General test format -This document defines the YAML format and structure used for consensus spec testing. - -## Table of contents - - -* [About](#about) - + [Test-case formats](#test-case-formats) -* [Glossary](#glossary) -* [Test format philosophy](#test-format-philosophy) - + [Config design](#config-design) - + [Test completeness](#test-completeness) -* [Test structure](#test-structure) - + [`/`](#--config-name---) - + [`/`](#--fork-or-phase-name---) - + [`/`](#--test-runner-name---) - + [`/`](#--test-handler-name---) - + [`/`](#--test-suite-name---) - + [`/`](#--test-case---) - + [``](#--output-part--) +This document defines the YAML format and structure used for consensus spec +testing. + + + +- [About](#about) + - [Test-case formats](#test-case-formats) +- [Glossary](#glossary) +- [Test format philosophy](#test-format-philosophy) + - [Config design](#config-design) + - [Test completeness](#test-completeness) +- [Test structure](#test-structure) + - [`/`](#config-name) + - [`/`](#fork-or-phase-name) + - [`/`](#test-runner-name) + - [`/`](#test-handler-name) + - [`/`](#test-suite-name) + - [`/`](#test-case) + - [``](#output-part) + - [Common output formats](#common-output-formats) - [Special output parts](#special-output-parts) - * [`meta.yaml`](#-metayaml-) -* [Config](#config) -* [Config sourcing](#config-sourcing) -* [Note for implementers](#note-for-implementers) + - [`meta.yaml`](#metayaml) + - [`config.yaml`](#configyaml) +- [Config sourcing](#config-sourcing) +- [Note for implementers](#note-for-implementers) - + ## About -Ethereum 2.0 uses YAML as the format for all cross client tests. This document describes at a high level the general format to which all test files should conform. +The consensus layer uses YAML as the format for all cross client tests. This +document describes at a high level the general format to which all test files +should conform. ### Test-case formats -The particular formats of specific types of tests (test suites) are defined in separate documents. +The particular formats of specific types of tests (test suites) are defined in +separate documents. Test formats: + - [`bls`](./bls/README.md) - [`epoch_processing`](./epoch_processing/README.md) - [`genesis`](./genesis/README.md) @@ -46,53 +51,72 @@ Test formats: - [`ssz_static`](./ssz_static/README.md) - More formats are planned, see tracking issues for CI/testing - ## 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, - `general` may be used as a catch-all for tests not restricted to one configuration. (E.g. BLS). +- `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, `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"*. - - A `runner` focuses on *only one* `type`, and each type has *only one* `runner`. -- `handler`: a `runner` may be too limited sometimes, you may have a set of tests with a specific focus that requires a different format. - 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, - 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. + - A `runner` focuses on *only one* `type`, and each type has *only one* + `runner`. +- `handler`: a `runner` may be too limited sometimes, you may have a set of + tests with a specific focus that requires a different format. 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, 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. ## Test format philosophy ### Config design 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 - `(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*. - 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). - -Based on these types of changes, we model the config as a list of key value pairs, - that only grows with every fork (they may change in development versions of forks, however; git manages this). -With this approach, configurations are backwards compatible (older clients ignore unknown variables) and easy to maintain. +- 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*. + 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). + +Based on these types of changes, we model the config as a list of key value +pairs, that only grows with every fork (they may change in development versions +of forks, however; git manages this). With this approach, configurations are +backwards compatible (older clients ignore unknown variables) and easy to +maintain. ### Test completeness -Tests should be independent of any sync-data. If one wants to run a test, the input data should be available from the YAML. -The aim is to provide clients with a well-defined scope of work to run a particular set of test-suites. - -- Clients that are complete are expected to contribute to testing, seeking for better resources to get conformance with the spec, and other clients. -- Clients that are not complete in functionality can choose to ignore suites that use certain test-runners, or specific handlers of these test-runners. -- Clients that are on older versions can test their work based on older releases of the generated tests, and catch up with newer releases when possible. +Tests should be independent of any sync-data. If one wants to run a test, the +input data should be available from the YAML. The aim is to provide clients with +a well-defined scope of work to run a particular set of test-suites. +- Clients that are complete are expected to contribute to testing, seeking for + better resources to get conformance with the spec, and other clients. +- Clients that are not complete in functionality can choose to ignore suites + that use certain test-runners, or specific handlers of these test-runners. +- Clients that are on older versions can test their work based on older releases + of the generated tests, and catch up with newer releases when possible. ## Test structure @@ -103,66 +127,82 @@ tests///// ### `/` -Configs are upper level. Some clients want to run minimal first, and useful for sanity checks during development too. -As a top level dir, it is not duplicated, and the used config can be copied right into this directory as reference. +Configs are upper level. Some clients want to run minimal first, and useful for +sanity checks during development too. As a top level dir, it is not duplicated, +and the used config can be copied right into this directory as reference. ### `/` -This would be: "phase0", "altair", etc. Each introduces new tests, and modifies any tests that change: -some tests of earlier forks repeat with updated state data. +This would be: "phase0", "altair", etc. Each introduces new tests, and modifies +any tests that change: some tests of earlier forks repeat with updated state +data. ### `/` -The well known bls/shuffling/ssz_static/operations/epoch_processing/etc. Handlers can change the format, but there is a general target to test. - +The well known bls/shuffling/ssz_static/operations/epoch_processing/etc. +Handlers can change the format, but there is a general target to test. ### `/` -Specialization within category. All suites in here will have the same test case format. -Using a `handler` in a `runner` is optional. A `core` (or other generic) handler may be used if the `runner` does not have different formats. +Specialization within category. All suites in here will have the same test case +format. Using a `handler` in a `runner` is optional. A `core` (or other generic) +handler may be used if the `runner` does not have different formats. ### `/` -Suites are split up. Suite size (i.e. the amount of tests) does not change the maximum memory requirement, as test cases can be loaded one by one. -This also makes filtered sets of tests fast and easy to load. +Suites are split up. Suite size (i.e. the amount of tests) does not change the +maximum memory requirement, as test cases can be loaded one by one. This also +makes filtered sets of tests fast and easy to load. ### `/` -Cases are split up too. This enables diffing of parts of the test case, tracking changes per part, while still using LFS. Also enables different formats for some parts. +Cases are split up too. This enables diffing of parts of the test case, tracking +changes per part, while still using LFS. Also enables different formats for some +parts. ### `` -These files allow for custom formats for some parts of the test. E.g. something encoded in SSZ. -Or to avoid large files, the SSZ can be compressed with Snappy. +These files allow for custom formats for some parts of the test. E.g. something +encoded in SSZ. Or to avoid large files, the SSZ can be compressed with Snappy. E.g. `pre.ssz_snappy`, `deposit.ssz_snappy`, `post.ssz_snappy`. -Diffing a `pre.ssz_snappy` and `post.ssz_snappy` provides all the information for testing, when decompressed and decoded. -Then the difference between pre and post can be compared to anything that changes the pre state, e.g. `deposit.ssz_snappy` +Diffing a `pre.ssz_snappy` and `post.ssz_snappy` provides all the information +for testing, when decompressed and decoded. Then the difference between pre and +post can be compared to anything that changes the pre state, e.g. +`deposit.ssz_snappy` -Note that by default, the SSZ data is in the given test case's version, e.g., if it's `altair` test case, use `altair.BeaconState` container to deserialize the given state. +Note that by default, the SSZ data is in the given test case's + version, e.g., if it's `altair` test case, use +`altair.BeaconState` container to deserialize the given state. -YAML is generally used for test metadata, and for tests that do not use SSZ: e.g. shuffling and BLS tests. -In this case, there is no point in adding special SSZ types. And the size and efficiency of YAML is acceptable. +YAML is generally used for test metadata, and for tests that do not use SSZ: +e.g. shuffling and BLS tests. In this case, there is no point in adding special +SSZ types. And the size and efficiency of YAML is acceptable. #### Common output formats Between all types of tests, a few formats are common: -- **`.yaml`**: A YAML file containing structured data to describe settings or test contents. -- **`.ssz`**: A file containing raw SSZ-encoded data. Previously widely used in tests, but replaced with compressed variant. +- **`.yaml`**: A YAML file containing structured data to describe settings or + test contents. +- **`.ssz`**: A file containing raw SSZ-encoded data. Previously widely used in + tests, but replaced with compressed variant. - **`.ssz_snappy`**: Like `.ssz`, but compressed with Snappy block compression. - Snappy block compression is already applied to SSZ in consensus-layer gossip, available in client implementations, and thus chosen as compression method. - + Snappy block compression is already applied to SSZ in consensus-layer gossip, + available in client implementations, and thus chosen as compression method. #### Special output parts ##### `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: -Some test-case formats share some common key-value pair patterns, and these are documented here: +Some test-case formats share some common key-value pair patterns, and these are +documented here: ``` bls_setting: int -- optional, can have 3 different values: @@ -171,20 +211,18 @@ bls_setting: int -- optional, can have 3 different values: but there is no change of outcome when running the test if BLS is ON or OFF. 1: known as "BLS required" - if the test validity is strictly dependent on BLS being ON 2: known as "BLS ignored" - if the test validity is strictly dependent on BLS being OFF -reveal_deadlines_setting: -- optional, can have 2 different values: - 0: default, `process_reveal_deadlines` is ON. - 1: `process_reveal_deadlines` is OFF. ``` ##### `config.yaml` -The runtime-configurables may be different for specific tests. -When present, this replaces the default runtime-config that comes with the otherwise compile-time preset settings. +The runtime-configurables may be different for specific tests. When present, +this replaces the default runtime-config that comes with the otherwise +compile-time preset settings. The format matches that of the `mainnet_config.yaml` and `minimal_config.yaml`, -see the [`/configs`](../../configs/README.md#format) documentation. -Config values that are introduced at a later fork may be omitted from tests of previous forks. - +see the [`/configs`](../../configs/README.md#format) documentation. Config +values that are introduced at a later fork may be omitted from tests of previous +forks. ## Config sourcing @@ -200,21 +238,23 @@ And copied by CI for testing purposes to: /tests//.yaml ``` -The first `` is a directory, which contains exactly all tests that make use of the given config. - +The first `` is a directory, which contains exactly all tests that +make use of the given config. ## Note for implementers 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. -3. Select the category and specialization of interest (e.g. `operations > deposits`). Again, repeat for each if running all. +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. +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. 6. Load the parts of the case. And `meta.yaml` if present. 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. +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. diff --git a/tests/formats/bls/README.md b/tests/formats/bls/README.md index 77a9654a8d..4b238c0c72 100644 --- a/tests/formats/bls/README.md +++ b/tests/formats/bls/README.md @@ -1,7 +1,8 @@ # BLS tests -A test type for BLS. Primarily geared towards verifying the *integration* of any BLS library. -We do not recommend rolling your own crypto or using an untested BLS library. +A test type for BLS. Primarily geared towards verifying the *integration* of any +BLS library. We do not recommend rolling your own crypto or using an untested +BLS library. The BLS test suite runner has the following handlers: @@ -13,4 +14,5 @@ The BLS test suite runner has the following handlers: - [`sign`](./sign.md) - [`verify`](./verify.md) -*Note*: Signature-verification and aggregate-verify test cases are not yet supported. +*Note*: Signature-verification and aggregate-verify test cases are not yet +supported. diff --git a/tests/formats/bls/aggregate.md b/tests/formats/bls/aggregate.md index 81ce85fe66..de34085de9 100644 --- a/tests/formats/bls/aggregate.md +++ b/tests/formats/bls/aggregate.md @@ -1,6 +1,7 @@ # Test format: BLS signature aggregation -A BLS signature aggregation combines a series of signatures into a single signature. +A BLS signature aggregation combines a series of signatures into a single +signature. ## Test case format @@ -8,14 +9,17 @@ The test data is declared in a `data.yaml` file: ```yaml input: List[BLS Signature] -- list of input BLS signatures -output: BLS Signature -- expected output, single BLS signature or empty. +output: BLS Signature -- expected output, single BLS signature or `null`. ``` -- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. -- No output value if the input is invalid. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes + (192 nibbles), prefixed with `0x`. +- output value is `null` if the input is invalid. -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. ## Condition -The `aggregate` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. +The `aggregate` handler should aggregate the signatures in the `input`, and the +result should match the expected `output`. diff --git a/tests/formats/bls/aggregate_verify.md b/tests/formats/bls/aggregate_verify.md index 9b251af46e..891c7a75bd 100644 --- a/tests/formats/bls/aggregate_verify.md +++ b/tests/formats/bls/aggregate_verify.md @@ -14,11 +14,15 @@ input: output: bool -- true (VALID) or false (INVALID) ``` -- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. -- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 + nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes + (192 nibbles), prefixed with `0x`. -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. ## Condition -The `aggregate_verify` handler should verify the signature with pubkeys and messages in the `input`, and the result should match the expected `output`. +The `aggregate_verify` handler should verify the signature with pubkeys and +messages in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth_aggregate_pubkeys.md b/tests/formats/bls/eth_aggregate_pubkeys.md index 4f66adec21..677e2e6083 100644 --- a/tests/formats/bls/eth_aggregate_pubkeys.md +++ b/tests/formats/bls/eth_aggregate_pubkeys.md @@ -8,12 +8,14 @@ The test data is declared in a `data.yaml` file: ```yaml input: List[BLS Pubkey] -- list of input BLS pubkeys -output: BLSPubkey -- expected output, single BLS pubkeys or empty. +output: BLSPubkey -- expected output, single BLS pubkeys or `null`. ``` -- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. -- No output value if the input is invalid. +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 + nibbles), prefixed with `0x`. +- output value is `null` if the input is invalid. ## Condition -The `eth_aggregate_pubkeys` handler should aggregate the signatures in the `input`, and the result should match the expected `output`. +The `eth_aggregate_pubkeys` handler should aggregate the signatures in the +`input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/eth_fast_aggregate_verify.md b/tests/formats/bls/eth_fast_aggregate_verify.md index 83b5484e05..e4ac539f28 100644 --- a/tests/formats/bls/eth_fast_aggregate_verify.md +++ b/tests/formats/bls/eth_fast_aggregate_verify.md @@ -14,11 +14,15 @@ input: output: bool -- true (VALID) or false (INVALID) ``` -- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. -- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 + nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes + (192 nibbles), prefixed with `0x`. -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. ## Condition -The `eth_fast_aggregate_verify` handler should verify the signature with pubkeys and message in the `input`, and the result should match the expected `output`. +The `eth_fast_aggregate_verify` handler should verify the signature with pubkeys +and message in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/fast_aggregate_verify.md b/tests/formats/bls/fast_aggregate_verify.md index 38ea29bb5f..bd7fffdb84 100644 --- a/tests/formats/bls/fast_aggregate_verify.md +++ b/tests/formats/bls/fast_aggregate_verify.md @@ -14,11 +14,15 @@ input: output: bool -- true (VALID) or false (INVALID) ``` -- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 nibbles), prefixed with `0x`. -- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes (192 nibbles), prefixed with `0x`. +- `BLS Pubkey` here is encoded as a string: hexadecimal encoding of 48 bytes (96 + nibbles), prefixed with `0x`. +- `BLS Signature` here is encoded as a string: hexadecimal encoding of 96 bytes + (192 nibbles), prefixed with `0x`. -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. ## Condition -The `fast_aggregate_verify` handler should verify the signature with pubkeys and message in the `input`, and the result should match the expected `output`. +The `fast_aggregate_verify` handler should verify the signature with pubkeys and +message in the `input`, and the result should match the expected `output`. diff --git a/tests/formats/bls/sign.md b/tests/formats/bls/sign.md index 93001beeed..a4f940069c 100644 --- a/tests/formats/bls/sign.md +++ b/tests/formats/bls/sign.md @@ -10,7 +10,14 @@ The test data is declared in a `data.yaml` file: input: privkey: bytes32 -- the private key used for signing message: bytes32 -- input message to sign (a hash) -output: BLS Signature -- expected output, single BLS signature or empty. +output: BLS Signature -- expected output, single BLS signature or `null`. ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +- All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with + `0x`. +- output value is `null` if the input is invalid. + +## Condition + +The `sign` handler should sign `message` with `privkey`, and the resulting +signature should match the expected `output`. diff --git a/tests/formats/bls/verify.md b/tests/formats/bls/verify.md index 57ec8a33a7..0c95de2e70 100644 --- a/tests/formats/bls/verify.md +++ b/tests/formats/bls/verify.md @@ -14,4 +14,5 @@ input: output: bool -- VALID or INVALID ``` -All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. diff --git a/tests/formats/epoch_processing/README.md b/tests/formats/epoch_processing/README.md index 1032026a63..8f669a134b 100644 --- a/tests/formats/epoch_processing/README.md +++ b/tests/formats/epoch_processing/README.md @@ -1,9 +1,10 @@ # Epoch processing tests The different epoch sub-transitions are tested individually with test handlers. -The format is similar to block-processing state-transition tests. -There is no "change" factor however, the transitions are pure functions with just the pre-state as input. -Hence, the format is shared between each test-handler. (See test condition documentation on how to run the tests.) +The format is similar to block-processing state-transition tests. There is no +"change" factor however, the transitions are pure functions with just the +pre-state as input. Hence, the format is shared between each test-handler. (See +test condition documentation on how to run the tests.) ## Test case format @@ -17,33 +18,65 @@ bls_setting: int -- see general test-format spec. ### `pre.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state before running the epoch sub-transition. +An SSZ-snappy encoded `BeaconState`, the state before running the epoch +sub-transition. ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the epoch sub-transition. +An SSZ-snappy encoded `BeaconState`, the state after applying the epoch +sub-transition. No value if the sub-transition processing is aborted. + +### `pre_epoch.ssz_snappy` + +An SSZ-snappy encoded `BeaconState`, the state before running the epoch +transition. + +### `post_epoch.ssz_snappy` + +An SSZ-snappy encoded `BeaconState`, the state after applying the epoch +transition. No value if the transition processing is aborted. ## Condition -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. +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. Sub-transitions: -- `justification_and_finalization` -- `inactivity_updates` (Altair) -- `rewards_and_penalties` -- `registry_updates` -- `slashings` -- `eth1_data_reset` -- `effective_balance_updates` -- `slashings_reset` -- `randao_mixes_reset` -- `historical_roots_update` -- `participation_record_updates` (Phase 0 only) -- `participation_flag_updates` (Altair) -- `sync_committee_updates` (Altair) +- `eth1_data_reset` (>=Phase0) +- `historical_roots_update` (>=Phase0,\<=Bellatrix) +- `justification_and_finalization` (>=Phase0) +- `participation_record_updates` (==Phase0) +- `randao_mixes_reset` (>=Phase0) +- `registry_updates` (>=Phase0) +- `rewards_and_penalties` (>=Phase0) +- `slashings_reset` (>=Phase0) +- `slashings` (>=Phase0) +- `inactivity_updates` (>=Altair) +- `participation_flag_updates` (>=Altair) +- `sync_committee_updates` (>=Altair) +- `historical_summaries_update` (>=Capella) +- `effective_balance_updates` (>=Electra) +- `pending_consolidations` (>=Electra) +- `pending_deposits` (>=Electra) The resulting state should match the expected `post` state. + +## Condition (alternative) + +Instead of having a different handler for each sub-transition, a single handler +for all cases should load `pre_full` state, call `process_epoch` and then assert +that the result state should match `post_full` state. + +This has the advantages: + +- Less code to maintain for the epoch processing handler. +- Works with single pass epoch processing. +- Can detect bugs related to data dependencies between different + sub-transitions. + +As a disadvantage this condition takes more resources to compute, but just a +constant amount per test vector. diff --git a/tests/formats/finality/README.md b/tests/formats/finality/README.md index af39f5c8ca..0faa49e9cd 100644 --- a/tests/formats/finality/README.md +++ b/tests/formats/finality/README.md @@ -16,15 +16,16 @@ blocks_count: int -- the number of blocks processed in this test. ### `pre.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state before running the block transitions. +An SSZ-snappy encoded `BeaconState`, the state before running the block +transitions. Also available as `pre.ssz_snappy`. - ### `blocks_.yaml` -A series of files, with `` in range `[0, blocks_count)`. Blocks need to be processed in order, - following the main transition function (i.e. process slot and epoch transitions in between blocks as normal) +A series of files, with `` in range `[0, blocks_count)`. Blocks need to +be processed in order, following the main transition function (i.e. process slot +and epoch transitions in between blocks as normal) Each file is a YAML-encoded `SignedBeaconBlock`. @@ -32,10 +33,10 @@ Each block is also available as `blocks_.ssz_snappy` ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the block transitions. - +An SSZ-snappy encoded `BeaconState`, the state after applying the block +transitions. ## Condition -The resulting state should match the expected `post` state, or if the `post` state is left blank, - the handler should reject the series of blocks as invalid. +The resulting state should match the expected `post` state, or if the `post` +state is left blank, the handler should reject the series of blocks as invalid. diff --git a/tests/formats/fork_choice/README.md b/tests/formats/fork_choice/README.md index cfc86776dd..2fc204e061 100644 --- a/tests/formats/fork_choice/README.md +++ b/tests/formats/fork_choice/README.md @@ -1,6 +1,27 @@ # Fork choice tests -The aim of the fork choice tests is to provide test coverage of the various components of the fork choice. +The aim of the fork choice tests is to provide test coverage of the various +components of the fork choice. + + + +- [Test case format](#test-case-format) + - [`meta.yaml`](#metayaml) + - [`anchor_state.ssz_snappy`](#anchor_statessz_snappy) + - [`anchor_block.ssz_snappy`](#anchor_blockssz_snappy) + - [`steps.yaml`](#stepsyaml) + - [`on_tick` execution step](#on_tick-execution-step) + - [`on_attestation` execution step](#on_attestation-execution-step) + - [`on_block` execution step](#on_block-execution-step) + - [`on_merge_block` execution step](#on_merge_block-execution-step) + - [`on_attester_slashing` execution step](#on_attester_slashing-execution-step) + - [`on_payload_info` execution step](#on_payload_info-execution-step) + - [Checks step](#checks-step) + - [`attestation_<32-byte-root>.ssz_snappy`](#attestation_32-byte-rootssz_snappy) + - [`block_<32-byte-root>.ssz_snappy`](#block_32-byte-rootssz_snappy) +- [Condition](#condition) + + ## Test case format @@ -13,15 +34,20 @@ bls_setting: int -- see general test-format spec. ### `anchor_state.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state to initialize store with `get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock)` helper. +An SSZ-snappy encoded `BeaconState`, the state to initialize store with +`get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock)` +helper. ### `anchor_block.ssz_snappy` -An SSZ-snappy encoded `BeaconBlock`, the block to initialize store with `get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock)` helper. +An SSZ-snappy encoded `BeaconBlock`, the block to initialize store with +`get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock)` +helper. ### `steps.yaml` -The steps to execute in sequence. There may be multiple items of the following types: +The steps to execute in sequence. There may be multiple items of the following +types: #### `on_tick` execution step @@ -39,7 +65,8 @@ After this step, the `store` object may have been updated. #### `on_attestation` execution step -The parameter that is required for executing `on_attestation(store, attestation)`. +The parameter that is required for executing +`on_attestation(store, attestation)`. ```yaml { @@ -49,6 +76,7 @@ The parameter that is required for executing `on_attestation(store, attestation) If it's `false`, this execution step is expected to be invalid. } ``` + The file is located in the same folder (see below). After this step, the `store` object may have been updated. @@ -59,25 +87,110 @@ The parameter that is required for executing `on_block(store, block)`. ```yaml { - block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. - To execute `on_block(store, block)` with the given attestation. - valid: bool -- optional, default to `true`. - If it's `false`, this execution step is expected to be invalid. -} + block: string -- the name of the `block_<32-byte-root>.ssz_snappy` file. + To execute `on_block(store, block)` with the given attestation. + blobs: string -- optional, the name of the `blobs_<32-byte-root>.ssz_snappy` file. + The blobs file content is a `List[Blob, MAX_BLOB_COMMITMENTS_PER_BLOCK]` SSZ object. + proofs: array of byte48 hex string -- optional, the proofs of blob commitments. + columns: string -- optional, array of the names of the `column_<32-byte-root>.ssz_snappy` files. + 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). +`blobs` and `proofs` are new fields from Deneb EIP-4844. These fields indicate +the expected values from `retrieve_blobs_and_proofs()` helper inside +`is_data_available()` helper. If these two fields are not provided, +`retrieve_blobs_and_proofs()` returns empty lists. + +`columns` is a new field in Fulu EIP-7594. This field indicate the expected +values from `retrieve_column_sidecars` helper inside `is_data_available()` +helper. If this field is an empty array, `retrieve_column_sidecars` should throw +an exception (not enough data sampled). If this field is not provided, +`retrieve_column_sidecars` returns an empty list. + +Post-Deneb and pre-Fulu, `columns` should not be present. Post-Fulu `blobs` and +`proofs` should not be present. + After this step, the `store` object may have been updated. +#### `on_merge_block` execution step + +Adds `PowBlock` data which is required for executing `on_block(store, block)`. + +```yaml +{ + pow_block: string -- the name of the `pow_block_<32-byte-root>.ssz_snappy` file. + To be used in `get_pow_block` lookup +} +``` + +The file is located in the same folder (see below). PowBlocks should be used as +return values for `get_pow_block(hash: Hash32) -> PowBlock` function if hashes +match. + +#### `on_attester_slashing` execution step + +The parameter that is required for executing +`on_attester_slashing(store, attester_slashing)`. + +```yaml +{ + attester_slashing: string -- the name of the `attester_slashing_<32-byte-root>.ssz_snappy` file. + To execute `on_attester_slashing(store, attester_slashing)` with the given attester slashing. + 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). + +After this step, the `store` object may have been updated. + +#### `on_payload_info` execution step + +Optional step for optimistic sync tests. + +```yaml +{ + block_hash: string, -- Encoded 32-byte value of payload's block hash. + payload_status: { + status: string, -- Enum, "VALID" | "INVALID" | "SYNCING" | "ACCEPTED" | "INVALID_BLOCK_HASH". + latest_valid_hash: string, -- Encoded 32-byte value of the latest valid block hash, may be `null`. + validation_error: string, -- Message providing additional details on the validation error, may be `null`. + } +} +``` + +This step sets the +[`payloadStatus`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#payloadstatusv1) +value that Execution Layer client mock returns in responses to the following +Engine API calls: + +- [`engine_newPayloadV1(payload)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_newpayloadv1) + if `payload.blockHash == payload_info.block_hash` +- [`engine_forkchoiceUpdatedV1(forkchoiceState, ...)`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_forkchoiceupdatedv1) + if `forkchoiceState.headBlockHash == payload_info.block_hash` + +*Note*: Status of a payload must be *initialized* via `on_payload_info` before +the corresponding `on_block` execution step. + +*Note*: Status of the same payload may be updated for several times throughout +the test. + #### Checks step The checks to verify the current status of `store`. ```yaml -checks: {: value} -- the assertions. +checks: {: value} -- the assertions. ``` -`` is the field member or property of [`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by client implementation. Currently, the possible fields included: +`` is the field member or property of +[`Store`](../../../specs/phase0/fork-choice.md#store) object that maintained by +client implementation. The fields include: ```yaml head: { @@ -94,23 +207,43 @@ finalized_checkpoint: { epoch: int, -- Integer value from store.finalized_checkpoint.epoch root: string, -- Encoded 32-byte value from store.finalized_checkpoint.root } -best_justified_checkpoint: { - epoch: int, -- Integer value from store.best_justified_checkpoint.epoch - root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root +proposer_boost_root: string -- Encoded 32-byte value from store.proposer_boost_root +viable_for_head_roots_and_weights: [{ + root: string, -- Encoded 32-byte value of filtered_block_tree leaf blocks + weight: int -- Integer value from get_weight(store, viable_block_root) +}] +``` + +Additionally, these fields if `get_proposer_head` and +`should_override_forkchoice_update` features are implemented: + +```yaml +get_proposer_head: string -- Encoded 32-byte value from get_proposer_head(store) +should_override_forkchoice_update: { -- [New in Bellatrix] + validator_is_connected: bool, -- The mocking result of `validator_is_connected(proposer_index)` in this call + result: bool, -- The result of `should_override_forkchoice_update(store, head_root)`, where head_root is the result value from get_head(store) } ``` For example: + ```yaml - checks: time: 192 head: {slot: 32, root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'} justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'} finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'} - best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'} + proposer_boost_root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb' + get_proposer_head: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb' + should_override_forkchoice_update: {validator_is_connected: false, result: false} + viable_for_head_roots_and_weights: [ + {root: '0x533290b6f44d31c925acd08dfc8448624979d48c40b877d4e6714648866c9ddb', weight: 192000000000}, + {root: '0x5cfb9d9099cdf1d8ab68ce96cdae9f0fa6eef16914a01070580dfdc1d2d59ec3', weight: 544000000000} + ] ``` -*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store. +*Note*: Each `checks` step may include one or multiple items. Each item has to +be checked against the current store. ### `attestation_<32-byte-root>.ssz_snappy` @@ -126,8 +259,15 @@ Each file is an SSZ-snappy encoded `SignedBeaconBlock`. ## Condition -1. Deserialize `anchor_state.ssz_snappy` and `anchor_block.ssz_snappy` to initialize the local store object by with `get_forkchoice_store(anchor_state, anchor_block)` helper. +1. Deserialize `anchor_state.ssz_snappy` and `anchor_block.ssz_snappy` to + initialize the local store object by with + `get_forkchoice_store(anchor_state, anchor_block)` helper. 2. Iterate sequentially through `steps.yaml` - - For each execution, look up the corresponding ssz_snappy file. Execute the corresponding helper function on the current store. - - For the `on_block` execution step: if `len(block.message.body.attestations) > 0`, execute each attestation with `on_attestation(store, attestation)` after executing `on_block(store, block)`. - - For each `checks` step, the assertions on the current store must be satisfied. + - For each execution, look up the corresponding ssz_snappy file. Execute the + corresponding helper function on the current store. + - For the `on_block` execution step: if + `len(block.message.body.attestations) > 0`, execute each attestation with + `on_attestation(store, attestation)` after executing + `on_block(store, block)`. + - For each `checks` step, the assertions on the current store must be + satisfied. diff --git a/tests/formats/forks/README.md b/tests/formats/forks/README.md index 1d3b18d0d8..915c134c4d 100644 --- a/tests/formats/forks/README.md +++ b/tests/formats/forks/README.md @@ -1,10 +1,12 @@ # Forks The aim of the fork tests is to ensure that a pre-fork state can be transformed - into a valid post-fork state, utilizing the `upgrade` function found in the relevant `fork.md` spec. +into a valid post-fork state, utilizing the `upgrade` function found in the +relevant `fork.md` spec. -There is only one handler: `fork`. Each fork (after genesis) is handled with the same format, - and the particular fork boundary being tested is noted in `meta.yaml`. +There is only one handler: `fork`. Each fork (after genesis) is handled with the +same format, and the particular fork boundary being tested is noted in +`meta.yaml`. ## Test case format @@ -20,24 +22,28 @@ fork: str -- Fork being transitioned to Key of valid `fork` strings that might be found in `meta.yaml` -| String ID | Pre-fork | Post-fork | Function | -| - | - | - | - | -| `altair` | Phase 0 | Altair | `upgrade_to_altair` | -| `merge` | Phase 0 | Merge | `upgrade_to_merge` | +| String ID | Pre-fork | Post-fork | Function | +| ----------- | -------- | --------- | ---------------------- | +| `altair` | Phase 0 | Altair | `upgrade_to_altair` | +| `bellatrix` | Altair | Bellatrix | `upgrade_to_bellatrix` | ### `pre.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state before running the fork transition. +An SSZ-snappy encoded `BeaconState`, the state before running the fork +transition. ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the fork transition. +An SSZ-snappy encoded `BeaconState`, the state after applying the fork +transition. -*Note*: This type is the `BeaconState` after the fork and is *not* the same type as `pre`. +*Note*: This type is the `BeaconState` after the fork and is *not* the same type +as `pre`. ## Processing -To process this test, pass `pre` into the upgrade function defined by the `fork` in `meta.yaml`. +To process this test, pass `pre` into the upgrade function defined by the `fork` +in `meta.yaml`. ## Condition diff --git a/tests/formats/genesis/README.md b/tests/formats/genesis/README.md index 25761e2f6a..64aa772109 100644 --- a/tests/formats/genesis/README.md +++ b/tests/formats/genesis/README.md @@ -1,8 +1,11 @@ # Genesis tests -The aim of the genesis tests is to provide a baseline to test genesis-state initialization and test - if the proposed genesis-validity conditions are working. +The aim of the genesis tests is to provide a baseline to test genesis-state +initialization and test if the proposed genesis-validity conditions are working. There are two handlers, documented individually: -- [`validity`](./validity.md): Tests if a genesis state is valid, i.e. if it counts as trigger to launch. -- [`initialization`](./initialization.md): Tests the initialization of a genesis state based on Eth1 data. + +- [`validity`](./validity.md): Tests if a genesis state is valid, i.e. if it + counts as trigger to launch. +- [`initialization`](./initialization.md): Tests the initialization of a genesis + state based on Eth1 data. diff --git a/tests/formats/genesis/initialization.md b/tests/formats/genesis/initialization.md index 73630de51c..18ca07b5f1 100644 --- a/tests/formats/genesis/initialization.md +++ b/tests/formats/genesis/initialization.md @@ -11,31 +11,38 @@ eth1_block_hash: Bytes32 -- A `Bytes32` hex encoded, with prefix 0x. The root o eth1_timestamp: int -- An integer. The timestamp of the block, in seconds. ``` - ### `meta.yaml` A yaml file to help read the deposit count: ```yaml -description: string -- Optional. Description of test case, purely for debugging purposes. -deposits_count: int -- Amount of deposits. +description: string -- Optional. Description of test case, purely for debugging purposes. +deposits_count: int -- Amount of deposits. +execution_payload_header: bool -- `execution_payload_header` field is filled or not. If `true`, `execution_payload_header.ssz_snappy` file exists. ``` ### `deposits_.ssz_snappy` -A series of files, with `` in range `[0, deposits_count)`. Deposits need to be processed in order. -Each file is a SSZ-snappy encoded `Deposit` object. +A series of files, with `` in range `[0, deposits_count)`. Deposits need +to be processed in order. Each file is a SSZ-snappy encoded `Deposit` object. -### `state.ssz_snappy` +### `execution_payload_header.ssz_snappy` -The expected genesis state. An SSZ-snappy encoded `BeaconState` object. +*Note*: Param added only for Bellatrix and subsequent forks. + +The execution payload header that state is initialized with. An SSZ-snappy +encoded `BeaconState` object. +### `state.ssz_snappy` + +The expected genesis state. An SSZ-snappy encoded `BeaconState` object. ## Processing -To process this test, build a genesis state with the provided `eth1_block_hash`, `eth1_timestamp` and `deposits`: +To process this test, build a genesis state with the provided `eth1_block_hash`, +`eth1_timestamp` and `deposits`: `initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)`, - as described in the Beacon Chain specification. +as described in the Beacon Chain specification. ## Condition diff --git a/tests/formats/genesis/validity.md b/tests/formats/genesis/validity.md index 15236c3ba3..6c8d7b3655 100644 --- a/tests/formats/genesis/validity.md +++ b/tests/formats/genesis/validity.md @@ -16,17 +16,16 @@ description: string -- Optional. Description of test case, purely for debuggi An SSZ-snappy encoded `BeaconState`, the state to validate as genesis candidate. - ### `is_valid.yaml` -A boolean, true if the genesis state is deemed valid as to launch with, false otherwise. - +A boolean, true if the genesis state is deemed valid as to launch with, false +otherwise. ## Processing To process the data, call `is_valid_genesis_state(genesis)`. - ## Condition -The result of calling `is_valid_genesis_state(genesis)` should match the expected `is_valid` boolean. +The result of calling `is_valid_genesis_state(genesis)` should match the +expected `is_valid` boolean. diff --git a/tests/formats/kzg_4844/README.md b/tests/formats/kzg_4844/README.md new file mode 100644 index 0000000000..c4888a9d76 --- /dev/null +++ b/tests/formats/kzg_4844/README.md @@ -0,0 +1,16 @@ +# KZG tests + +A test type for KZG libraries. Tests all the public interfaces that a KZG +library required to implement EIP-4844 needs to provide, as defined in +`polynomial-commitments.md`. + +We do not recommend rolling your own crypto or using an untested KZG library. + +The KZG test suite runner has the following handlers: + +- [`blob_to_kzg_commitment`](./blob_to_kzg_commitment.md) +- [`compute_kzg_proof`](./compute_kzg_proof.md) +- [`verify_kzg_proof`](./verify_kzg_proof.md) +- [`compute_blob_kzg_proof`](./compute_blob_kzg_proof.md) +- [`verify_blob_kzg_proof`](./verify_blob_kzg_proof.md) +- [`verify_blob_kzg_proof_batch`](./verify_blob_kzg_proof_batch.md) diff --git a/tests/formats/kzg_4844/blob_to_kzg_commitment.md b/tests/formats/kzg_4844/blob_to_kzg_commitment.md new file mode 100644 index 0000000000..afb5fe5b22 --- /dev/null +++ b/tests/formats/kzg_4844/blob_to_kzg_commitment.md @@ -0,0 +1,26 @@ +# Test format: Blob to KZG commitment + +Compute the KZG commitment for a given `blob`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob +output: KZGCommitment -- The KZG commitment +``` + +- `blob` here is encoded as a string: hexadecimal encoding of + `4096 * 32 = 131072` bytes, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `blob_to_kzg_commitment` handler should compute the KZG commitment for +`blob`, and the result should match the expected `output`. If the blob is +invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a +BLS field element) it should error, i.e. the output should be `null`. diff --git a/tests/formats/kzg_4844/compute_blob_kzg_proof.md b/tests/formats/kzg_4844/compute_blob_kzg_proof.md new file mode 100644 index 0000000000..b3d5761d95 --- /dev/null +++ b/tests/formats/kzg_4844/compute_blob_kzg_proof.md @@ -0,0 +1,30 @@ +# Test format: Compute blob KZG proof + +Compute the blob KZG proof for a given `blob`, that helps with quickly verifying +that the KZG commitment for the blob is correct. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob + commitment: Bytes48 -- the commitment to the blob +output: KZGProof -- The blob KZG proof +``` + +- `blob` here is encoded as a string: hexadecimal encoding of + `4096 * 32 = 131072` bytes, prefixed with `0x`. +- `commitment` here is encoded as a string: hexadecimal encoding of `48` bytes, + prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `compute_blob_kzg_proof` handler should compute the blob KZG proof for +`blob`, and the result should match the expected `output`. If the blob is +invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a +BLS field element) it should error, i.e. the output should be `null`. diff --git a/tests/formats/kzg_4844/compute_kzg_proof.md b/tests/formats/kzg_4844/compute_kzg_proof.md new file mode 100644 index 0000000000..f252e9ce07 --- /dev/null +++ b/tests/formats/kzg_4844/compute_kzg_proof.md @@ -0,0 +1,33 @@ +# Test format: Compute KZG proof + +Compute the KZG proof for a given `blob` and an evaluation point `z`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob representing a polynomial + z: Bytes32 -- bytes encoding the BLS field element at which the polynomial should be evaluated +output: Tuple[KZGProof, Bytes32] -- The KZG proof and the value y = f(z) +``` + +- `blob` here is encoded as a string: hexadecimal encoding of + `4096 * 32 = 131072` bytes, prefixed with `0x`. +- `z` here is encoded as a string: hexadecimal encoding of `32` bytes + representing a big endian encoded field element, prefixed with `0x`. +- `y` here is encoded as a string: hexadecimal encoding of `32` bytes + representing a big endian encoded field element, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `compute_kzg_proof` handler should compute the KZG proof as well as the +value `y` for evaluating the polynomial represented by `blob` at `z`, and the +result should match the expected `output`. If the blob is invalid (e.g. +incorrect length or one of the 32-byte blocks does not represent a BLS field +element) or `z` is not a valid BLS field element, it should error, i.e. the +output should be `null`. diff --git a/tests/formats/kzg_4844/verify_blob_kzg_proof.md b/tests/formats/kzg_4844/verify_blob_kzg_proof.md new file mode 100644 index 0000000000..a3ec1e28b3 --- /dev/null +++ b/tests/formats/kzg_4844/verify_blob_kzg_proof.md @@ -0,0 +1,31 @@ +# Test format: Verify blob KZG proof + +Use the blob KZG proof to verify that the KZG commitment for a given `blob` is +correct + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob + commitment: KZGCommitment -- the KZG commitment to the data blob + proof: KZGProof -- The KZG proof +output: bool -- true (valid proof) or false (incorrect proof) +``` + +- `blob` here is encoded as a string: hexadecimal encoding of + `4096 * 32 = 131072` bytes, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `verify_blob_kzg_proof` handler should verify that `commitment` is a correct +KZG commitment to `blob` by using the blob KZG proof `proof`, and the result +should match the expected `output`. If the commitment or proof is invalid (e.g. +not on the curve or not in the G1 subgroup of the BLS curve) or `blob` is +invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a +BLS field element), it should error, i.e. the output should be `null`. diff --git a/tests/formats/kzg_4844/verify_blob_kzg_proof_batch.md b/tests/formats/kzg_4844/verify_blob_kzg_proof_batch.md new file mode 100644 index 0000000000..2bc1efd816 --- /dev/null +++ b/tests/formats/kzg_4844/verify_blob_kzg_proof_batch.md @@ -0,0 +1,32 @@ +# Test format: Verify blob KZG proof batch + +Use the blob KZG proofs to verify that the KZG commitments for given `blobs` are +correct + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blobs: List[Blob] -- the data blob + commitments: List[KZGCommitment] -- the KZG commitment to the data blob + proofs: List[KZGProof] -- The KZG proof +output: bool -- true (all proofs are valid) or false (some proofs incorrect) +``` + +- `blobs` here are encoded as a string: hexadecimal encoding of + `4096 * 32 = 131072` bytes, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `verify_blob_kzg_proof_batch` handler should verify that `commitments` are +correct KZG commitments to `blobs` by using the blob KZG proofs `proofs`, and +the result should match the expected `output`. If any of the commitments or +proofs are invalid (e.g. not on the curve or not in the G1 subgroup of the BLS +curve) or any blob is invalid (e.g. incorrect length or one of the 32-byte +blocks does not represent a BLS field element), it should error, i.e. the output +should be `null`. diff --git a/tests/formats/kzg_4844/verify_kzg_proof.md b/tests/formats/kzg_4844/verify_kzg_proof.md new file mode 100644 index 0000000000..552df66973 --- /dev/null +++ b/tests/formats/kzg_4844/verify_kzg_proof.md @@ -0,0 +1,34 @@ +# Test format: Verify KZG proof + +Verify the KZG proof for a given `blob` and an evaluation point `z` that claims +to result in a value of `y`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + commitment: KZGCommitment -- the KZG commitment to the data blob + z: Bytes32 -- bytes encoding the BLS field element at which the polynomial should be evaluated + y: Bytes32 -- the claimed result of the evaluation + proof: KZGProof -- The KZG proof +output: bool -- true (valid proof) or false (incorrect proof) +``` + +- `z` here is encoded as a string: hexadecimal encoding of `32` bytes + representing a big endian encoded field element, prefixed with `0x`. +- `y` here is encoded as a string: hexadecimal encoding of `32` bytes + representing a big endian encoded field element, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `verify_kzg_proof` handler should verify the KZG proof for evaluating the +polynomial represented by `blob` at `z` resulting in the value `y`, and the +result should match the expected `output`. If the commitment or proof is invalid +(e.g. not on the curve or not in the G1 subgroup of the BLS curve) or `z` or `y` +are not a valid BLS field element, it should error, i.e. the output should be +`null`. diff --git a/tests/formats/kzg_7594/README.md b/tests/formats/kzg_7594/README.md new file mode 100644 index 0000000000..9fb4ed755d --- /dev/null +++ b/tests/formats/kzg_7594/README.md @@ -0,0 +1,13 @@ +# KZG tests for EIP-7594 + +A test type for KZG libraries. Tests all the public interfaces that a KZG +library is required to implement for EIP-7594, as defined in +`polynomial-commitments-sampling.md`. + +We do not recommend rolling your own crypto or using an untested KZG library. + +The KZG test suite runner has the following handlers: + +- [`compute_cells_and_kzg_proofs`](./compute_cells_and_kzg_proofs.md) +- [`recover_cells_and_kzg_proofs`](./recover_cells_and_kzg_proofs.md) +- [`verify_cell_kzg_proof_batch`](./verify_cell_kzg_proof_batch.md) diff --git a/tests/formats/kzg_7594/compute_cells.md b/tests/formats/kzg_7594/compute_cells.md new file mode 100644 index 0000000000..9845a339b5 --- /dev/null +++ b/tests/formats/kzg_7594/compute_cells.md @@ -0,0 +1,27 @@ +# Test format: Compute cells + +Compute the cells for a given `blob`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob +output: List[Cell] -- the cells +``` + +- `Blob` is a 131072-byte hexadecimal string, prefixed with `0x`. +- `Cell` is a 2048-byte hexadecimal string, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `compute_cells` handler should compute the cells (chunks of an extended +blob) for `blob`, and the result should match the expected `output`. If the blob +is invalid (e.g. incorrect length or one of the 32-byte blocks does not +represent a BLS field element) it should error, i.e. the output should be +`null`. diff --git a/tests/formats/kzg_7594/compute_cells_and_kzg_proofs.md b/tests/formats/kzg_7594/compute_cells_and_kzg_proofs.md new file mode 100644 index 0000000000..f38f49cc18 --- /dev/null +++ b/tests/formats/kzg_7594/compute_cells_and_kzg_proofs.md @@ -0,0 +1,28 @@ +# Test format: Compute cells and KZG proofs + +Compute the cells and cell KZG proofs for a given `blob`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + blob: Blob -- the data blob +output: Tuple[List[Cell], List[KZGProof]] -- the cells and proofs +``` + +- `Blob` is a 131072-byte hexadecimal string, prefixed with `0x`. +- `Cell` is a 2048-byte hexadecimal string, prefixed with `0x`. +- `KZGProof` is a 48-byte hexadecimal string, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `compute_cells_and_kzg_proofs` handler should compute the cells (chunks of +an extended blob) and cell KZG proofs for `blob`, and the result should match +the expected `output`. If the blob is invalid (e.g. incorrect length or one of +the 32-byte blocks does not represent a BLS field element) it should error, i.e. +the output should be `null`. diff --git a/tests/formats/kzg_7594/recover_cells_and_kzg_proofs.md b/tests/formats/kzg_7594/recover_cells_and_kzg_proofs.md new file mode 100644 index 0000000000..71d5eac5ba --- /dev/null +++ b/tests/formats/kzg_7594/recover_cells_and_kzg_proofs.md @@ -0,0 +1,30 @@ +# Test format: Recover cells and KZG proofs + +Recover all cells/proofs given at least 50% of the original `cells`. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + cell_indices: List[CellIndex] -- the cell indices + cells: List[Cell] -- the partial collection of cells +output: Tuple[List[Cell], List[KZGProof]] -- all cells and proofs +``` + +- `CellIndex` is an unsigned 64-bit integer. +- `Cell` is a 2048-byte hexadecimal string, prefixed with `0x`. +- `KZGProof` is a 48-byte hexadecimal string, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `recover_cells_and_kzg_proofs` handler should recover missing cells and +proofs, and the result should match the expected `output`. If any cell is +invalid (e.g. incorrect length or one of the 32-byte blocks does not represent a +BLS field element), or any `cell_index` is invalid (e.g. greater than the number +of cells for an extended blob), it should error, i.e. the output should be +`null`. diff --git a/tests/formats/kzg_7594/verify_cell_kzg_proof_batch.md b/tests/formats/kzg_7594/verify_cell_kzg_proof_batch.md new file mode 100644 index 0000000000..a16e8a983e --- /dev/null +++ b/tests/formats/kzg_7594/verify_cell_kzg_proof_batch.md @@ -0,0 +1,35 @@ +# Test format: Verify cell KZG proof batch + +Use the cell KZG `proofs` to verify that the KZG `commitments` for the given +`cells` are correct. + +## Test case format + +The test data is declared in a `data.yaml` file: + +```yaml +input: + commitments: List[Bytes48] -- the KZG commitments for each cell + cell_indices: List[CellIndex] -- the cell index for each cell + cells: List[Cell] -- the cells + proofs: List[Bytes48] -- the KZG proof for each cell +output: bool -- true (all proofs are correct) or false (some proofs incorrect) +``` + +- `Bytes48` is a 48-byte hexadecimal string, prefixed with `0x`. +- `CellIndex` is an unsigned 64-bit integer. +- `Cell` is a 2048-byte hexadecimal string, prefixed with `0x`. + +All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with +`0x`. + +## Condition + +The `verify_cell_kzg_proof_batch` handler should verify that `commitments` are +correct KZG commitments to `cells` by using the cell KZG proofs `proofs`, and +the result should match the expected `output`. If any of the commitments or +proofs are invalid (e.g. not on the curve or not in the G1 subgroup of the BLS +curve), any cell is invalid (e.g. incorrect length or one of the 32-byte blocks +does not represent a BLS field element), or any `cell_index` is invalid (e.g. +greater than the number of cells for an extended blob), it should error, i.e. +the output should be `null`. diff --git a/tests/formats/light_client/README.md b/tests/formats/light_client/README.md new file mode 100644 index 0000000000..d03bd8aa64 --- /dev/null +++ b/tests/formats/light_client/README.md @@ -0,0 +1,14 @@ +# Light client sync protocol tests + +This series of tests provides reference test vectors for the light client sync +protocol spec. + +Handlers: + +- `data_collection`: see + [Light client data collection test format](./data_collection.md) +- `single_merkle_proof`: see + [Single leaf merkle proof test format](./single_merkle_proof.md) +- `sync`: see [Light client sync test format](./sync.md) +- `update_ranking`: see + [`LightClientUpdate` ranking test format](./update_ranking.md) diff --git a/tests/formats/light_client/data_collection.md b/tests/formats/light_client/data_collection.md new file mode 100644 index 0000000000..4b3debf83c --- /dev/null +++ b/tests/formats/light_client/data_collection.md @@ -0,0 +1,86 @@ +# Light client data collection tests + +This series of tests provides reference test vectors for validating that a full +node collects canonical data for serving to light clients implementing the light +client sync protocol to sync to the latest block header. + +## Test case format + +### `initial_state.ssz_snappy` + +An SSZ-snappy encoded object of type `BeaconState` to initialize the blockchain +from. The state's `slot` is epoch aligned. + +### `steps.yaml` + +The steps to execute in sequence. + +#### `new_block` execution step + +The new block described by the test step should be imported, but does not become +head yet. + +```yaml +{ + fork_digest: string -- encoded `ForkDigest`-context of `block` + data: string -- name of the `*.ssz_snappy` file to load + as a `SignedBeaconBlock` object +} +``` + +#### `new_head` execution step + +The given block (previously imported) should become head, leading to potential +updates to: + +- The best `LightClientUpdate` for non-finalized sync committee periods. +- The latest `LightClientFinalityUpdate` and `LightClientOptimisticUpdate`. +- The latest finalized `Checkpoint` (across all branches). +- The available `LightClientBootstrap` instances for newly finalized + `Checkpoint`s. + +```yaml +{ + head_block_root: Bytes32 -- string, hex encoded, with 0x prefix + checks: { + latest_finalized_checkpoint: { -- tracked across all branches + epoch: int -- integer, decimal + root: Bytes32 -- string, hex encoded, with 0x prefix + } + bootstraps: [ -- one entry per `LightClientBootstrap` + block_root: Bytes32 -- string, hex encoded, with 0x prefix + bootstrap: { -- only exists if a `LightClientBootstrap` is available + fork_digest: string -- encoded `ForkDigest`-context of `data` + data: string -- name of the `*.ssz_snappy` file to load + as a `LightClientBootstrap` object + } + ] + best_updates: [ -- one entry per sync committee period + period: int -- integer, decimal + update: { -- only exists if a best `LightClientUpdate` is available + fork_digest: string -- encoded `ForkDigest`-context of `data` + data: string -- name of the `*.ssz_snappy` file to load + as a `LightClientUpdate` object + } + ] + latest_finality_update: { -- only exists if a `LightClientFinalityUpdate` is available + fork_digest: string -- encoded `ForkDigest`-context of `data` + data: string -- name of the `*.ssz_snappy` file to load + as a `LightClientFinalityUpdate` object + } + latest_optimistic_update: { -- only exists if a `LightClientOptimisticUpdate` is available + fork_digest: string -- encoded `ForkDigest`-context of `data` + data: string -- name of the `*.ssz_snappy` file to load + as a `LightClientOptimisticUpdate` object + } + } +} +``` + +## Condition + +A test-runner should initialize a simplified blockchain from `initial_state`. An +external signal is used to control fork choice. The test-runner should then +proceed to execute all the test steps in sequence, collecting light client data +during execution. After each `new_head` step, it should verify that the +collected light client data matches the provided `checks`. diff --git a/tests/formats/light_client/single_merkle_proof.md b/tests/formats/light_client/single_merkle_proof.md new file mode 100644 index 0000000000..582908976d --- /dev/null +++ b/tests/formats/light_client/single_merkle_proof.md @@ -0,0 +1,34 @@ +# Single leaf merkle proof tests + +This series of tests provides reference test vectors for validating correct +generation and verification of merkle proofs based on static data. + +## Test case format + +Tests for each individual SSZ type are grouped into a `suite` indicating the SSZ +type name. + +### `object.ssz_snappy` + +A SSZ-snappy encoded object from which other data is generated. The SSZ type can +be determined from the test `suite` name. + +### `proof.yaml` + +A proof of the leaf value (a merkle root) at generalized-index `leaf_index` in +the given `object`. + +```yaml +leaf: Bytes32 # string, hex encoded, with 0x prefix +leaf_index: int # integer, decimal +branch: list of Bytes32 # list, each element is a string, hex encoded, with 0x prefix +``` + +## Condition + +A test-runner can implement the following assertions: + +- Check that `is_valid_merkle_branch` confirms `leaf` at `leaf_index` to verify + against `hash_tree_root(object)` and `branch`. +- If the implementation supports generating merkle proofs, check that the + self-generated proof matches the `branch` provided with the test. diff --git a/tests/formats/light_client/sync.md b/tests/formats/light_client/sync.md new file mode 100644 index 0000000000..09f1263953 --- /dev/null +++ b/tests/formats/light_client/sync.md @@ -0,0 +1,102 @@ +# Light client sync tests + +This series of tests provides reference test vectors for validating that a light +client implementing the sync protocol can sync to the latest block header. + +## Test case format + +### `meta.yaml` + +```yaml +genesis_validators_root: Bytes32 -- string, hex encoded, with 0x prefix +trusted_block_root: Bytes32 -- string, hex encoded, with 0x prefix +bootstrap_fork_digest: string -- encoded `ForkDigest`-context of `bootstrap` +store_fork_digest: string -- encoded `ForkDigest`-context of `store` object being tested +``` + +### `bootstrap.ssz_snappy` + +An SSZ-snappy encoded `bootstrap` object of type `LightClientBootstrap` to +initialize a local `store` object of type `LightClientStore` with +`store_fork_digest` using +`initialize_light_client_store(trusted_block_rooot, bootstrap)`. The SSZ type +can be determined from `bootstrap_fork_digest`. + +If `store_fork_digest` differs from `bootstrap_fork_digest`, the `bootstrap` +object may need to be upgraded before initializing the store. + +### `steps.yaml` + +The steps to execute in sequence. + +#### Checks to run after each step + +Each step includes checks to verify the expected impact on the `store` object. + +```yaml +finalized_header: { + slot: int, -- Integer value from store.finalized_header.beacon.slot + beacon_root: string, -- Encoded 32-byte value from store.finalized_header.beacon.hash_tree_root() + execution_root: string, -- From Capella onward; get_lc_execution_root(store.finalized_header) +} +optimistic_header: { + slot: int, -- Integer value from store.optimistic_header.beacon.slot + beacon_root: string, -- Encoded 32-byte value from store.optimistic_header.beacon.hash_tree_root() + execution_root: string, -- From Capella onward; get_lc_execution_root(store.optimistic_header) +} +``` + +#### `force_update` execution step + +The function `process_light_client_store_force_update(store, current_slot)` +should be executed with the specified parameters: + +```yaml +{ + current_slot: int -- integer, decimal + checks: {: value} -- the assertions. +} +``` + +After this step, the `store` object may have been updated. + +#### `process_update` execution step + +The function +`process_light_client_update(store, update, current_slot, genesis_validators_root)` +should be executed with the specified parameters: + +```yaml +{ + update_fork_digest: string -- encoded `ForkDigest`-context of `update` + update: string -- name of the `*.ssz_snappy` file to load + as a `LightClientUpdate` object + current_slot: int -- integer, decimal + checks: {: value} -- the assertions. +} +``` + +If `store_fork_digest` differs from `update_fork_digest`, the `update` object +may need to be upgraded before processing the update. + +After this step, the `store` object may have been updated. + +#### `upgrade_store` + +The `store` should be upgraded to reflect the new `store_fork_digest`: + +```yaml +{ + store_fork_digest: string -- encoded `ForkDigest`-context of `store` + checks: {: value} -- the assertions. +} +``` + +After this step, the `store` object may have been updated. + +## Condition + +A test-runner should initialize a local `LightClientStore` using the provided +`bootstrap` object. It should then proceed to execute all the test steps in +sequence. After each step, it should verify that the resulting `store` verifies +against the provided `checks`. diff --git a/tests/formats/light_client/update_ranking.md b/tests/formats/light_client/update_ranking.md new file mode 100644 index 0000000000..4abbc102f2 --- /dev/null +++ b/tests/formats/light_client/update_ranking.md @@ -0,0 +1,26 @@ +# `LightClientUpdate` ranking tests + +This series of tests provides reference test vectors for validating that +`LightClientUpdate` instances are ranked in a canonical order. + +## Test case format + +### `meta.yaml` + +```yaml +updates_count: int -- integer, decimal +``` + +### `updates_.ssz_snappy` + +A series of files, with `` in range `[0, updates_count)`, ordered by +descending precedence according to `is_better_update` (best update at index 0). + +Each file is a SSZ-snappy encoded `LightClientUpdate`. + +## Condition + +A test-runner should load the provided `update` objects and verify that the +local implementation ranks them in the same order. Note that the `update` +objects are not restricted to a single sync committee period for the scope of +this test. diff --git a/tests/formats/merkle_proof/README.md b/tests/formats/merkle_proof/README.md new file mode 100644 index 0000000000..5400a1a3bd --- /dev/null +++ b/tests/formats/merkle_proof/README.md @@ -0,0 +1,6 @@ +# Merkle proof tests + +Handlers: + +- `single_merkle_proof`: see + [Single leaf merkle proof test format](../light_client/single_merkle_proof.md) diff --git a/tests/formats/networking/README.md b/tests/formats/networking/README.md new file mode 100644 index 0000000000..deae363dea --- /dev/null +++ b/tests/formats/networking/README.md @@ -0,0 +1,11 @@ +# Networking tests + +The aim of the networking tests is to set a base-line on what really needs to +pass, i.e. the essentials. + +Handlers: + +- [`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..5a61f0cad5 --- /dev/null +++ b/tests/formats/networking/compute_columns_for_custody_group.md @@ -0,0 +1,14 @@ +# `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_groups.md b/tests/formats/networking/get_custody_groups.md new file mode 100644 index 0000000000..d28527c4af --- /dev/null +++ b/tests/formats/networking/get_custody_groups.md @@ -0,0 +1,15 @@ +# `get_custody_groups` tests + +`get_custody_groups` tests provide sanity checks for the correctness of the +`get_custody_groups` helper function. + +## Test case format + +### `meta.yaml` + +```yaml +description: string -- optional: description of test case, purely for debugging purposes. +node_id: int -- argument: the NodeID input. +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/operations/README.md b/tests/formats/operations/README.md index c69d798d77..371891e9a0 100644 --- a/tests/formats/operations/README.md +++ b/tests/formats/operations/README.md @@ -1,6 +1,7 @@ # Operations tests -The different kinds of operations ("transactions") are tested individually with test handlers. +The different kinds of operations ("transactions") are tested individually with +test handlers. ## Test case format @@ -22,34 +23,41 @@ An SSZ-snappy encoded operation object, e.g. a `ProposerSlashing`, or `Deposit`. ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the operation. No value if operation processing is aborted. - +An SSZ-snappy encoded `BeaconState`, the state after applying the operation. No +value if operation processing is aborted. ## Condition -A handler of the `operations` test-runner should process these cases, - calling the corresponding processing implementation. -This excludes the other parts of the block-transition. +A handler of the `operations` test-runner should process these cases, calling +the corresponding processing implementation. This excludes the other parts of +the block-transition. Operations: -| *`operation-name`* | *`operation-object`* | *`input name`* | *`processing call`* | -|-------------------------|-----------------------|----------------------|----------------------------------------------------------------------| -| `attestation` | `Attestation` | `attestation` | `process_attestation(state, attestation)` | -| `attester_slashing` | `AttesterSlashing` | `attester_slashing` | `process_attester_slashing(state, attester_slashing)` | -| `block_header` | `BeaconBlock` | **`block`** | `process_block_header(state, block)` | -| `deposit` | `Deposit` | `deposit` | `process_deposit(state, deposit)` | -| `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` | -| `voluntary_exit` | `SignedVoluntaryExit` | `voluntary_exit` | `process_voluntary_exit(state, voluntary_exit)` | -| `sync_aggregate` | `SyncAggregate` | `sync_aggregate` | `process_sync_aggregate(state, sync_aggregate)` (new in Altair) | -| `execution_payload` | `ExecutionPayload` | `execution_payload` | `process_execution_payload(state, execution_payload)` (new in Merge) | - -Note that `block_header` is not strictly an operation (and is a full `Block`), but processed in the same manner, and hence included here. - -The `execution_payload` processing normally requires a `verify_execution_state_transition(execution_payload)`, -a responsibility of an (external) execution engine. -During testing this execution is mocked, an `execution.yml` is provided instead: -a dict containing an `execution_valid` boolean field with the verification result. - -The resulting state should match the expected `post` state, or if the `post` state is left blank, - the handler should reject the input operation as invalid. +| *`operation-name`* | *`operation-object`* | *`input name`* | *`processing call`* | +| ------------------------- | ---------------------------- | ----------------------- | ------------------------------------------------------------------------------ | +| `attestation` | `Attestation` | `attestation` | `process_attestation(state, attestation)` | +| `attester_slashing` | `AttesterSlashing` | `attester_slashing` | `process_attester_slashing(state, attester_slashing)` | +| `block_header` | `BeaconBlock` | **`block`** | `process_block_header(state, block)` | +| `deposit` | `Deposit` | `deposit` | `process_deposit(state, deposit)` | +| `proposer_slashing` | `ProposerSlashing` | `proposer_slashing` | `process_proposer_slashing(state, proposer_slashing)` | +| `voluntary_exit` | `SignedVoluntaryExit` | `voluntary_exit` | `process_voluntary_exit(state, voluntary_exit)` | +| `sync_aggregate` | `SyncAggregate` | `sync_aggregate` | `process_sync_aggregate(state, sync_aggregate)` (new in Altair) | +| `execution_payload` | `BeaconBlockBody` | **`body`** | `process_execution_payload(state, body)` (new in Bellatrix) | +| `withdrawals` | `ExecutionPayload` | `execution_payload` | `process_withdrawals(state, execution_payload)` (new in Capella) | +| `bls_to_execution_change` | `SignedBLSToExecutionChange` | `address_change` | `process_bls_to_execution_change(state, address_change)` (new in Capella) | +| `deposit_request` | `DepositRequest` | `deposit_request` | `process_deposit_request(state, deposit_request)` (new in Electra) | +| `withdrawal_request` | `WithdrawalRequest` | `withdrawal_request` | `process_withdrawal_request(state, withdrawal_request)` (new in Electra) | +| `consolidation_request` | `ConsolidationRequest` | `consolidation_request` | `process_consolidation_request(state, consolidation_request)` (new in Electra) | + +Note that `block_header` is not strictly an operation (and is a full `Block`), +but processed in the same manner, and hence included here. + +The `execution_payload` processing normally requires a +`verify_execution_state_transition(execution_payload)`, a responsibility of an +(external) execution engine. During testing this execution is mocked, an +`execution.yml` is provided instead: a dict containing an `execution_valid` +boolean field with the verification result. + +The resulting state should match the expected `post` state, or if the `post` +state is left blank, the handler should reject the input operation as invalid. diff --git a/tests/formats/random/README.md b/tests/formats/random/README.md new file mode 100644 index 0000000000..7ec26b9d92 --- /dev/null +++ b/tests/formats/random/README.md @@ -0,0 +1,8 @@ +# Random tests + +The random tests are generated with various randomized states and blocks. + +## Test case format + +- `random` handler: same as the [`blocks`](../sanity/blocks.md) handler test + case format from sanity tests. diff --git a/tests/formats/rewards/README.md b/tests/formats/rewards/README.md index a6682042f7..7ad081915c 100644 --- a/tests/formats/rewards/README.md +++ b/tests/formats/rewards/README.md @@ -1,10 +1,11 @@ # Rewards tests -All rewards deltas sub-functions are tested for each test case. -There is no "change" factor, the rewards/penalties outputs are pure functions with just the pre-state as input. -(See test condition documentation on how to run the tests.) +All rewards deltas sub-functions are tested for each test case. There is no +"change" factor, the rewards/penalties outputs are pure functions with just the +pre-state as input. (See test condition documentation on how to run the tests.) `Deltas` is defined as: + ```python class Deltas(Container): rewards: List[Gwei, VALIDATOR_REGISTRY_LIMIT] @@ -21,43 +22,49 @@ description: string -- Optional description of test case, purely for debuggin ``` _Note_: No signature verification happens within rewards sub-functions. These - tests can safely be run with or without BLS enabled. +tests can safely be run with or without BLS enabled. ### `pre.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state before running the rewards sub-function. +An SSZ-snappy encoded `BeaconState`, the state before running the rewards +sub-function. ### `source_deltas.ssz_snappy` -An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned by the rewards the `get_source_deltas` function +An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned +by the rewards the `get_source_deltas` function ### `target_deltas.ssz_snappy` -An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned by the rewards the `get_target_deltas` function +An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned +by the rewards the `get_target_deltas` function ### `head_deltas.ssz_snappy` -An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned by the rewards the `get_head_deltas` function +An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned +by the rewards the `get_head_deltas` function ### `inclusion_delay_deltas.ssz_snappy` -An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned by the rewards the `get_inclusion_delay_deltas` function +An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned +by the rewards the `get_inclusion_delay_deltas` function ### `inactivity_penalty_deltas.ssz_snappy` -An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned by the rewards the `get_inactivity_penalty_deltas` function +An SSZ-snappy encoded `Deltas` representing the rewards and penalties returned +by the rewards the `get_inactivity_penalty_deltas` function ## Condition -A handler of the `rewards` test-runner should process these cases, - calling the corresponding rewards deltas function for each set of deltas. +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. -The provided `deltas` should match the return values of the - deltas function. Specifically the following must hold true for each set of deltas: +The provided `deltas` should match the return values of the deltas function. +Specifically the following must hold true for each set of deltas: ```python - deltas.rewards == deltas_function(state)[0] - deltas.penalties == deltas_function(state)[1] +deltas.rewards == deltas_function(state)[0] +deltas.penalties == deltas_function(state)[1] ``` diff --git a/tests/formats/sanity/README.md b/tests/formats/sanity/README.md index 20b36208a4..2b35dfc470 100644 --- a/tests/formats/sanity/README.md +++ b/tests/formats/sanity/README.md @@ -1,7 +1,10 @@ # Sanity tests -The aim of the sanity tests is to set a base-line on what really needs to pass, i.e. the essentials. +The aim of the sanity tests is to set a base-line on what really needs to pass, +i.e. the essentials. There are two handlers, documented individually: -- [`slots`](./slots.md): transitions of one or more slots (and epoch transitions within) + +- [`slots`](./slots.md): transitions of one or more slots (and epoch transitions + within) - [`blocks`](./blocks.md): transitions triggered by one or more blocks diff --git a/tests/formats/sanity/blocks.md b/tests/formats/sanity/blocks.md index 7ea646b9e0..ec656618b5 100644 --- a/tests/formats/sanity/blocks.md +++ b/tests/formats/sanity/blocks.md @@ -1,6 +1,7 @@ # Sanity blocks testing -Sanity tests to cover a series of one or more blocks being processed, aiming to cover common changes. +Sanity tests to cover a series of one or more blocks being processed, aiming to +cover common changes. ## Test case format @@ -13,25 +14,25 @@ reveal_deadlines_setting: int -- see general test-format spec. blocks_count: int -- the number of blocks processed in this test. ``` - ### `pre.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state before running the block transitions. - +An SSZ-snappy encoded `BeaconState`, the state before running the block +transitions. ### `blocks_.ssz_snappy` -A series of files, with `` in range `[0, blocks_count)`. Blocks need to be processed in order, - following the main transition function (i.e. process slot and epoch transitions in between blocks as normal) +A series of files, with `` in range `[0, blocks_count)`. Blocks need to +be processed in order, following the main transition function (i.e. process slot +and epoch transitions in between blocks as normal) Each file is a SSZ-snappy encoded `SignedBeaconBlock`. ### `post.ssz_snappy` -An SSZ-snappy encoded `BeaconState`, the state after applying the block transitions. - +An SSZ-snappy encoded `BeaconState`, the state after applying the block +transitions. ## Condition -The resulting state should match the expected `post` state, or if the `post` state is left blank, - the handler should reject the series of blocks as invalid. +The resulting state should match the expected `post` state, or if the `post` +state is left blank, the handler should reject the series of blocks as invalid. diff --git a/tests/formats/sanity/slots.md b/tests/formats/sanity/slots.md index f1b8a13219..31b6b387d5 100644 --- a/tests/formats/sanity/slots.md +++ b/tests/formats/sanity/slots.md @@ -1,6 +1,7 @@ # Sanity slots testing -Sanity tests to cover a series of one or more empty-slot transitions being processed, aiming to cover common changes. +Sanity tests to cover a series of one or more empty-slot transitions being +processed, aiming to cover common changes. ## Test case format @@ -11,17 +12,16 @@ description: string -- Optional. Description of test case, purely for debuggi bls_setting: int -- see general test-format spec. ``` - ### `pre.ssz_snappy` An SSZ-snappy `BeaconState`, the state before running the transitions. Also available as `pre.ssz_snappy`. - ### `slots.yaml` -An integer. The amount of slots to process (i.e. the difference in slots between pre and post), always a positive number. +An integer. The amount of slots to process (i.e. the difference in slots between +pre and post), always a positive number. ### `post.ssz_snappy` @@ -29,11 +29,11 @@ An SSZ-snappy `BeaconState`, the state after applying the transitions. Also available as `post.ssz_snappy`. - ### Processing -The transition with pure time, no blocks, is known as `process_slots(state, slot)` in the spec. -This runs state-caching (pure slot transition) and epoch processing (every E slots). +The transition with pure time, no blocks, is known as +`process_slots(state, slot)` in the spec. This runs state-caching (pure slot +transition) and epoch processing (every E slots). To process the data, call `process_slots(pre, pre.slot + N)`. diff --git a/tests/formats/shuffling/README.md b/tests/formats/shuffling/README.md index 15bfe6996b..1fe2db8aeb 100644 --- a/tests/formats/shuffling/README.md +++ b/tests/formats/shuffling/README.md @@ -2,15 +2,19 @@ The runner of the Shuffling test type has only one handler: `core`. -However, this does not mean that testing is limited. -Clients may take different approaches to shuffling, for optimizing, - and supporting advanced lookup behavior back in older history. +However, this does not mean that testing is limited. Clients may take different +approaches to shuffling, for optimizing, and supporting advanced lookup behavior +back in older history. For implementers, possible test runners implementing testing can include: -1) Just test permute-index, run it for each index `i` in `range(count)`, and check against expected `mapping[i]` (default spec implementation). -2) Test un-permute-index (the reverse lookup; implemented by running the shuffling rounds in reverse, from `round_count-1` to `0`). -3) Test the optimized complete shuffle, where all indices are shuffled at once; test output in one go. -4) Test complete shuffle in reverse (reverse rounds, same as #2). + +1. Just test permute-index, run it for each index `i` in `range(count)`, and + check against expected `mapping[i]` (default spec implementation). +2. Test un-permute-index (the reverse lookup; implemented by running the + shuffling rounds in reverse, from `round_count-1` to `0`). +3. Test the optimized complete shuffle, where all indices are shuffled at once; + test output in one go. +4. Test complete shuffle in reverse (reverse rounds, same as #2). ## Test case format @@ -22,17 +26,23 @@ count: int mapping: List[int] ``` -- The `bytes32` is encoded as a string, hexadecimal encoding, prefixed with `0x`. -- Integers are validator indices. These are `uint64`, but realistically they are not as big. +- The `bytes32` is encoded as a string, hexadecimal encoding, prefixed with + `0x`. +- Integers are validator indices. These are `uint64`, but realistically they are + not as big. -The `count` specifies the validator registry size. One should compute the shuffling for indices `0, 1, 2, 3, ..., count (exclusive)`. +The `count` specifies the validator registry size. One should compute the +shuffling for indices `0, 1, 2, 3, ..., count (exclusive)`. -The `seed` is the raw shuffling seed, passed to permute-index (or optimized shuffling approach). +The `seed` is the raw shuffling seed, passed to permute-index (or optimized +shuffling approach). -The `mapping` is a look up array, constructed as `[spec.compute_shuffled_index(i, count, seed) for i in range(count)]` -I.e. `mapping[i]` is the shuffled location of `i`. +The `mapping` is a look up array, constructed as +`[spec.compute_shuffled_index(i, count, seed) for i in range(count)]` 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 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). diff --git a/tests/formats/ssz_generic/README.md b/tests/formats/ssz_generic/README.md index 85a507985e..6a51209d11 100644 --- a/tests/formats/ssz_generic/README.md +++ b/tests/formats/ssz_generic/README.md @@ -1,35 +1,38 @@ # SSZ, generic tests -This set of test-suites provides general testing for SSZ: - to decode any container/list/vector/other type from binary data, encode it back, and compute the hash-tree-root. +This set of test-suites provides general testing for SSZ: to decode any +container/list/vector/other type from binary data, encode it back, and compute +the hash-tree-root. -This test collection for general-purpose SSZ is experimental. -The `ssz_static` suite is the required minimal support for SSZ, and should be prioritized. +This test collection for general-purpose SSZ is experimental. The `ssz_static` +suite is the required minimal support for SSZ, and should be prioritized. -The `ssz_generic` tests are split up into different handler, each specialized into a SSZ type: +The `ssz_generic` tests are split up into different handler, each specialized +into a SSZ type: - Vectors - - `basic_vector` - - `complex_vector` *not supported yet* + - `basic_vector` + - `complex_vector` *not supported yet* - List - - `basic_list` *not supported yet* - - `complex_list` *not supported yet* + - `basic_list` *not supported yet* + - `complex_list` *not supported yet* - Bitfields - - `bitvector` - - `bitlist` + - `bitvector` + - `bitlist` - Basic types - - `boolean` - - `uints` + - `boolean` + - `uints` - Containers - - `containers` - + - `containers` ## Format -For each type, a `valid` and an `invalid` suite is implemented. -The cases have the same format, but those in the `invalid` suite only declare a subset of the data a test in the `valid` declares. +For each type, a `valid` and an `invalid` suite is implemented. The cases have +the same format, but those in the `invalid` suite only declare a subset of the +data a test in the `valid` declares. -Each of the handlers encodes the SSZ type declaration in the file-name. See [Type Declarations](#type-declarations). +Each of the handlers encodes the SSZ type declaration in the file-name. See +[Type Declarations](#type-declarations). ### `valid` @@ -37,8 +40,8 @@ Valid has 3 parts: `meta.yaml`, `serialized.ssz_snappy`, `value.yaml` ### `meta.yaml` -Valid ssz objects can have a hash-tree-root. -The expected roots are encoded into the metadata yaml: +Valid ssz objects can have a hash-tree-root. The expected roots are encoded into +the metadata yaml: ```yaml root: Bytes32 -- Hash-tree-root of the object @@ -52,14 +55,17 @@ The serialized form of the object, as snappy-compressed SSZ bytes. ### `value.yaml` -The object, encoded as a YAML structure. Using the same familiar encoding as YAML data in the other test suites. +The object, encoded as a YAML structure. Using the same familiar encoding as +YAML data in the other test suites. ### Conditions 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. +- 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. - Hash-tree-root: the root should match the root declared in the metadata. ## `invalid` @@ -68,20 +74,22 @@ Test cases in the `invalid` suite only include the `serialized.ssz_snappy` #### Condition -Unlike the `valid` suite, invalid encodings do not have any `value` or hash tree root. -The `serialized` data should simply not be decoded without raising an error. - -Note that for some type declarations in the invalid suite, the type itself may technically be invalid. -This is a valid way of detecting `invalid` data too. E.g. a 0-length basic vector. +Unlike the `valid` suite, invalid encodings do not have any `value` or hash tree +root. The `serialized` data should simply not be decoded without raising an +error. +Note that for some type declarations in the invalid suite, the type itself may +technically be invalid. This is a valid way of detecting `invalid` data too. +E.g. a 0-length basic vector. ## Type declarations -Most types are not as static, and can reasonably be constructed during test runtime from the test case name. -Formats are listed below. +Most types are not as static, and can reasonably be constructed during test +runtime from the test case name. Formats are listed below. For each test case, an additional `_{extra...}` may be appended to the name, - where `{extra...}` contains a human readable indication of the test case contents for debugging purposes. +where `{extra...}` contains a human readable indication of the test case +contents for debugging purposes. ### `basic_vector` @@ -97,7 +105,6 @@ Data: {length}: an unsigned integer ``` - ### `bitlist` ``` @@ -110,7 +117,6 @@ Data: {limit}: the list limit, in bits, of the bitlist. Does not include the length-delimiting bit in the serialized form. ``` - ### `bitvector` ``` @@ -125,7 +131,8 @@ Data: ### `boolean` -A boolean has no type variations. Instead, file names just plainly describe the contents for debugging. +A boolean has no type variations. Instead, file names just plainly describe the +contents for debugging. ### `uints` @@ -141,7 +148,8 @@ Data: ### `containers` -Containers are more complicated than the other types. Instead, a set of pre-defined container structures is referenced: +Containers are more complicated than the other types. Instead, a set of +pre-defined container structures is referenced: ``` Template: @@ -154,7 +162,6 @@ Data: ``` ```python - class SingleFieldTestStruct(Container): A: byte @@ -180,7 +187,7 @@ class ComplexTestStruct(Container): A: uint16 B: List[uint16, 128] C: uint8 - D: Bytes[256] + D: ByteList[256] E: VarTestStruct F: Vector[FixedTestStruct, 4] G: Vector[VarTestStruct, 2] diff --git a/tests/formats/ssz_static/README.md b/tests/formats/ssz_static/README.md index ffa7373349..f99c8584ae 100644 --- a/tests/formats/ssz_static/README.md +++ b/tests/formats/ssz_static/README.md @@ -1,8 +1,9 @@ # SSZ, static tests -This set of test-suites provides static testing for SSZ: - to instantiate just the known Ethereum SSZ types from binary data. +This set of test-suites provides static testing for SSZ: to instantiate just the +known Ethereum SSZ types from binary data. -This series of tests is based on the spec-maintained `eth2spec/utils/ssz/ssz_impl.py`, i.e. fully consistent with the SSZ spec. +This series of tests is based on the spec-maintained +`eth2spec/utils/ssz/ssz_impl.py`, i.e. fully consistent with the SSZ spec. Test format documentation can be found here: [core test format](./core.md). diff --git a/tests/formats/ssz_static/core.md b/tests/formats/ssz_static/core.md index 09ff04e20d..5e9fb0ebe6 100644 --- a/tests/formats/ssz_static/core.md +++ b/tests/formats/ssz_static/core.md @@ -1,22 +1,26 @@ # Test format: SSZ static types -The goal of this type is to provide clients with a solid reference for how the known SSZ objects should be encoded. -Each object described in the Phase 0 spec is covered. -This is important, as many of the clients aiming to serialize/deserialize objects directly into structs/classes -do not support (or have alternatives for) generic SSZ encoding/decoding. +The goal of this type is to provide clients with a solid reference for how the +known SSZ objects should be encoded. Each object described in the Phase 0 spec +is covered. This is important, as many of the clients aiming to +serialize/deserialize objects directly into structs/classes do not support (or +have alternatives for) generic SSZ encoding/decoding. This test-format ensures these direct serializations are covered. -Note that this test suite does not cover the invalid-encoding case: - SSZ implementations should be hardened against invalid inputs with the other SSZ tests as guide, along with fuzzing. +Note that this test suite does not cover the invalid-encoding case: SSZ +implementations should be hardened against invalid inputs with the other SSZ +tests as guide, along with fuzzing. ## Test case format -Each SSZ type is a `handler`, since the format is semantically different: the type of the data is different. +Each SSZ type is a `handler`, since the format is semantically different: the +type of the data is different. -One can iterate over the handlers, and select the type based on the handler name. -Suites are then the same format, but each specialized in one randomization mode. -Some randomization modes may only produce a single test case (e.g. the all-zeroes case). +One can iterate over the handlers, and select the type based on the handler +name. Suites are then the same format, but each specialized in one randomization +mode. Some randomization modes may only produce a single test case (e.g. the +all-zeroes case). The output parts are: `roots.yaml`, `serialized.ssz_snappy`, `value.yaml` @@ -34,20 +38,23 @@ The SSZ-snappy encoded bytes. The same value as `serialized.ssz_snappy`, represented as YAML. - ## Condition A test-runner can implement the following assertions: + - If YAML decoding of SSZ objects is supported by the implementation: - - 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` + - 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, - 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` - + - 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` ## References **`serialized`**—[SSZ serialization](../../../ssz/simple-serialize.md#serialization) -**`root`**—[hash_tree_root](../../../ssz/simple-serialize.md#merkleization) function +**`root`**—[hash_tree_root](../../../ssz/simple-serialize.md#merkleization) +function diff --git a/tests/formats/sync/README.md b/tests/formats/sync/README.md new file mode 100644 index 0000000000..519c732a6d --- /dev/null +++ b/tests/formats/sync/README.md @@ -0,0 +1,4 @@ +# Sync tests + +It reuses the [fork choice test format](../fork_choice/README.md) to apply the +test script. diff --git a/tests/formats/transition/README.md b/tests/formats/transition/README.md index 37df655397..b06c175094 100644 --- a/tests/formats/transition/README.md +++ b/tests/formats/transition/README.md @@ -2,14 +2,22 @@ Transition tests to cover processing the chain across a fork boundary. -Each test case contains a `post_fork` key in the `meta.yaml` that indicates the target fork which also fixes the fork the test begins in. +Each test case contains a `post_fork` key in the `meta.yaml` that indicates the +target fork which also fixes the fork the test begins in. Clients should assume forks happen sequentially in the following manner: 0. `phase0` 1. `altair` +2. `bellatrix` +3. `capella` +4. `deneb` -For example, if a test case has `post_fork` of `altair`, the test consumer should assume the test begins in `phase0` and use that specification to process the initial state and any blocks up until the fork epoch. After the fork happens, the test consumer should use the specification according to the `altair` fork to process the remaining data. +For example, if a test case has `post_fork` of `altair`, the test consumer +should assume the test begins in `phase0` and use that specification to process +the initial state and any blocks up until the fork epoch. After the fork +happens, the test consumer should use the specification according to the +`altair` fork to process the remaining data. ## Test case format @@ -27,26 +35,26 @@ Refer to the specs for the relevant fork for further details. ### `pre.ssz_snappy` -A SSZ-snappy encoded `BeaconState` according to the specification of -the initial fork, the state before running the block transitions. +A SSZ-snappy encoded `BeaconState` according to the specification of the initial +fork, the state before running the block transitions. ### `blocks_.ssz_snappy` -A series of files, with `` in range `[0, blocks_count)`. -Blocks must be processed in order, following the main transition function -(i.e. process slot and epoch transitions in between blocks as normal). +A series of files, with `` in range `[0, blocks_count)`. Blocks must be +processed in order, following the main transition function (i.e. process slot +and epoch transitions in between blocks as normal). -Blocks are encoded as `SignedBeaconBlock`s from the relevant spec version -as indicated by the `post_fork` and `fork_block` data in the `meta.yaml`. +Blocks are encoded as `SignedBeaconBlock`s from the relevant spec version as +indicated by the `post_fork` and `fork_block` data in the `meta.yaml`. -As blocks span fork boundaires, a `fork_block` number is given in -the `meta.yaml` to help resolve which blocks belong to which fork. +As blocks span fork boundaries, a `fork_block` number is given in the +`meta.yaml` to help resolve which blocks belong to which fork. -The `fork_block` is the index in the test data of the **last** block -of the **initial** fork. +The `fork_block` is the index in the test data of the **last** block of the +**initial** fork. -To demonstrate, the following diagram shows slots with `_` and blocks -in those slots as `x`. The fork happens at the epoch delineated by the `|`. +To demonstrate, the following diagram shows slots with `_` and blocks in those +slots as `x`. The fork happens at the epoch delineated by the `|`. ``` x x x x @@ -59,13 +67,13 @@ testing the fork from Phase 0 to Altair, blocks with indices `0, 1` represent `SignedBeaconBlock`s defined in the Phase 0 spec and blocks with indices `2, 3` represent `SignedBeaconBlock`s defined in the Altair spec. -*Note*: If `fork_block` is missing, then all block data should be -interpreted as belonging to the post fork. +*Note*: If `fork_block` is missing, then all block data should be interpreted as +belonging to the post fork. ### `post.ssz_snappy` -A SSZ-snappy encoded `BeaconState` according to the specification of -the post fork, the state after running the block transitions. +A SSZ-snappy encoded `BeaconState` according to the specification of the post +fork, the state after running the block transitions. ## Condition diff --git a/tests/generators/README.md b/tests/generators/README.md deleted file mode 100644 index a84331dcb7..0000000000 --- a/tests/generators/README.md +++ /dev/null @@ -1,230 +0,0 @@ -# Consensus test generators - -This directory contains all the generators for tests, consumed by consensus-layer client implementations. - -Any issues with the generators and/or generated tests should be filed in the repository that hosts the generator outputs, - here: [ethereum/consensus-spec-tests](https://github.com/ethereum/consensus-spec-tests). - -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. - -## Table of contents - - - - - -- [How to run generators](#how-to-run-generators) - - [Cleaning](#cleaning) - - [Running all test generators](#running-all-test-generators) - - [Running a single generator](#running-a-single-generator) -- [Developing a generator](#developing-a-generator) -- [How to add a new test generator](#how-to-add-a-new-test-generator) -- [How to remove a test generator](#how-to-remove-a-test-generator) - - - - - -## How to run generators - -Prerequisites: -- Python 3 installed -- PIP 3 -- GNU Make - -### Cleaning - -This removes the existing virtual environments (`/tests/generators//venv`) and generated tests (`../consensus-spec-tests/tests`). - -```bash -make clean -``` - -### Running all test generators - -This runs all of the generators. - -```bash -make -j 4 generate_tests -``` - -The `-j N` flag makes the generators run in parallel, with `N` being the amount of cores. - - -### Running a single generator - -The makefile auto-detects generators in the `tests/generators` directory and provides a tests-gen target (gen_) for each generator. See example: - -```bash -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 a `main.py` file. The shuffling test generator is a good minimal starting point: - -```python -from eth2spec.phase0 import spec as spec -from eth_utils import to_tuple -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from preset_loader import loader -from typing import Iterable - - -def shuffling_case_fn(seed, count): - yield 'mapping', 'data', { - 'seed': '0x' + seed.hex(), - 'count': count, - 'mapping': [int(spec.compute_shuffled_index(i, count, seed)) for i in range(count)] - } - - -def shuffling_case(seed, count): - return f'shuffle_0x{seed.hex()}_{count}', lambda: shuffling_case_fn(seed, count) - - -@to_tuple -def shuffling_test_cases(): - for seed in [spec.hash(seed_init_value.to_bytes(length=4, byteorder='little')) for seed_init_value in range(30)]: - for count in [0, 1, 2, 3, 5, 10, 33, 100, 1000, 9999]: - yield shuffling_case(seed, count) - - -def create_provider(config_name: str) -> gen_typing.TestProvider: - - def prepare_fn(configs_path: str) -> str: - presets = loader.load_presets(configs_path, config_name) - spec.apply_constants_preset(presets) - return config_name - - def cases_fn() -> Iterable[gen_typing.TestCase]: - for (case_name, case_fn) in shuffling_test_cases(): - yield gen_typing.TestCase( - fork_name='phase0', - runner_name='shuffling', - handler_name='core', - suite_name='shuffle', - case_name=case_name, - case_fn=case_fn - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - gen_runner.run_generator("shuffling", [create_provider("minimal"), create_provider("mainnet")]) -``` - -This generator: -- builds off of `gen_runner.run_generator` to handle configuration / filter / output logic. -- parametrized the creation of a test-provider to support multiple configs. -- Iterates through tests cases. -- Each test case provides a `case_fn`, to be executed by the `gen_runner.run_generator` if the case needs to be generated. But skipped otherwise. - -To extend this, one could decide to parametrize the `shuffling_test_cases` function, and create test provider for any test-yielding function. - -Another example, to generate tests from pytests: - -```python -from eth2spec.phase0 import spec as spec_phase0 -from eth2spec.altair import spec as spec_altair -from eth2spec.test.helpers.constants import PHASE0, ALTAIR - -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators - - -specs = (spec_phase0, spec_altair) - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ - 'blocks', - 'slots', - ]} - altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [ - 'blocks', - ]}, **phase_0_mods} # also run the previous phase 0 tests - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - } - - run_state_test_generators(runner_name="sanity", specs=specs, all_mods=all_mods) -``` - -Here multiple phases load the configuration, and the stream of test cases is derived from a pytest file using the `eth2spec.gen_helpers.gen_from_tests.gen.run_state_test_generators` utility. Note that this helper generates all available tests of `TESTGEN_FORKS` forks of `ALL_CONFIGS` configs of the given runner. - -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. -- 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). -- 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 - -To add a new test generator that builds `New Tests`: - -1. Create a new directory `new_tests` within the `tests/generators` directory. - Note that `new_tests` is also the name of the directory in which the tests will appear in the tests repository later. -2. Your generator is assumed to have a `requirements.txt` file, - with any dependencies it may need. Leave it empty if your generator has none. -3. Your generator is assumed to have a `main.py` file in its root. - By adding the base generator to your requirements, you can make a generator really easily. See docs below. -4. Your generator is called with `-o some/file/path/for_testing/can/be_anything -c some/other/path/to_configs/`. - The base generator helps you handle this; you only have to define test case providers. -5. Finally, add any linting or testing commands to the - [circleci config file](../../.circleci/config.yml) if desired to increase code quality. - Or add it to the [`Makefile`](../../Makefile), if it can be run locally. - -*Note*: You do not have to change the makefile. -However, if necessary (e.g. not using Python, or mixing in other languages), submit an issue, and it can be a special case. -Do note that generators should be easy to maintain, lean, and based on the spec. - - -## How to remove a test generator - -If a test generator is not needed anymore, undo the steps described above and make a new release: - -1. Remove the generator directory. -2. Remove the generated tests in the [`consensus-spec-tests`](https://github.com/ethereum/consensus-spec-tests) repository by opening a pull request there. -3. Make a new release. diff --git a/tests/generators/bls/README.md b/tests/generators/bls/README.md deleted file mode 100644 index 24013f88e7..0000000000 --- a/tests/generators/bls/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# BLS Test Generator - -The [BLS Signature APIs](../../../specs/phase0/beacon-chain.md#bls-signatures) - -Information on the format of the tests can be found in the [BLS test formats documentation](../../formats/bls/README.md). - -## Resources - -- [IETF BLS Signature Scheme](https://datatracker.ietf.org/doc/draft-irtf-cfrg-bls-signature/) -- [Finite Field Arithmetic](http://www.springeronline.com/sgw/cda/pageitems/document/cda_downloaddocument/0,11996,0-0-45-110359-0,00.pdf) -- Chapter 2 of [Elliptic Curve Cryptography](http://cacr.uwaterloo.ca/ecc/). Darrel Hankerson, Alfred Menezes, and Scott Vanstone diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py deleted file mode 100644 index 75468b1627..0000000000 --- a/tests/generators/bls/main.py +++ /dev/null @@ -1,543 +0,0 @@ -""" -BLS test vectors generator -""" - -from hashlib import sha256 -from typing import Tuple, Iterable, Any, Callable, Dict - -from eth_utils import ( - encode_hex, - int_to_big_endian, -) -import milagro_bls_binding as milagro_bls - -from eth2spec.utils import bls -from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.test.helpers.typing import SpecForkName -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.altair import spec - - -def to_bytes(i): - return i.to_bytes(32, "big") - - -def hash(x): - return sha256(x).digest() - - -def int_to_hex(n: int, byte_length: int = None) -> str: - byte_value = int_to_big_endian(n) - if byte_length: - byte_value = byte_value.rjust(byte_length, b'\x00') - return encode_hex(byte_value) - - -def hex_to_int(x: str) -> int: - return int(x, 16) - - -MESSAGES = [ - bytes(b'\x00' * 32), - bytes(b'\x56' * 32), - bytes(b'\xab' * 32), -] -SAMPLE_MESSAGE = b'\x12' * 32 - -PRIVKEYS = [ - # Curve order is 256 so private keys are 32 bytes at most. - # Also not all integers is a valid private key, so using pre-generated keys - hex_to_int('0x00000000000000000000000000000000263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3'), - hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'), - hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'), -] -PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] - -ZERO_PUBKEY = b'\x00' * 48 -G1_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 47 - -ZERO_SIGNATURE = b'\x00' * 96 -G2_POINT_AT_INFINITY = b'\xc0' + b'\x00' * 95 - -ZERO_PRIVKEY = 0 -ZERO_PRIVKEY_BYTES = b'\x00' * 32 - - -def expect_exception(func, *args): - try: - func(*args) - except Exception: - pass - else: - raise Exception("should have raised exception") - - -def case01_sign(): - # Valid cases - for privkey in PRIVKEYS: - for message in MESSAGES: - sig = bls.Sign(privkey, message) - assert sig == milagro_bls.Sign(to_bytes(privkey), message) # double-check with milagro - identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}' - yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'privkey': int_to_hex(privkey), - 'message': encode_hex(message), - }, - 'output': encode_hex(sig) - } - # Edge case: privkey == 0 - expect_exception(bls.Sign, ZERO_PRIVKEY, message) - expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) - yield f'sign_case_zero_privkey', { - 'input': { - 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), - 'message': encode_hex(message), - }, - 'output': None - } - - -def case02_verify(): - for i, privkey in enumerate(PRIVKEYS): - for message in MESSAGES: - # Valid signature - signature = bls.Sign(privkey, message) - pubkey = bls.SkToPk(privkey) - - assert milagro_bls.SkToPk(to_bytes(privkey)) == pubkey - assert milagro_bls.Sign(to_bytes(privkey), message) == signature - - identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}' - - assert bls.Verify(pubkey, message, signature) - assert milagro_bls.Verify(pubkey, message, signature) - - yield f'verify_valid_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkey': encode_hex(pubkey), - 'message': encode_hex(message), - 'signature': encode_hex(signature), - }, - 'output': True, - } - - # Invalid signatures -- wrong pubkey - wrong_pubkey = bls.SkToPk(PRIVKEYS[(i + 1) % len(PRIVKEYS)]) - identifier = f'{encode_hex(wrong_pubkey)}_{encode_hex(message)}' - assert not bls.Verify(wrong_pubkey, message, signature) - assert not milagro_bls.Verify(wrong_pubkey, message, signature) - yield f'verify_wrong_pubkey_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkey': encode_hex(wrong_pubkey), - 'message': encode_hex(message), - 'signature': encode_hex(signature), - }, - 'output': False, - } - - # Invalid signature -- tampered with signature - tampered_signature = signature[:-4] + b'\xFF\xFF\xFF\xFF' - identifier = f'{encode_hex(pubkey)}_{encode_hex(message)}' - assert not bls.Verify(pubkey, message, tampered_signature) - assert not milagro_bls.Verify(pubkey, message, tampered_signature) - yield f'verify_tampered_signature_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkey': encode_hex(pubkey), - 'message': encode_hex(message), - 'signature': encode_hex(tampered_signature), - }, - 'output': False, - } - - # Invalid pubkey and signature with the point at infinity - assert not bls.Verify(G1_POINT_AT_INFINITY, SAMPLE_MESSAGE, G2_POINT_AT_INFINITY) - assert not milagro_bls.Verify(G1_POINT_AT_INFINITY, SAMPLE_MESSAGE, G2_POINT_AT_INFINITY) - yield f'verify_infinity_pubkey_and_infinity_signature', { - 'input': { - 'pubkey': encode_hex(G1_POINT_AT_INFINITY), - 'message': encode_hex(SAMPLE_MESSAGE), - 'signature': encode_hex(G2_POINT_AT_INFINITY), - }, - 'output': False, - } - - -def case03_aggregate(): - for message in MESSAGES: - sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS] - aggregate_sig = bls.Aggregate(sigs) - assert aggregate_sig == milagro_bls.Aggregate(sigs) - yield f'aggregate_{encode_hex(message)}', { - 'input': [encode_hex(sig) for sig in sigs], - 'output': encode_hex(aggregate_sig), - } - - # Invalid pubkeys -- len(pubkeys) == 0 - expect_exception(bls.Aggregate, []) - # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. - # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.8 - yield f'aggregate_na_signatures', { - 'input': [], - 'output': None, - } - - # Valid to aggregate G2 point at infinity - aggregate_sig = bls.Aggregate([G2_POINT_AT_INFINITY]) - assert aggregate_sig == milagro_bls.Aggregate([G2_POINT_AT_INFINITY]) == G2_POINT_AT_INFINITY - yield f'aggregate_infinity_signature', { - 'input': [encode_hex(G2_POINT_AT_INFINITY)], - 'output': encode_hex(aggregate_sig), - } - - -def case04_fast_aggregate_verify(): - for i, message in enumerate(MESSAGES): - privkeys = PRIVKEYS[:i + 1] - sigs = [bls.Sign(privkey, message) for privkey in privkeys] - aggregate_signature = bls.Aggregate(sigs) - pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] - pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys] - - # Valid signature - identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert bls.FastAggregateVerify(pubkeys, message, aggregate_signature) - assert milagro_bls.FastAggregateVerify(pubkeys, message, aggregate_signature) - yield f'fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_serial, - 'message': encode_hex(message), - 'signature': encode_hex(aggregate_signature), - }, - 'output': True, - } - - # Invalid signature -- extra pubkey - pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])] - pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] - identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' - assert not bls.FastAggregateVerify(pubkeys_extra, message, aggregate_signature) - assert not milagro_bls.FastAggregateVerify(pubkeys_extra, message, aggregate_signature) - yield f'fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_extra_serial, - 'message': encode_hex(message), - 'signature': encode_hex(aggregate_signature), - }, - 'output': False, - } - - # Invalid signature -- tampered with signature - tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff' - identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert not bls.FastAggregateVerify(pubkeys, message, tampered_signature) - assert not milagro_bls.FastAggregateVerify(pubkeys, message, tampered_signature) - yield f'fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_serial, - 'message': encode_hex(message), - 'signature': encode_hex(tampered_signature), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE - assert not bls.FastAggregateVerify([], message, G2_POINT_AT_INFINITY) - assert not milagro_bls.FastAggregateVerify([], message, G2_POINT_AT_INFINITY) - yield f'fast_aggregate_verify_na_pubkeys_and_infinity_signature', { - 'input': { - 'pubkeys': [], - 'message': encode_hex(message), - 'signature': encode_hex(G2_POINT_AT_INFINITY), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not bls.FastAggregateVerify([], message, ZERO_SIGNATURE) - assert not milagro_bls.FastAggregateVerify([], message, ZERO_SIGNATURE) - yield f'fast_aggregate_verify_na_pubkeys_and_zero_signature', { - 'input': { - 'pubkeys': [], - 'message': encode_hex(message), - 'signature': encode_hex(ZERO_SIGNATURE), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- pubkeys contains point at infinity - pubkeys = PUBKEYS.copy() - pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] - signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] - aggregate_signature = bls.Aggregate(signatures) - assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - yield f'fast_aggregate_verify_infinity_pubkey', { - 'input': { - 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], - 'message': encode_hex(SAMPLE_MESSAGE), - 'signature': encode_hex(aggregate_signature), - }, - 'output': False, - } - - -def case05_aggregate_verify(): - pubkeys = [] - pubkeys_serial = [] - messages = [] - messages_serial = [] - sigs = [] - for privkey, message in zip(PRIVKEYS, MESSAGES): - sig = bls.Sign(privkey, message) - pubkey = bls.SkToPk(privkey) - pubkeys.append(pubkey) - pubkeys_serial.append(encode_hex(pubkey)) - messages.append(message) - messages_serial.append(encode_hex(message)) - sigs.append(sig) - - aggregate_signature = bls.Aggregate(sigs) - assert bls.AggregateVerify(pubkeys, messages, aggregate_signature) - assert milagro_bls.AggregateVerify(pubkeys, messages, aggregate_signature) - yield f'aggregate_verify_valid', { - 'input': { - 'pubkeys': pubkeys_serial, - 'messages': messages_serial, - 'signature': encode_hex(aggregate_signature), - }, - 'output': True, - } - - tampered_signature = aggregate_signature[:4] + b'\xff\xff\xff\xff' - assert not bls.AggregateVerify(pubkey, messages, tampered_signature) - assert not milagro_bls.AggregateVerify(pubkeys, messages, tampered_signature) - yield f'aggregate_verify_tampered_signature', { - 'input': { - 'pubkeys': pubkeys_serial, - 'messages': messages_serial, - 'signature': encode_hex(tampered_signature), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == Z1_SIGNATURE - assert not bls.AggregateVerify([], [], G2_POINT_AT_INFINITY) - assert not milagro_bls.AggregateVerify([], [], G2_POINT_AT_INFINITY) - yield f'aggregate_verify_na_pubkeys_and_infinity_signature', { - 'input': { - 'pubkeys': [], - 'messages': [], - 'signature': encode_hex(G2_POINT_AT_INFINITY), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not bls.AggregateVerify([], [], ZERO_SIGNATURE) - assert not milagro_bls.AggregateVerify([], [], ZERO_SIGNATURE) - yield f'aggregate_verify_na_pubkeys_and_zero_signature', { - 'input': { - 'pubkeys': [], - 'messages': [], - 'signature': encode_hex(ZERO_SIGNATURE), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- pubkeys contains point at infinity - pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] - messages_with_sample = messages + [SAMPLE_MESSAGE] - assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) - assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) - yield f'aggregate_verify_infinity_pubkey', { - 'input': { - 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], - 'messages': [encode_hex(message) for message in messages_with_sample], - 'signature': encode_hex(aggregate_signature), - }, - 'output': False, - } - - -def case06_eth_aggregate_pubkeys(): - for pubkey in PUBKEYS: - encoded_pubkey = encode_hex(pubkey) - aggregate_pubkey = spec.eth_aggregate_pubkeys([pubkey]) - # Should be unchanged - assert aggregate_pubkey == milagro_bls._AggregatePKs([pubkey]) == pubkey - # Valid pubkey - yield f'eth_aggregate_pubkeys_valid_{(hash(bytes(encoded_pubkey, "utf-8"))[:8]).hex()}', { - 'input': [encode_hex(pubkey)], - 'output': encode_hex(aggregate_pubkey), - } - - # Valid pubkeys - aggregate_pubkey = spec.eth_aggregate_pubkeys(PUBKEYS) - assert aggregate_pubkey == milagro_bls._AggregatePKs(PUBKEYS) - yield f'eth_aggregate_pubkeys_valid_pubkeys', { - 'input': [encode_hex(pubkey) for pubkey in PUBKEYS], - 'output': encode_hex(aggregate_pubkey), - } - - # Invalid pubkeys -- len(pubkeys) == 0 - expect_exception(spec.eth_aggregate_pubkeys, []) - expect_exception(milagro_bls._AggregatePKs, []) - yield f'eth_aggregate_pubkeys_empty_list', { - 'input': [], - 'output': None, - } - - # Invalid pubkeys -- [ZERO_PUBKEY] - expect_exception(spec.eth_aggregate_pubkeys, [ZERO_PUBKEY]) - expect_exception(milagro_bls._AggregatePKs, [ZERO_PUBKEY]) - yield f'eth_aggregate_pubkeys_zero_pubkey', { - 'input': [encode_hex(ZERO_PUBKEY)], - 'output': None, - } - - # Invalid pubkeys -- G1 point at infinity - expect_exception(spec.eth_aggregate_pubkeys, [G1_POINT_AT_INFINITY]) - expect_exception(milagro_bls._AggregatePKs, [G1_POINT_AT_INFINITY]) - yield f'eth_aggregate_pubkeys_infinity_pubkey', { - 'input': [encode_hex(G1_POINT_AT_INFINITY)], - 'output': None, - } - - # Invalid pubkeys -- b'\x40\x00\x00\x00....\x00' pubkey - x40_pubkey = b'\x40' + b'\00' * 47 - expect_exception(spec.eth_aggregate_pubkeys, [x40_pubkey]) - expect_exception(milagro_bls._AggregatePKs, [x40_pubkey]) - yield f'eth_aggregate_pubkeys_x40_pubkey', { - 'input': [encode_hex(x40_pubkey)], - 'output': None, - } - - -def case07_eth_fast_aggregate_verify(): - """ - Similar to `case04_fast_aggregate_verify` except for the empty case - """ - for i, message in enumerate(MESSAGES): - privkeys = PRIVKEYS[:i + 1] - sigs = [bls.Sign(privkey, message) for privkey in privkeys] - aggregate_signature = bls.Aggregate(sigs) - pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] - pubkeys_serial = [encode_hex(pubkey) for pubkey in pubkeys] - - # Valid signature - identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert spec.eth_fast_aggregate_verify(pubkeys, message, aggregate_signature) - yield f'eth_fast_aggregate_verify_valid_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_serial, - 'message': encode_hex(message), - 'signature': encode_hex(aggregate_signature), - }, - 'output': True, - } - - # Invalid signature -- extra pubkey - pubkeys_extra = pubkeys + [bls.SkToPk(PRIVKEYS[-1])] - pubkeys_extra_serial = [encode_hex(pubkey) for pubkey in pubkeys_extra] - identifier = f'{pubkeys_extra_serial}_{encode_hex(message)}' - assert not spec.eth_fast_aggregate_verify(pubkeys_extra, message, aggregate_signature) - yield f'eth_fast_aggregate_verify_extra_pubkey_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_extra_serial, - 'message': encode_hex(message), - 'signature': encode_hex(aggregate_signature), - }, - 'output': False, - } - - # Invalid signature -- tampered with signature - tampered_signature = aggregate_signature[:-4] + b'\xff\xff\xff\xff' - identifier = f'{pubkeys_serial}_{encode_hex(message)}' - assert not spec.eth_fast_aggregate_verify(pubkeys, message, tampered_signature) - yield f'eth_fast_aggregate_verify_tampered_signature_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { - 'input': { - 'pubkeys': pubkeys_serial, - 'message': encode_hex(message), - 'signature': encode_hex(tampered_signature), - }, - 'output': False, - } - - # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY is VALID - assert spec.eth_fast_aggregate_verify([], message, G2_POINT_AT_INFINITY) - yield f'eth_fast_aggregate_verify_na_pubkeys_and_infinity_signature', { - 'input': { - 'pubkeys': [], - 'message': encode_hex(message), - 'signature': encode_hex(G2_POINT_AT_INFINITY), - }, - 'output': True, - } - - # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... - assert not spec.eth_fast_aggregate_verify([], message, ZERO_SIGNATURE) - yield f'eth_fast_aggregate_verify_na_pubkeys_and_zero_signature', { - 'input': { - 'pubkeys': [], - 'message': encode_hex(message), - 'signature': encode_hex(ZERO_SIGNATURE), - }, - 'output': False, - } - - # Invalid pubkeys and signature -- pubkeys contains point at infinity - pubkeys = PUBKEYS.copy() - pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] - signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] - aggregate_signature = bls.Aggregate(signatures) - assert not spec.eth_fast_aggregate_verify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - yield f'eth_fast_aggregate_verify_infinity_pubkey', { - 'input': { - 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], - 'message': encode_hex(SAMPLE_MESSAGE), - 'signature': encode_hex(aggregate_signature), - }, - 'output': False, - } - - -def create_provider(fork_name: SpecForkName, - handler_name: str, - test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: - - def prepare_fn() -> None: - # Nothing to load / change in spec. Maybe in future forks. - # Put the tests into the general config category, to not require any particular configuration. - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - for data in test_case_fn(): - print(data) - (case_name, case_content) = data - yield gen_typing.TestCase( - fork_name=fork_name, - preset_name='general', - runner_name='bls', - handler_name=handler_name, - suite_name='small', - case_name=case_name, - case_fn=lambda: [('data', 'data', case_content)] - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - bls.use_py_ecc() # Py-ecc is chosen instead of Milagro, since the code is better understood to be correct. - gen_runner.run_generator("bls", [ - # PHASE0 - create_provider(PHASE0, 'sign', case01_sign), - create_provider(PHASE0, 'verify', case02_verify), - create_provider(PHASE0, 'aggregate', case03_aggregate), - create_provider(PHASE0, 'fast_aggregate_verify', case04_fast_aggregate_verify), - create_provider(PHASE0, 'aggregate_verify', case05_aggregate_verify), - # ALTAIR - create_provider(ALTAIR, 'eth_aggregate_pubkeys', case06_eth_aggregate_pubkeys), - create_provider(ALTAIR, 'eth_fast_aggregate_verify', case07_eth_fast_aggregate_verify), - ]) diff --git a/tests/generators/bls/requirements.txt b/tests/generators/bls/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/bls/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/compliance_runners/fork_choice/README.md b/tests/generators/compliance_runners/fork_choice/README.md new file mode 100644 index 0000000000..6d696216e8 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/README.md @@ -0,0 +1,63 @@ +# Fork choice compliance test generator + +Fork Choice test generator intended to produce tests to validate conformance to +the specs of various Fork Choice implementations. + +Implementation of the approach described in the +[Fork Choice compliance testing framework](https://hackmd.io/@ericsson49/fork-choice-implementation-vs-spec-testing). + +Preliminary research has been also performed in this +[repo](https://github.com/txrx-research/fork_choice_test_generation/tree/main). + +To simplify adoption of the tests, we follow the test format described in the +[fork choice test formats documentation](../../../formats/fork_choice/README.md), +with a minor exception (new check added). + +This work was supported by a grant from the Ethereum Foundation. + +# Pre-requisites + +Install pyspec using the top-level Makefile, this will install necessary +pre-requiesites. + +``` +> make pyspec +``` + +# Generating tests + +From the root directory: + +``` +> python -m tests.generators.compliance_runners.fork_choice.test_gen -o ${test_dir} --fc-gen-config ${config} +``` + +where `config` can be either: `tiny`, `small` or \`standard. + +Or specify path to the configuration file directly: + +``` +> python -m tests.generators.compliance_runners.fork_choice.test_gen -o ${test_dir} --fc-gen-config-path ${config_path} +``` + +There are three configurations in the repo: [tiny](tiny/), [small](small/) and +[standard](standard/). + +# Running tests + +From the root directory: + +``` +> python -m tests.generators.compliance_runners.fork_choice.test_run -i ${test_dir} +``` + +# Generating configurations + +Files in [tiny](tiny/), [small](small/) and [standard](standard/) are generated +with [generate_test_instances.py](generate_test_instances.py), e.g. + +``` +> python -m tests.generators.compliance_runners.fork_choice.generate_test_instances +``` + +But one normally doesn't need to generate them. diff --git a/tests/generators/compliance_runners/fork_choice/generate_test_instances.py b/tests/generators/compliance_runners/fork_choice/generate_test_instances.py new file mode 100644 index 0000000000..a8b35a2855 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/generate_test_instances.py @@ -0,0 +1,305 @@ +from collections.abc import Iterable +from itertools import product +from os import path + +from minizinc import Instance, Model, Solver +from ruamel.yaml import YAML +from toolz.dicttoolz import merge + +base_dir = path.dirname(__file__) +model_dir = path.join(base_dir, "model") + + +def solve_sm_links( + anchor_epoch: int, number_of_epochs: int, number_of_links: int, number_of_solutions: int +): + sm_links = Model(path.join(model_dir, "SM_links.mzn")) + solver = Solver.lookup("gecode") + instance = Instance(solver, sm_links) + instance["AE"] = anchor_epoch # anchor epoch + instance["NE"] = number_of_epochs # number of epochs, starting from AE + instance["NL"] = number_of_links # number of super-majority links + + assert number_of_solutions is None + solutions = instance.solve(all_solutions=True) + + for i in range(len(solutions)): + yield {"sm_links": list(zip(solutions[i, "sources"], solutions[i, "targets"]))} + + +def generate_sm_links(params): + anchor_epoch = params["anchor_epoch"] + number_of_epochs = params["number_of_epochs"] + number_of_links = params["number_of_links"] + number_of_solutions = params.get("number_of_solutions") + yield from solve_sm_links(anchor_epoch, number_of_epochs, number_of_links, number_of_solutions) + + +def solve_block_tree( + number_of_blocks: int, max_children: int, number_of_solutions: int +) -> Iterable[dict]: + model = Model(path.join(model_dir, "Block_tree.mzn")) + solver = Solver.lookup("gecode") + instance = Instance(solver, model) + instance["NB"] = number_of_blocks + instance["MC"] = max_children + + if number_of_solutions is None: + solutions = instance.solve(all_solutions=True) + else: + solutions = instance.solve(nr_solutions=number_of_solutions) + + return [{"block_parents": s.parent} for s in solutions] + + +def generate_block_tree(params) -> Iterable[dict]: + number_of_blocks = params["number_of_blocks"] + max_children = params["max_children"] + number_of_solutions = params.get("number_of_solutions") + yield from solve_block_tree(number_of_blocks, max_children, number_of_solutions) + + +def solve_block_cover( + anchor_epoch: int, + store_justified_epoch_equal_zero: bool, + block_voting_source_epoch_equal_store_justified_epoch: bool, + block_voting_source_epoch_plus_two_greater_or_equal_current_epoch: bool, + block_is_leaf: bool, + number_of_solutions: int, +): + block_cover3 = Model(path.join(model_dir, "Block_cover.mzn")) + solver = Solver.lookup("gecode") + instance = Instance(solver, block_cover3) + instance["AE"] = anchor_epoch + instance["store_je_eq_zero"] = store_justified_epoch_equal_zero + instance["block_vse_eq_store_je"] = block_voting_source_epoch_equal_store_justified_epoch + instance["block_vse_plus_two_ge_curr_e"] = ( + block_voting_source_epoch_plus_two_greater_or_equal_current_epoch + ) + instance["block_is_leaf"] = block_is_leaf + + assert number_of_solutions is not None + result = instance.solve(nr_solutions=number_of_solutions) + + if anchor_epoch == 0 and not store_justified_epoch_equal_zero: + return + + for s in result.solution: + max_block = s.max_block + yield { + "block_epochs": s.es[: max_block + 1], + "parents": s.parents[: max_block + 1], + "previous_justifications": s.prevs[: max_block + 1], + "current_justifications": s.currs[: max_block + 1], + "current_epoch": s.curr_e, + "store_justified_epoch": s.store_je, + "target_block": s.target_block, + "predicates": { + "store_je_eq_zero": store_justified_epoch_equal_zero, + "block_vse_eq_store_je": block_voting_source_epoch_equal_store_justified_epoch, + "block_vse_plus_two_ge_curr_e": block_voting_source_epoch_plus_two_greater_or_equal_current_epoch, + "block_is_leaf": block_is_leaf, + }, + } + + +def generate_block_cover(params): + anchor_epoch = params["anchor_epoch"] + number_of_solutions = params.get("number_of_solutions", 1) + + for ps in product(*([(True, False)] * 4)): + yield from solve_block_cover(anchor_epoch, *ps, number_of_solutions) + + +gen_params = { + ################### + # tiny instances # + ################### + "block_tree_tree_tiny": { + "out_path": "tiny/block_tree_tree.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + {"anchor_epoch": 0, "number_of_epochs": 4, "number_of_links": 3}, + {"number_of_blocks": 8, "max_children": 2, "number_of_solutions": 3}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 3}, + ), + ], + }, + "block_tree_other_tiny": { + "out_path": "tiny/block_tree_other.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 12, "max_children": 2, "number_of_solutions": 3}, + ), + ], + }, + "block_cover_tiny": { + "out_path": "tiny/block_cover.yaml", + "models": ["block_cover"], + "params": [ + ({"anchor_epoch": 0, "number_of_solutions": 1},), + ({"anchor_epoch": 2, "number_of_solutions": 1},), + ], + }, + ################### + # small instances # + ################### + "block_tree_tree_small": { + "out_path": "small/block_tree_tree.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + {"anchor_epoch": 0, "number_of_epochs": 5, "number_of_links": 3}, + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 2}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 5, "max_children": 3, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 12, "max_children": 2, "number_of_solutions": 73}, + ), + ], + }, + "block_tree_tree_small_2": { + "out_path": "small/block_tree_tree_2.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + {"anchor_epoch": 0, "number_of_epochs": 6, "number_of_links": 4}, + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 2}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 6, "max_children": 4, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 12, "max_children": 2, "number_of_solutions": 283}, + ), + ], + }, + "block_tree_other_small": { + "out_path": "small/block_tree_other.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 12, "max_children": 2, "number_of_solutions": 4}, + ), + ], + }, + "block_cover_small": { + "out_path": "small/block_cover.yaml", + "models": ["block_cover"], + "params": [ + ({"anchor_epoch": 0, "number_of_solutions": 2},), + ({"anchor_epoch": 2, "number_of_solutions": 2},), + ], + }, + ###################### + # standard instances # + ###################### + "block_tree_tree": { + "out_path": "standard/block_tree_tree.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + {"anchor_epoch": 0, "number_of_epochs": 6, "number_of_links": 4}, + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 5}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 6, "max_children": 4, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 7, "max_children": 2, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 358}, + ), + ], + }, + "block_tree_tree_2": { + "out_path": "standard/block_tree_tree_2.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + {"anchor_epoch": 0, "number_of_epochs": 6, "number_of_links": 4}, + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 5}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 6, "max_children": 4, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 7, "max_children": 3, "number_of_solutions": None}, + ), + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 16, "max_children": 2, "number_of_solutions": 3101}, + ), + ], + }, + "block_tree_other": { + "out_path": "standard/block_tree_other.yaml", + "models": ["sm_link", "block_tree"], + "params": [ + ( + [{"sm_links": [[0, 1], [0, 2], [2, 3], [3, 4]]}], + {"number_of_blocks": 12, "max_children": 2, "number_of_solutions": 8}, + ), + ], + }, + "block_cover": { + "out_path": "standard/block_cover.yaml", + "models": ["block_cover"], + "params": [ + ({"anchor_epoch": 0, "number_of_solutions": 5},), + ({"anchor_epoch": 2, "number_of_solutions": 5},), + ], + }, +} + + +if __name__ == "__main__": + yaml = YAML(typ="safe") + sm_links = [] + + for model_name, parameters in gen_params.items(): + print(f"processing {model_name}") + out_path = path.join(base_dir, parameters["out_path"]) + models = parameters["models"] + solutions = [] + for params in parameters["params"]: + model_solutions = [] + for model, mod_params in zip(models, params): + print(f" model: {model}") + print(f" parameters: {mod_params}") + if isinstance(mod_params, list): + model_solutions.append(mod_params) + elif isinstance(mod_params, dict): + if model == "sm_link": + model_solutions.append(list(generate_sm_links(mod_params))) + elif model == "block_tree": + model_solutions.append(list(generate_block_tree(mod_params))) + elif model == "block_cover": + model_solutions.append(list(generate_block_cover(mod_params))) + else: + print("todo", model, mod_params) + else: + assert False + results = [merge(*sol) for sol in product(*model_solutions)] + solutions.extend(results) + with open(out_path, "w") as f: + yaml.dump(solutions, f) diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/block_cover.py b/tests/generators/compliance_runners/fork_choice/instantiators/block_cover.py new file mode 100644 index 0000000000..9b7dd61106 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/block_cover.py @@ -0,0 +1,330 @@ +import random + +from eth2spec.test.helpers.state import ( + next_slot, + transition_to, +) +from eth2spec.utils import bls + +from .helpers import ( + advance_state_to_anchor_epoch, + attest_to_slot, + BranchTip, + FCTestData, + produce_block, + ProtocolMessage, +) + + +def _should_justify_epoch(parents, current_justifications, previous_justifications, block) -> bool: + if current_justifications[block]: + return True + + # Check if any child of the block justifies the epoch + for c in (b for b, p in enumerate(parents) if p == block): + if previous_justifications[c]: + return True + + return False + + +def _generate_filter_block_tree( + spec, + genesis_state, + block_epochs, + parents, + previous_justifications, + current_justifications, + rnd: random.Random, + debug, +) -> ([], []): + JUSTIFYING_SLOT = 2 * spec.SLOTS_PER_EPOCH // 3 + 1 + JUSTIFYING_SLOT_COUNT = spec.SLOTS_PER_EPOCH - JUSTIFYING_SLOT + + anchor_epoch = block_epochs[0] + + # Run constraint checks before starting to generate blocks + for epoch in range(anchor_epoch + 1, max(block_epochs) + 1): + current_blocks = [i for i, e in enumerate(block_epochs) if e == epoch] + assert len(current_blocks) <= spec.SLOTS_PER_EPOCH, ( + "Number of blocks does not fit into an epoch=" + str(epoch) + ) + + justifying_blocks = [ + b + for b in current_blocks + if _should_justify_epoch(parents, current_justifications, previous_justifications, b) + ] + + # There should be enough slots to propose all blocks that are required to justify the epoch + assert len(justifying_blocks) <= JUSTIFYING_SLOT_COUNT, ( + "Not enough slots to accommodate blocks justifying epoch=" + str(epoch) + ) + + signed_blocks, anchor_tip = advance_state_to_anchor_epoch( + spec, genesis_state, anchor_epoch, debug + ) + + block_tips = [None for _ in range(0, len(block_epochs))] + # Initialize with the anchor block + block_tips[0] = anchor_tip + + for epoch in range(anchor_epoch + 1, max(block_epochs) + 1): + current_blocks = [i for i, e in enumerate(block_epochs) if e == epoch] + if len(current_blocks) == 0: + continue + + # Case 2. Blocks are from disjoint subtrees -- not supported yet + assert len(set([a for i, a in enumerate(parents) if i in current_blocks])) == 1, ( + "Disjoint trees are not supported" + ) + + # Case 1. Blocks have common ancestor + a = parents[current_blocks[0]] + ancestor_tip = block_tips[a].copy() + + state = ancestor_tip.beacon_state + attestations = ancestor_tip.attestations + + justifying_blocks = [ + b + for b in current_blocks + if _should_justify_epoch(parents, current_justifications, previous_justifications, b) + ] + + common_prefix_len = min(JUSTIFYING_SLOT, spec.SLOTS_PER_EPOCH - len(current_blocks)) + threshold_slot = spec.compute_start_slot_at_epoch(epoch) + common_prefix_len + + # Build the chain up to but excluding a block that will justify current checkpoint + while state.slot < threshold_slot: + # Do not include attestations into blocks + if state.slot < spec.compute_start_slot_at_epoch(epoch): + new_block, state, _, _ = produce_block(spec, state, []) + signed_blocks.append(new_block) + else: + # Prevent previous epoch from being accidentally justified + # by filtering out previous epoch attestations + curr_epoch_attestations = [ + a for a in attestations if epoch == spec.compute_epoch_at_slot(a.data.slot) + ] + other_attestations = [a for a in attestations if a not in curr_epoch_attestations] + new_block, state, curr_epoch_attestations, _ = produce_block( + spec, state, curr_epoch_attestations + ) + attestations = other_attestations + curr_epoch_attestations + signed_blocks.append(new_block) + + # Attest + curr_slot_attestations = attest_to_slot(spec, state, state.slot) + attestations = curr_slot_attestations + attestations + + # Next slot + next_slot(spec, state) + + common_state = state + + # Assumption: one block is enough to satisfy previous_justifications[b] and current_justifications[b], + # i.e. block capacity is enough to accommodate attestations to justify previous and current epoch checkpoints + # if that needed. Considering that most of attestations were already included into the common chain prefix, + # we assume it is possible + empty_slot_count = spec.SLOTS_PER_EPOCH - common_prefix_len - len(current_blocks) + block_distribution = current_blocks.copy() + [-1 for _ in range(0, empty_slot_count)] + + # Randomly distribute blocks across slots + rnd.shuffle(block_distribution) + + # Move all blocks that require to justify current epoch to the end to increase the chance of justification + block_distribution = [b for b in block_distribution if b not in justifying_blocks] + block_distribution = block_distribution + justifying_blocks + + for index, block in enumerate(block_distribution): + slot = threshold_slot + index + state = common_state.copy() + + # Advance state to the slot + if state.slot < slot: + transition_to(spec, state, slot) + + # Propose a block if slot isn't empty + block_attestations = [] + if block > -1: + previous_epoch_attestations = [ + a for a in attestations if epoch == spec.compute_epoch_at_slot(a.data.slot) + 1 + ] + current_epoch_attestations = [ + a for a in attestations if epoch == spec.compute_epoch_at_slot(a.data.slot) + ] + if previous_justifications[block]: + block_attestations = block_attestations + previous_epoch_attestations + if current_justifications[block]: + block_attestations = block_attestations + current_epoch_attestations + + # Propose block + new_block, state, _, _ = produce_block(spec, state, block_attestations) + signed_blocks.append(new_block) + + # Attest + # TODO pick a random tip to make attestation with if the slot is empty + curr_slot_attestations = attest_to_slot(spec, state, state.slot) + attestations = curr_slot_attestations + attestations + + # Next slot + next_slot(spec, state) + + if block > -1: + not_included_attestations = [a for a in attestations if a not in block_attestations] + + check_up_state = state.copy() + spec.process_justification_and_finalization(check_up_state) + + if current_justifications[block]: + assert check_up_state.current_justified_checkpoint.epoch == epoch, ( + "Unexpected current_jusitified_checkpoint.epoch: " + + str(check_up_state.current_justified_checkpoint.epoch) + + " != " + + str(epoch) + ) + elif previous_justifications[block]: + assert check_up_state.current_justified_checkpoint.epoch + 1 == epoch, ( + "Unexpected current_jusitified_checkpoint.epoch: " + + str(check_up_state.current_justified_checkpoint.epoch) + + " != " + + str(epoch - 1) + ) + + block_tips[block] = BranchTip( + state, + not_included_attestations, + [*range(0, len(state.validators))], + check_up_state.current_justified_checkpoint, + ) + + return signed_blocks, block_tips + + +def gen_block_cover_test_data(spec, state, model_params, debug, seed) -> (FCTestData, object): + anchor_state = state + anchor_block = spec.BeaconBlock(state_root=anchor_state.hash_tree_root()) + + if debug: + print("\nseed:", seed) + print("model_params:", str(model_params)) + + block_epochs = model_params["block_epochs"] + parents = model_params["parents"] + previous_justifications = model_params["previous_justifications"] + current_justifications = model_params["current_justifications"] + + store_justified_epoch = model_params["store_justified_epoch"] + target_block = model_params["target_block"] + + # Ensure that there is no attempt to justify GENESIS_EPOCH + 1 as it is not supported by the protocol + assert store_justified_epoch != spec.GENESIS_EPOCH + 1, ( + "Justification of epoch 1 is not supported by the protocol" + ) + + # Ensure that epoch(block) == epoch(parent) + 1 + for b in range(1, len(block_epochs)): + assert block_epochs[b] == block_epochs[parents[b]] + 1, ( + "epoch(" + + str(b) + + ") != epoch(" + + str(parents[b]) + + ") + 1, block_epochs=" + + str(block_epochs) + + ", parents=" + + str(parents) + ) + + # Ensure that a descendant doesn't attempt to justify the previous epoch checkpoint + # if it has already been justified by its ancestor + for b in range(1, len(block_epochs)): + if previous_justifications[b]: + a = parents[b] + assert not current_justifications[a], ( + str(b) + " attempts to justify already justified epoch" + ) + + rnd = random.Random(seed) + signed_blocks, post_block_tips = _generate_filter_block_tree( + spec, + state, + block_epochs, + parents, + previous_justifications, + current_justifications, + rnd, + debug, + ) + + # Meta data + meta = { + "seed": seed, + "model_params": model_params, + "bls_setting": 0 if bls.bls_active else 2, + } + + blocks = [ProtocolMessage(block) for block in signed_blocks] + + current_epoch_slot = spec.compute_start_slot_at_epoch(model_params["current_epoch"]) + current_epoch_time = state.genesis_time + current_epoch_slot * spec.config.SECONDS_PER_SLOT + + test_data = FCTestData( + meta, anchor_block, anchor_state, blocks, store_final_time=current_epoch_time + ) + target_block_root = spec.hash_tree_root( + post_block_tips[target_block].beacon_state.latest_block_header + ) + + return test_data, target_block_root + + +def run_sanity_checks(spec, store, model_params, target_block_root): + current_epoch = spec.get_current_store_epoch(store) + # Ensure the epoch is correct + assert current_epoch == model_params["current_epoch"], ( + str(current_epoch) + " != " + str(model_params["current_epoch"]) + ) + # Ensure the store.justified_checkpoint.epoch is as expected + assert store.justified_checkpoint.epoch == model_params["store_justified_epoch"] + + # Check predicates + predicates = model_params["predicates"] + if predicates["store_je_eq_zero"]: + assert store.justified_checkpoint.epoch == spec.GENESIS_EPOCH, ( + "store_je_eq_zero not satisfied" + ) + + if predicates["block_is_leaf"]: + assert not any(b for b in store.blocks.values() if b.parent_root == target_block_root), ( + "block_is_leaf not satisfied" + ) + else: + assert any(b for b in store.blocks.values() if b.parent_root == target_block_root), ( + "block_is_leaf not satisfied" + ) + + voting_source = spec.get_voting_source(store, target_block_root) + if predicates["block_vse_eq_store_je"]: + assert voting_source.epoch == store.justified_checkpoint.epoch, ( + "block_vse_eq_store_je not satisfied" + ) + else: + assert voting_source.epoch != store.justified_checkpoint.epoch, ( + "block_vse_eq_store_je not satisfied" + ) + + if predicates["block_vse_plus_two_ge_curr_e"]: + assert voting_source.epoch + 2 >= current_epoch, ( + "block_vse_plus_two_ge_curr_e not satisfied" + ) + else: + assert voting_source.epoch + 2 < current_epoch, "block_vse_plus_two_ge_curr_e not satisfied" + + # Ensure the target block is in filtered blocks if it is a leaf and eligible + if predicates["block_is_leaf"] and ( + predicates["store_je_eq_zero"] + or predicates["block_vse_eq_store_je"] + or predicates["block_vse_plus_two_ge_curr_e"] + ): + assert target_block_root in spec.get_filtered_block_tree(store).keys() diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/block_tree.py b/tests/generators/compliance_runners/fork_choice/instantiators/block_tree.py new file mode 100644 index 0000000000..4b79cf4dd2 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/block_tree.py @@ -0,0 +1,706 @@ +import random + +from eth2spec.test.helpers.attester_slashings import ( + get_valid_attester_slashing_by_indices, +) +from eth2spec.test.helpers.state import ( + next_slot, + transition_to, +) +from eth2spec.utils import bls + +from .debug_helpers import ( + attesters_in_block, + print_block_tree, + print_epoch, +) +from .helpers import ( + advance_branch_to_next_epoch, + advance_state_to_anchor_epoch, + attest_to_slot, + BranchTip, + FCTestData, + produce_block, + ProtocolMessage, +) + +MAX_JUSTIFICATION_RATE = 99 +MIN_JUSTIFICATION_RATE = 91 + +MAX_UNDERJUSTIFICATION_RATE = 65 +MIN_UNDERJUSTIFICATION_RATE = 55 + +EMPTY_SLOTS_RATE = 3 +MAX_TIPS_TO_ATTEST = 2 + +OFF_CHAIN_ATTESTATION_RATE = 10 +ON_CHAIN_ATTESTATION_RATE = 20 + +MAX_ATTESTER_SLASHINGS = 8 +ATTESTER_SLASHINGS_RATE = 8 +OFF_CHAIN_SLASHING_RATE = 33 +ON_CHAIN_SLASHING_RATE = 33 + +INVALID_MESSAGES_RATE = 5 + + +class SmLink(tuple): + @property + def source(self): + return self[0] + + @property + def target(self): + return self[1] + + +def _justifying_participation_rate(rnd: random.Random): + """ + Should be high enough to ensure justification happens + """ + return rnd.randint(MIN_JUSTIFICATION_RATE, MAX_JUSTIFICATION_RATE) + + +def _under_justifying_participation_rate(rnd: random.Random): + return rnd.randint(MIN_UNDERJUSTIFICATION_RATE, MAX_UNDERJUSTIFICATION_RATE) + + +def _create_new_branch_tip(spec, branch_tips: dict[SmLink:BranchTip], sm_link: SmLink) -> BranchTip: + """ + Initialized a branch tip state for a new branch satisfying the given sm_link. + :return: a new branch tip. + """ + + # Find all forks with justified source + tips_with_justified_source = [ + s for s in branch_tips.values() if s.eventually_justified_checkpoint.epoch == sm_link.source + ] + assert len(tips_with_justified_source) > 0 + + # Find and return the most adanced one + most_recent_tip = max(tips_with_justified_source, key=lambda s: s.beacon_state.slot) + return BranchTip( + most_recent_tip.beacon_state.copy(), + most_recent_tip.attestations.copy(), + [], + most_recent_tip.eventually_justified_checkpoint, + ) + + +def _sample_validator_partition(spec, state, epoch, participation_rate, rnd): + active_validator_indices = spec.get_active_validator_indices(state, epoch) + participants_count = len(active_validator_indices) * participation_rate // 100 + return rnd.sample(active_validator_indices, participants_count) + + +def _compute_validator_partitions( + spec, branch_tips, current_links, current_epoch, rnd: random.Random +) -> dict[SmLink, list[int]]: + """ + Note: O(N) complex (N is a number of validators) and might be inefficient with large validator sets + + Uniformly distributes active validators between active forks specified by a given set of sm_links. + Handles two cases: + 1. Single active fork: + Randomly sample a single validator partition taking into account + whether the fork should have a justified checkpoint in the current epoch. + 2. Multiple active forks: + i. sample the majority partition if one of the forks is about to justify during the current epoch, + ii. run through a set of active validators and randomly select a fork for it, + do no consider validators that were sampled into the majority partition. + + Does not take into account validator's effective balance, based on assumption that the EB of every validator + is nearly the same. + + :return: [SmLink: participants] + """ + + justifying_links = [l for l in current_links if l.target == current_epoch] + + # Justifying conflicting checkpoints isn't supported + assert len(justifying_links) < 2 + justifying_link = justifying_links[0] if any(justifying_links) else None + + # Sanity check + for sm_link in current_links: + assert spec.get_current_epoch(branch_tips[sm_link].beacon_state) == current_epoch + + # Case when there is just one active fork + if len(current_links) == 1: + the_sm_link = current_links[0] + + if the_sm_link == justifying_link: + participation_rate = _justifying_participation_rate(rnd) + else: + participation_rate = _under_justifying_participation_rate(rnd) + + state = branch_tips[the_sm_link].beacon_state + participants = _sample_validator_partition( + spec, state, current_epoch, participation_rate, rnd + ) + + return {the_sm_link: participants} + + # Cases with more than one active fork + participants = {l: [] for l in current_links} + + # Move the majority to the branch containing justification target + justifying_participants = [] + if justifying_link is not None: + state = branch_tips[justifying_link].beacon_state + justifying_participants = _sample_validator_partition( + spec, + branch_tips[justifying_link].beacon_state, + current_epoch, + _justifying_participation_rate(rnd), + rnd, + ) + + participants[justifying_link] = justifying_participants + + # Collect a set of active validator indexes across all forks + active_validator_per_branch = {} + all_active_validators = set() + for l in current_links: + state = branch_tips[l].beacon_state + active_validator_per_branch[l] = spec.get_active_validator_indices(state, current_epoch) + all_active_validators.update(active_validator_per_branch[l]) + + # Remove validators selected for justifying branch from the pool of active participants + all_active_validators = all_active_validators.difference(justifying_participants) + + # For each index: + # 1) Collect a set of branches where the validators is in active state (except for justifying branch) + # 2) Append the index to the list of participants for a randomly selected branch + for index in all_active_validators: + active_branches = [ + l + for l in current_links + if index in active_validator_per_branch[l] and l not in justifying_links + ] + participants[tuple(rnd.choice(active_branches))].append(index) + + return participants + + +def _any_change_to_validator_partitions(spec, sm_links, current_epoch, anchor_epoch) -> bool: + """ + Returns ``true`` if validator partitions should be re-shuffled to advance branches in the current epoch: + 1. The first epoch after the anchor epoch always requires a new shuffling. + 2. Previous epoch has justified checkpoints. + Therefore, new supermajority links may need to be processed during the current epoch, + thus new block tree branches with new validator partitions may need to be created. + 3. Current epoch has justified checkpoint. + Therefore, the majority of validators must be moved to the justifying branch. + """ + assert current_epoch > anchor_epoch + + previous_epoch = current_epoch - 1 + + if previous_epoch == anchor_epoch: + return True + + for l in sm_links: + if l.target == current_epoch or l.target == previous_epoch: + return True + + return False + + +def _generate_sm_link_tree( + spec, genesis_state, sm_links, rnd: random.Random, debug +) -> ([], BranchTip): + """ + Generates a sequence of blocks satisfying a tree of supermajority links specified in the sm_links list, + i.e. a sequence of blocks with attestations required to create given supermajority links. + + The block generation strategy is to run through a span of epochs covered by the supermajority links + and for each epoch of the span apply the following steps: + 1. Obtain a list of supermajority links covering the epoch. + 2. Create a new block tree branch (fork) for every newly observed supermajority link. + 3. Randomly sample all active validators between a set of forks that are being advanced in the epoch. + Validator partitions are disjoint and are changing only at the epoch boundary. + If no new branches are created in the current epoch then partitions from the previous epoch will be used + to advahce the state of every fork to the next epoch. + 4. Advance every fork to the next epoch respecting a validator partition assigned to it in the current epoch. + Preserve attestations produced but not yet included on chain for potential inclusion in the next epoch. + 5. Justify required checkpoints by moving the majority of validators to the justifying fork, + this is taken into account by step (3). + + :return: Sequence of signed blocks oredered by a slot number. + """ + assert any(sm_links) + + # Find anchor epoch + anchor_epoch = min(sm_links, key=lambda l: l.source).source + + signed_blocks, anchor_tip = advance_state_to_anchor_epoch( + spec, genesis_state, anchor_epoch, debug + ) + + # branch_tips hold the most recent state, validator partition and not included attestations for every fork + # Initialize branch tips with the anchor tip + anchor_link = SmLink((spec.GENESIS_EPOCH, anchor_epoch)) + branch_tips = {anchor_link: anchor_tip} + + highest_target_sm_link = max(sm_links, key=lambda l: l.target) + + # Finish at after the highest justified checkpoint + for current_epoch in range(anchor_epoch + 1, highest_target_sm_link.target + 1): + # Obtain sm links that span over the current epoch + current_epoch_sm_links = [l for l in sm_links if l.source < current_epoch <= l.target] + + # Initialize new forks + for l in (l for l in current_epoch_sm_links if branch_tips.get(l) is None): + new_branch_tip = _create_new_branch_tip(spec, branch_tips, l) + # Abort the test if any sm_links constraint appears to be unreachable + # because the justification of the source checkpoint hasn't been realized on chain yet + if ( + l.target == current_epoch + and new_branch_tip.beacon_state.current_justified_checkpoint.epoch < l.source + ): + return [], new_branch_tip + + branch_tips[l] = new_branch_tip + + # Reshuffle partitions if needed + if _any_change_to_validator_partitions(spec, sm_links, current_epoch, anchor_epoch): + partitions = _compute_validator_partitions( + spec, branch_tips, current_epoch_sm_links, current_epoch, rnd + ) + for l in partitions.keys(): + old_tip_state = branch_tips[l] + new_tip_state = BranchTip( + old_tip_state.beacon_state, + old_tip_state.attestations, + partitions[l], + old_tip_state.eventually_justified_checkpoint, + ) + branch_tips[l] = new_tip_state + + # Debug checks + if debug: + print("\nepoch", str(current_epoch) + ":") + # Partitions are disjoint + for l1 in current_epoch_sm_links: + l1_participants = branch_tips[l1].participants + for l2 in current_epoch_sm_links: + if l1 != l2: + l2_participants = branch_tips[l2].participants + intersection = set(l1_participants).intersection(l2_participants) + assert len(intersection) == 0, ( + str(l1) + + " and " + + str(l2) + + " has common participants: " + + str(intersection) + ) + + # Advance every branch taking into account attestations from past epochs and voting partitions + for sm_link in current_epoch_sm_links: + branch_tip = branch_tips[sm_link] + assert spec.get_current_epoch(branch_tip.beacon_state) == current_epoch, ( + "Unexpected current_epoch(branch_tip.beacon_state): " + + str(spec.get_current_epoch(branch_tip.beacon_state)) + + " != " + + str(current_epoch) + ) + new_signed_blocks, new_branch_tip = advance_branch_to_next_epoch(spec, branch_tip) + + # Run sanity checks + post_state = new_branch_tip.beacon_state + assert spec.get_current_epoch(post_state) == current_epoch + 1, ( + "Unexpected post_state epoch: " + + str(spec.get_current_epoch(post_state)) + + " != " + + str(current_epoch + 1) + ) + if sm_link.target == current_epoch: + assert post_state.previous_justified_checkpoint.epoch == sm_link.source, ( + "Unexpected previous_justified_checkpoint.epoch: " + + str(post_state.previous_justified_checkpoint.epoch) + + " != " + + str(sm_link.source) + ) + assert new_branch_tip.eventually_justified_checkpoint.epoch == sm_link.target, ( + "Unexpected eventually_justified_checkpoint.epoch: " + + str(new_branch_tip.eventually_justified_checkpoint.epoch) + + " != " + + str(sm_link.target) + ) + elif sm_link.source != new_branch_tip.eventually_justified_checkpoint.epoch: + # Abort the test as the justification of the source checkpoint can't be realized on chain + # because of the lack of the block space + return [], new_branch_tip + + # If the fork won't be advanced in the future epochs + # ensure 1) all yet not included attestations are included on chain by advancing it to epoch N+1 + # 2) justification is realized by advancing it to epoch N+2 + is_fork_advanced_in_future = any(l for l in sm_links if l.source == sm_link.target) + if sm_link.target == current_epoch and not is_fork_advanced_in_future: + advanced_branch_tip = new_branch_tip + + # Advance to N+1 if state.current_justified_checkpoint.epoch < eventually_justified_checkpoint.epoch + current_justified_epoch = ( + new_branch_tip.beacon_state.current_justified_checkpoint.epoch + ) + eventually_justified_epoch = new_branch_tip.eventually_justified_checkpoint.epoch + if current_justified_epoch < eventually_justified_epoch: + advanced_signed_blocks, advanced_branch_tip = advance_branch_to_next_epoch( + spec, new_branch_tip, enable_attesting=False + ) + new_signed_blocks = new_signed_blocks + advanced_signed_blocks + + # Build a block in the next epoch to justify the target on chain + state = advanced_branch_tip.beacon_state + while spec.get_beacon_proposer_index(state) not in advanced_branch_tip.participants: + next_slot(spec, state) + + tip_block, _, _, _ = produce_block(spec, state, []) + new_signed_blocks.append(tip_block) + + assert state.current_justified_checkpoint.epoch == sm_link.target, ( + "Unexpected state.current_justified_checkpoint: " + + str(state.current_justified_checkpoint.epoch) + + " != " + + str(sm_link.target) + ) + + # Debug output + if debug: + print( + "branch" + str(sm_link) + ":", + print_epoch(spec, branch_tips[sm_link].beacon_state, new_signed_blocks), + ) + print( + " ", + len(branch_tips[sm_link].participants), + "participants:", + new_branch_tip.participants, + ) + print( + " ", + "state.current_justified_checkpoint:", + "(epoch=" + + str(post_state.current_justified_checkpoint.epoch) + + ", root=" + + str(post_state.current_justified_checkpoint.root)[:6] + + ")", + ) + print( + " ", + "eventually_justified_checkpoint:", + "(epoch=" + + str(new_branch_tip.eventually_justified_checkpoint.epoch) + + ", root=" + + str(new_branch_tip.eventually_justified_checkpoint.root)[:6] + + ")", + ) + + # Debug checks + if debug: + # Proposers are aligned with the partition + unexpected_proposers = [ + b.message.proposer_index + for b in new_signed_blocks + if b.message.proposer_index not in branch_tip.participants + ] + assert len(unexpected_proposers) == 0, "Unexpected proposer: " + str( + unexpected_proposers[0] + ) + + # Attesters are aligned with the partition + current_epoch_state = branch_tips[sm_link].beacon_state + for b in new_signed_blocks: + # Attesting indexes from on chain attestations + attesters = attesters_in_block(spec, current_epoch_state, b, current_epoch) + # Attesting indexes from not yet included attestations + for a in new_branch_tip.attestations: + if a.data.target.epoch == current_epoch: + attesters.update( + spec.get_attesting_indices( + current_epoch_state, a.data, a.aggregation_bits + ) + ) + unexpected_attesters = attesters.difference(branch_tip.participants) + assert len(unexpected_attesters) == 0, ( + "Unexpected attester: " + + str(unexpected_attesters.pop()) + + ", slot " + + str(b.message.slot) + ) + + # Store the result + branch_tips[sm_link] = new_branch_tip + signed_blocks = signed_blocks + new_signed_blocks + + # Sort blocks by a slot + signed_block_messages = [ProtocolMessage(b) for b in signed_blocks] + return ( + sorted(signed_block_messages, key=lambda b: b.payload.message.slot), + branch_tips[highest_target_sm_link], + ) + + +def _spoil_block(spec, rnd: random.Random, signed_block): + signed_block.message.state_root = spec.Root(rnd.randbytes(32)) + + +def _spoil_attester_slashing(spec, rnd: random.Random, attester_slashing): + attester_slashing.attestation_2.data = attester_slashing.attestation_1.data + + +def _spoil_attestation(spec, rnd: random.Random, attestation): + attestation.data.target.epoch = spec.GENESIS_EPOCH + + +def _generate_block_tree( + spec, + anchor_tip: BranchTip, + rnd: random.Random, + debug, + block_parents, + with_attester_slashings, + with_invalid_messages, +) -> ([], [], []): + in_block_attestations = anchor_tip.attestations.copy() + post_states = [anchor_tip.beacon_state.copy()] + current_slot = anchor_tip.beacon_state.slot + block_index = 1 + block_tree_tips = set([0]) + in_block_attester_slashings = [] + attester_slashings_count = 0 + out_of_block_attestation_messages = [] + out_of_block_attester_slashing_messages = [] + signed_block_messages = [] + + while block_index < len(block_parents): + # Propose a block if slot shouldn't be empty + if rnd.randint(1, 100) > EMPTY_SLOTS_RATE: + # Advance parent state to the current slot + parent_index = block_parents[block_index] + parent_state = post_states[parent_index].copy() + transition_to(spec, parent_state, current_slot) + + # Filter out non-viable attestations + in_block_attestations = [ + a + for a in in_block_attestations + if parent_state.slot <= a.data.slot + spec.SLOTS_PER_EPOCH + ] + + # Produce block + proposer = spec.get_beacon_proposer_index(parent_state) + if parent_state.validators[proposer].slashed or ( + with_invalid_messages and rnd.randint(0, 99) < INVALID_MESSAGES_RATE + ): + # Do not include attestations and slashings into invalid block + # as clients may opt in to process or not process attestations contained by invalid block + signed_block, _, _, _ = produce_block(spec, parent_state, [], []) + _spoil_block(spec, rnd, signed_block) + signed_block_messages.append(ProtocolMessage(signed_block, False)) + # Append the parent state as the post state as if the block were not applied + post_states.append(parent_state) + else: + signed_block, post_state, in_block_attestations, in_block_attester_slashings = ( + produce_block( + spec, parent_state, in_block_attestations, in_block_attester_slashings + ) + ) + + # Valid block + signed_block_messages.append(ProtocolMessage(signed_block, True)) + post_states.append(post_state) + + # Update tips + block_tree_tips.discard(parent_index) + block_tree_tips.add(block_index) + + # Next block + block_index += 1 + + # Attest to randomly selected tips + def split_list(lst, n): + k, m = divmod(len(lst), n) + return [lst[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)] + + attesting_tips = rnd.sample( + sorted(block_tree_tips), min(len(block_tree_tips), MAX_TIPS_TO_ATTEST) + ) + validator_count = len(post_states[attesting_tips[0]].validators) + attesters = split_list([i for i in range(validator_count)], len(attesting_tips)) + for index, attesting_block_index in enumerate(attesting_tips): + # Advance the state to the current slot + attesting_state = post_states[attesting_block_index] + transition_to(spec, attesting_state, current_slot) + + # Attest to the block + attestations_in_slot = attest_to_slot( + spec, + attesting_state, + attesting_state.slot, + lambda comm: [i for i in comm if i in attesters[index]], + ) + + # Sample on chain and off chain attestations + for a in attestations_in_slot: + choice = rnd.randint(0, 99) + if choice < OFF_CHAIN_ATTESTATION_RATE: + if with_invalid_messages and rnd.randint(0, 99) < INVALID_MESSAGES_RATE: + _spoil_attestation(spec, rnd, a) + attestation_message = ProtocolMessage(a, False) + else: + attestation_message = ProtocolMessage(a, True) + out_of_block_attestation_messages.append(attestation_message) + elif choice < OFF_CHAIN_ATTESTATION_RATE + ON_CHAIN_ATTESTATION_RATE: + in_block_attestations.insert(0, a) + else: + out_of_block_attestation_messages.append(ProtocolMessage(a, True)) + in_block_attestations.insert(0, a) + + # Create attester slashing + if with_attester_slashings and attester_slashings_count < MAX_ATTESTER_SLASHINGS: + if rnd.randint(0, 99) < ATTESTER_SLASHINGS_RATE: + state = post_states[attesting_tips[0]] + indices = [rnd.randint(0, len(state.validators) - 1)] + attester_slashing = get_valid_attester_slashing_by_indices( + spec, state, indices, slot=current_slot, signed_1=True, signed_2=True + ) + + choice = rnd.randint(0, 99) + if choice < OFF_CHAIN_SLASHING_RATE: + if with_invalid_messages and rnd.randint(0, 99) < INVALID_MESSAGES_RATE: + _spoil_attester_slashing(spec, rnd, attester_slashing) + attester_slashing_message = ProtocolMessage(attester_slashing, False) + else: + attester_slashing_message = ProtocolMessage(attester_slashing, True) + out_of_block_attester_slashing_messages.append(attester_slashing_message) + elif choice < OFF_CHAIN_SLASHING_RATE + ON_CHAIN_SLASHING_RATE: + in_block_attester_slashings.append(attester_slashing) + else: + out_of_block_attester_slashing_messages.append( + ProtocolMessage(attester_slashing, True) + ) + in_block_attester_slashings.append(attester_slashing) + + attester_slashings_count += 1 + + # Next slot + current_slot += 1 + + if debug: + print("\nblock_tree:") + print( + "blocks: ", + print_block_tree(spec, post_states[0], [b.payload for b in signed_block_messages]), + ) + print( + " ", + "state.current_justified_checkpoint:", + "(epoch=" + + str(post_states[len(post_states) - 1].current_justified_checkpoint.epoch) + + ", root=" + + str(post_states[len(post_states) - 1].current_justified_checkpoint.root)[:6] + + ")", + ) + + print("on_block:") + print(" ", "count =", len(signed_block_messages)) + print(" ", "valid =", len([b for b in signed_block_messages if b.valid])) + print("on_attestation:") + print(" ", "count =", len(out_of_block_attestation_messages)) + print( + " ", + "valid =", + len([a for a in out_of_block_attestation_messages if a.valid]), + ) + print("on_attester_slashing:") + print(" ", "count =", len(out_of_block_attester_slashing_messages)) + print( + " ", + "valid =", + len([s for s in out_of_block_attester_slashing_messages if s.valid]), + ) + + return ( + sorted(signed_block_messages, key=lambda b: b.payload.message.slot), + sorted(out_of_block_attestation_messages, key=lambda a: a.payload.data.slot), + sorted( + out_of_block_attester_slashing_messages, key=lambda a: a.payload.attestation_1.data.slot + ), + ) + + +def gen_block_tree_test_data( + spec, + state, + debug, + seed, + sm_links, + block_parents, + with_attester_slashings, + with_invalid_messages, +) -> FCTestData: + assert (1, 2) not in sm_links, "(1, 2) sm link is not supported due to unsatisfiability" + sm_links = [SmLink(l) for l in sm_links] + + anchor_state = state + anchor_block = spec.BeaconBlock(state_root=anchor_state.hash_tree_root()) + + # Find a reachable solution trying with different seeds if needed + # sm_links constraints may not have a solution because of the randomization affecting validator partitions + signed_block_messages = [] + highest_tip = BranchTip(state, [], [], state.current_justified_checkpoint) + while True: + if debug: + print("\nseed:", seed) + print("sm_links:", sm_links) + print("block_parents:", block_parents) + + rnd = random.Random(seed) + signed_block_messages, highest_tip = _generate_sm_link_tree( + spec, state, sm_links, rnd, debug + ) + if len(signed_block_messages) > 0: + break + + new_seed = rnd.randint(1, 10000) + if debug: + print( + "\nUnsatisfiable constraints: sm_links: " + + str(sm_links) + + ", seed=" + + str(seed) + + ", will retry with seed=" + + str(new_seed) + ) + seed = new_seed + + # Block tree model + block_tree, attestation_messages, attester_slashing_messages = _generate_block_tree( + spec, highest_tip, rnd, debug, block_parents, with_attester_slashings, with_invalid_messages + ) + + # Merge block_tree and sm_link_tree blocks + block_tree_root_slot = block_tree[0].payload.message.slot + signed_block_messages = [ + b for b in signed_block_messages if b.payload.message.slot < block_tree_root_slot + ] + signed_block_messages = signed_block_messages + block_tree + + # Meta data + meta = { + "seed": seed, + "sm_links": str(sm_links), + "block_parents": str(block_parents), + "bls_setting": 0 if bls.bls_active else 2, + } + + return FCTestData( + meta, + anchor_block, + anchor_state, + signed_block_messages, + attestation_messages, + attester_slashing_messages, + ) diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/debug_helpers.py b/tests/generators/compliance_runners/fork_choice/instantiators/debug_helpers.py new file mode 100644 index 0000000000..d7cfafd740 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/debug_helpers.py @@ -0,0 +1,94 @@ +from eth2spec.test.helpers.state import ( + transition_to, +) + + +def attesters_in_block(spec, epoch_state, signed_block, target_epoch): + block = signed_block.message + attesters = set() + for a in block.body.attestations: + if a.data.target.epoch == target_epoch: + attesters.update(spec.get_attesting_indices(epoch_state, a.data, a.aggregation_bits)) + return attesters + + +def print_block(spec, epoch_state, signed_block): + block = signed_block.message + if spec.get_current_epoch(epoch_state) > spec.GENESIS_EPOCH: + prev_attesters = attesters_in_block( + spec, epoch_state, signed_block, spec.get_previous_epoch(epoch_state) + ) + else: + prev_attesters = set() + + curr_attesters = attesters_in_block( + spec, epoch_state, signed_block, spec.get_current_epoch(epoch_state) + ) + prev_attester_str = "a_prev=" + str(prev_attesters) if any(prev_attesters) else "a_prev={}" + curr_attester_str = "a_curr=" + str(curr_attesters) if any(curr_attesters) else "a_curr={}" + + return ( + "b(r=" + + str(spec.hash_tree_root(block))[:6] + + ", p=" + + str(block.proposer_index) + + ", " + + prev_attester_str + + ", " + + curr_attester_str + + ")" + ) + + +def print_slot_range(spec, root_state, signed_blocks, start_slot, end_slot): + ret = "" + epoch_state = root_state.copy() + for slot in range(start_slot, end_slot): + transition_to(spec, epoch_state, slot) + blocks_in_slot = [b for b in signed_blocks if b.message.slot == slot] + if ret != "": + ret = ret + " <- " + if any(blocks_in_slot): + ret = ( + ret + + "s(" + + str(slot) + + ", " + + print_block(spec, epoch_state, blocks_in_slot[0]) + + ")" + ) + else: + ret = ret + "s(" + str(slot) + ", _)" + + return ret + + +def print_epoch(spec, epoch_state, signed_blocks): + epoch = spec.get_current_epoch(epoch_state) + start_slot = spec.compute_start_slot_at_epoch(epoch) + return print_slot_range( + spec, epoch_state, signed_blocks, start_slot, start_slot + spec.SLOTS_PER_EPOCH + ) + + +def print_block_tree(spec, root_state, signed_blocks): + start_slot = signed_blocks[0].message.slot + end_slot = signed_blocks[len(signed_blocks) - 1].message.slot + 1 + return print_slot_range(spec, root_state, signed_blocks, start_slot, end_slot) + + +def print_head(spec, store): + head = spec.get_head(store) + weight = spec.get_weight(store, head) + state = store.checkpoint_states[store.justified_checkpoint] + total_active_balance = spec.get_total_active_balance(state) + + return ( + "(slot=" + + str(store.blocks[head].slot) + + ", root=" + + str(head)[:6] + + ", weight=" + + str(weight * 100 // total_active_balance) + + "%)" + ) diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/helpers.py b/tests/generators/compliance_runners/fork_choice/instantiators/helpers.py new file mode 100644 index 0000000000..49bb1e11bb --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/helpers.py @@ -0,0 +1,468 @@ +from dataclasses import dataclass, field + +from eth2spec.test.helpers.attestations import ( + get_valid_attestation, +) +from eth2spec.test.helpers.block import ( + build_empty_block, + sign_block, +) +from eth2spec.test.helpers.fork_choice import ( + add_attestation, + add_attester_slashing, + add_block, + get_attestation_file_name, + get_attester_slashing_file_name, + get_block_file_name, + on_tick_and_append_step, + output_store_checks, + run_on_attestation, + run_on_attester_slashing, + run_on_block, +) +from eth2spec.test.helpers.state import ( + next_slot, +) +from eth2spec.utils.ssz.ssz_typing import View + +from .debug_helpers import print_epoch, print_head + + +@dataclass +class ProtocolMessage: + payload: object + valid: bool = True + + +@dataclass +class FCTestData: + meta: dict + anchor_block: object + anchor_state: object + blocks: list[ProtocolMessage] + atts: list[ProtocolMessage] = field(default_factory=list) + slashings: list[ProtocolMessage] = field(default_factory=list) + store_final_time: int = 0 + + +class BranchTip: + def __init__(self, beacon_state, attestations, participants, eventually_justified_checkpoint): + self.beacon_state = beacon_state + self.attestations = attestations + self.participants = participants + self.eventually_justified_checkpoint = eventually_justified_checkpoint + + def copy(self): + return BranchTip( + self.beacon_state.copy(), + self.attestations.copy(), + self.participants.copy(), + self.eventually_justified_checkpoint, + ) + + +def _get_eligible_attestations(spec, state, attestations) -> []: + def _get_voting_source(target: spec.Checkpoint) -> spec.Checkpoint: + if target.epoch == spec.get_current_epoch(state): + return state.current_justified_checkpoint + else: + return state.previous_justified_checkpoint + + return [ + a + for a in attestations + if state.slot <= a.data.slot + spec.SLOTS_PER_EPOCH + and a.data.source == _get_voting_source(a.data.target) + ] + + +def _compute_pseudo_randao_reveal(spec, proposer_index, epoch): + pseudo_vrn = spec.uint64((proposer_index + 1) * (epoch + 1)) + pseudo_vrn_bytes = spec.uint_to_bytes(pseudo_vrn) + randao_reveal_bytes = bytes(96 - len(pseudo_vrn_bytes)) + pseudo_vrn_bytes + return spec.BLSSignature(randao_reveal_bytes) + + +def produce_block(spec, state, attestations, attester_slashings=[]): + """ + Produces a block including as many attestations as it is possible. + :return: Signed block, the post block state and attestations that were not included into the block. + """ + + # Filter out too old attestastions (TODO relax condition for Deneb) + eligible_attestations = _get_eligible_attestations(spec, state, attestations) + + # Create a block with attestations + block = build_empty_block(spec, state) + block.body.randao_reveal = _compute_pseudo_randao_reveal( + spec, block.proposer_index, spec.get_current_epoch(state) + ) + + # Prepare attestations + limit = type(block.body.attestations).limit() + attestation_in_block = eligible_attestations[:limit] + + for a in attestation_in_block: + block.body.attestations.append(a) + + # Add attester slashings + attester_slashings_in_block = attester_slashings[: spec.MAX_ATTESTER_SLASHINGS] + for s in attester_slashings_in_block: + block.body.attester_slashings.append(s) + + # Run state transition and sign off on a block + post_state = state.copy() + + valid = True + try: + spec.process_block(post_state, block) + except AssertionError: + valid = False + + block.state_root = post_state.hash_tree_root() + signed_block = sign_block(spec, post_state, block) + + # Filter out operations only if the block is valid + not_included_attestations = attestations + not_included_attester_slashings = attester_slashings + if valid: + not_included_attestations = [a for a in attestations if a not in attestation_in_block] + not_included_attester_slashings = [ + s for s in attester_slashings if s not in attester_slashings_in_block + ] + + # Return a pre state if the block is invalid + if not valid: + post_state = state + + return signed_block, post_state, not_included_attestations, not_included_attester_slashings + + +def attest_to_slot(spec, state, slot_to_attest, participants_filter=None) -> []: + """ + Creates attestation is a slot respecting participating validators. + :return: produced attestations + """ + + assert slot_to_attest <= state.slot + + committees_per_slot = spec.get_committee_count_per_slot( + state, spec.compute_epoch_at_slot(slot_to_attest) + ) + attestations_in_slot = [] + for index in range(committees_per_slot): + beacon_committee = spec.get_beacon_committee(state, slot_to_attest, index) + participants = ( + beacon_committee + if participants_filter is None + else participants_filter(beacon_committee) + ) + if any(participants): + attestation = get_valid_attestation( + spec, + state, + slot_to_attest, + index=index, + signed=True, + filter_participant_set=participants_filter, + ) + attestations_in_slot.append(attestation) + + return attestations_in_slot + + +def _compute_eventually_justified_epoch(spec, state, attestations, participants): + # If not all attestations are included on chain + # and attestation.data.target.epoch > beacon_state.current_justified_checkpoint.epoch + # compute eventually_justified_checkpoint, a would be state.current_justified_checkpoint if all attestations + # were included; this computation respects the validator partition that was building the branch + if ( + len(attestations) > 0 + and attestations[0].data.target.epoch > state.current_justified_checkpoint.epoch + and attestations[0].data.target.epoch > spec.GENESIS_EPOCH + ): + branch_tip = BranchTip( + state, attestations, participants, state.current_justified_checkpoint + ) + _, new_branch_tip = advance_branch_to_next_epoch(spec, branch_tip, enable_attesting=False) + + return new_branch_tip.beacon_state.current_justified_checkpoint + else: + return state.current_justified_checkpoint + + +def advance_branch_to_next_epoch(spec, branch_tip, enable_attesting=True): + """ + Advances a state of the block tree branch to the next epoch + respecting validators participating in building and attesting to this branch. + + The returned beacon state is advanced to the first slot of the next epoch while no block for that slot is created, + produced attestations that aren't yet included on chain are preserved for the future inclusion. + """ + + def participants_filter(comm): + return [index for index in comm if index in branch_tip.participants] + + signed_blocks = [] + attestations = branch_tip.attestations.copy() + state = branch_tip.beacon_state.copy() + current_epoch = spec.get_current_epoch(state) + target_slot = spec.compute_start_slot_at_epoch(current_epoch + 1) + + while state.slot < target_slot: + # Produce block if the proposer is among participanting validators + proposer = spec.get_beacon_proposer_index(state) + if state.slot > spec.GENESIS_SLOT and proposer in branch_tip.participants: + signed_block, state, attestations, _ = produce_block(spec, state, attestations) + signed_blocks.append(signed_block) + + if enable_attesting: + # Produce attestations + attestations_in_slot = attest_to_slot(spec, state, state.slot, participants_filter) + # And prepend them to the list + attestations = list(attestations_in_slot) + attestations + + # Advance a slot + next_slot(spec, state) + + # Cleanup attestations by removing outdated ones + attestations = [ + a + for a in attestations + if a.data.target.epoch in (spec.get_previous_epoch(state), spec.get_current_epoch(state)) + ] + + eventually_justified_checkpoint = _compute_eventually_justified_epoch( + spec, state, attestations, branch_tip.participants + ) + + return signed_blocks, BranchTip( + state, attestations, branch_tip.participants, eventually_justified_checkpoint + ) + + +def advance_state_to_anchor_epoch(spec, state, anchor_epoch, debug) -> ([], BranchTip): + signed_blocks = [] + + genesis_tip = BranchTip( + state.copy(), [], [*range(0, len(state.validators))], state.current_justified_checkpoint + ) + + # Advance the state to the anchor_epoch + anchor_tip = genesis_tip + for epoch in range(spec.GENESIS_EPOCH, anchor_epoch + 1): + pre_state = anchor_tip.beacon_state + new_signed_blocks, anchor_tip = advance_branch_to_next_epoch(spec, anchor_tip) + signed_blocks = signed_blocks + new_signed_blocks + if debug: + post_state = anchor_tip.beacon_state + print("\nepoch", str(epoch) + ":") + print("branch(*, *):", print_epoch(spec, pre_state, new_signed_blocks)) + print( + " ", + len(anchor_tip.participants), + "participants:", + anchor_tip.participants, + ) + print( + " ", + "state.current_justified_checkpoint:", + "(epoch=" + + str(post_state.current_justified_checkpoint.epoch) + + ", root=" + + str(post_state.current_justified_checkpoint.root)[:6] + + ")", + ) + print( + " ", + "eventually_justified_checkpoint:", + "(epoch=" + + str(anchor_tip.eventually_justified_checkpoint.epoch) + + ", root=" + + str(anchor_tip.eventually_justified_checkpoint.root)[:6] + + ")", + ) + + return signed_blocks, anchor_tip + + +def make_events(spec, test_data: FCTestData) -> list[tuple[int, object, bool]]: + """ + Makes test events from `test_data`'s blocks, attestations and slashings, sorted by an effective slot. + Each event is a triple ('tick'|'block'|'attestation'|'attester_slashing', message, valid). + """ + genesis_time = test_data.anchor_state.genesis_time + test_events = [] + + def slot_to_time(slot): + return slot * spec.config.SECONDS_PER_SLOT + genesis_time + + def add_tick_step(time): + test_events.append(("tick", time, None)) + + def add_message_step(kind, message): + test_events.append((kind, message.payload, message.valid)) + + add_tick_step(slot_to_time(test_data.anchor_state.slot)) + slot = test_data.anchor_state.slot + + def get_seffective_slot(message): + event_kind, data, _ = message + if event_kind == "block": + return data.message.slot + elif event_kind == "attestation": + return data.data.slot + 1 + elif event_kind == "attester_slashing": + return max(data.attestation_1.data.slot, data.attestation_1.data.slot) + 1 + else: + assert False + + messages = ( + [("attestation", m.payload, m.valid) for m in test_data.atts] + + [("attester_slashing", m.payload, m.valid) for m in test_data.slashings] + + [("block", m.payload, m.valid) for m in test_data.blocks] + ) + + for event in sorted(messages, key=get_seffective_slot): + event_kind, message, valid = event + event_slot = get_seffective_slot(event) + while slot < event_slot: + slot += 1 + add_tick_step(slot_to_time(slot)) + add_message_step(event_kind, ProtocolMessage(message, valid)) + + if slot is None or slot_to_time(slot) < test_data.store_final_time: + add_tick_step(test_data.store_final_time) + + return test_events + + +def filter_out_duplicate_messages(fn): + def wrapper(*args, **kwargs): + processed_keys = set() + for data in fn(*args, **kwargs): + if len(data) != 2: + yield data + else: + (key, value) = data + if value is not None and isinstance(value, bytes | View): + # skip already processed ssz parts + if key not in processed_keys: + processed_keys.add(key) + yield data + else: + yield data + + return wrapper + + +def _add_block(spec, store, signed_block, test_steps): + """ + Helper method to add a block, when it's unknown whether it's valid or not + """ + yield get_block_file_name(signed_block), signed_block + try: + run_on_block(spec, store, signed_block) + valid = True + except AssertionError: + valid = False + + test_steps.append({"block": get_block_file_name(signed_block), "valid": valid}) + + if valid: + # An on_block step implies receiving block's attestations + for attestation in signed_block.message.body.attestations: + try: + run_on_attestation(spec, store, attestation, is_from_block=True, valid=True) + except AssertionError: + # ignore possible faults, if the block is valid + pass + + # An on_block step implies receiving block's attester slashings + for attester_slashing in signed_block.message.body.attester_slashings: + try: + run_on_attester_slashing(spec, store, attester_slashing, valid=True) + except AssertionError: + # ignore possible faults, if the block is valid + pass + + +@filter_out_duplicate_messages +def yield_fork_choice_test_events( + spec, store, test_data: FCTestData, test_events: list, debug: bool +): + # Yield meta + for k, v in test_data.meta.items(): + yield k, "meta", v + + # Yield anchor state and block initialization + yield "anchor_state", test_data.anchor_state + yield "anchor_block", test_data.anchor_block + + for message in test_data.blocks: + block = message.payload + yield get_block_file_name(block), block.encode_bytes() + + for message in test_data.atts: + attestation = message.payload + yield get_attestation_file_name(attestation), attestation.encode_bytes() + + for message in test_data.slashings: + attester_slashing = message.payload + yield get_attester_slashing_file_name(attester_slashing), attester_slashing.encode_bytes() + + test_steps = [] + + def try_add_mesage(runner, message): + try: + runner(spec, store, message, valid=True) + return True + except AssertionError: + return False + + # record initial tick + on_tick_and_append_step(spec, store, store.time, test_steps) + + for event in test_events: + event_kind = event[0] + if event_kind == "tick": + _, time, _ = event + if time > store.time: + on_tick_and_append_step(spec, store, time, test_steps) + assert store.time == time + elif event_kind == "block": + _, signed_block, valid = event + if valid is None: + yield from _add_block(spec, store, signed_block, test_steps) + else: + yield from add_block(spec, store, signed_block, test_steps, valid=valid) + + block_root = signed_block.message.hash_tree_root() + if valid: + assert store.blocks[block_root] == signed_block.message + else: + assert block_root not in store.blocks.values() + output_store_checks(spec, store, test_steps) + elif event_kind == "attestation": + _, attestation, valid = event + if valid is None: + valid = try_add_mesage(run_on_attestation, attestation) + yield from add_attestation(spec, store, attestation, test_steps, valid=valid) + output_store_checks(spec, store, test_steps) + elif event_kind == "attester_slashing": + _, attester_slashing, valid = event + if valid is None: + valid = try_add_mesage(run_on_attester_slashing, attester_slashing) + yield from add_attester_slashing( + spec, store, attester_slashing, test_steps, valid=valid + ) + output_store_checks(spec, store, test_steps) + else: + raise ValueError("Unknown event " + str(event_kind)) + + if debug: + print(" head: " + print_head(spec, store)) + + output_store_checks(spec, store, test_steps, with_viable_for_head_weights=True) + + yield "steps", test_steps diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/mutation_operators.py b/tests/generators/compliance_runners/fork_choice/instantiators/mutation_operators.py new file mode 100644 index 0000000000..687072efb2 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/mutation_operators.py @@ -0,0 +1,131 @@ +import random + + +def mut_shift_(tv, idx, delta): + time, event = tv[idx] + new_time = int(time) + delta + if new_time >= 0: + return sorted(tv[:idx] + [(new_time, event)] + tv[idx + 1 :], key=lambda x: x[0]) + else: + return idx + + +def mut_shift(tv, rnd: random.Random): + idx = rnd.choice(range(len(tv))) + idx_time = tv[idx][0] + dir = rnd.randint(0, 1) + if idx_time == 0 or dir: + time_shift = rnd.randint(0, 6) * 3 + else: + time_shift = -rnd.randint(0, idx_time // 3) + return mut_shift_(tv, idx, time_shift) + + +def mut_drop_(tv, idx): + return tv[:idx] + tv[idx + 1 :] + + +def mut_drop(tv, rnd: random.Random): + idx = rnd.choice(range(len(tv))) + return mut_drop_(tv, idx) + + +def mut_dup_(tv, idx, shift): + return mut_shift_(tv + [tv[idx]], len(tv), shift) + + +def mutate_test_vector(rnd, initial_tv, cnt, debug=False): + tv_ = initial_tv + for i in range(cnt): + coin = rnd.randint(0, 1) + if coin: + if debug: + print(" mutating initial tv") + tv__ = initial_tv + else: + if debug: + print(" mutating tv_") + tv__ = tv_ + tv = tv__ + op_kind = rnd.randint(0, 2) + if op_kind == 0: + idx = rnd.choice(range(len(tv))) + if debug: + print(f" dropping {idx}") + tv_ = mut_drop_(tv, idx) + elif op_kind == 1: + idx = rnd.choice(range(len(tv))) + idx_time = tv[idx][0] + dir = rnd.randint(0, 1) + if idx_time == 0 or dir: + time_shift = rnd.randint(0, 6) * 3 + else: + time_shift = -rnd.randint(0, idx_time // 3) * 3 + if debug: + print(f" shifting {idx} by {time_shift}") + tv_ = mut_shift_(tv, idx, time_shift) + elif op_kind == 2: + idx = rnd.choice(range(len(tv))) + shift = rnd.randint(0, 5) * 3 + if debug: + print(f" dupping {idx} and shifting by {shift}") + tv_ = mut_dup_(tv, idx, shift) + else: + assert False + yield tv_ + + +class MutationOps: + def __init__(self, start_time, seconds_per_slot, shift_bounds=(-2, 4)): + self.start_time = int(start_time) + self.seconds_per_slot = int(seconds_per_slot) + self.shift_bounds = shift_bounds + + def apply_shift(self, tv, idx, delta): + return mut_shift_(tv, idx, delta) + + def apply_drop(self, tv, idx): + return mut_drop_(tv, idx) + + def apply_dup_shift(self, tv, idx, delta): + return mut_dup_(tv, idx, delta) + + def apply_mutation(self, tv, op_kind, *params): + if op_kind == "shift": + return self.apply_shift(tv, *params) + elif op_kind == "dup_shift": + return self.apply_dup_shift(tv, *params) + elif op_kind == "drop": + return self.apply_drop(tv, *params) + else: + assert False + + def rand_shift(self, time: int, rnd: random.Random) -> int: + assert time >= self.start_time + neg_shift, pos_shift = self.shift_bounds + min_shift = max(self.start_time - time, neg_shift * self.seconds_per_slot) + max_shift = pos_shift * self.seconds_per_slot + if rnd.randint(0, 1) == 0: + return rnd.randint(min_shift, 0) + else: + return rnd.randint(1, max_shift) + + def rand_mutation(self, tv, rnd: random.Random): + idx = rnd.choice(range(len(tv))) + op_kind = rnd.choice(["shift", "drop", "dup_shift"]) + if op_kind == "shift" or op_kind == "dup_shift": + evt_time = int(tv[idx][0]) + params = idx, self.rand_shift(evt_time, rnd) + else: + params = (idx,) + return op_kind, *params + + def rand_mutations(self, tv, num, rnd: random.Random): + mutations = [] + for _ in range(num): + if len(tv) == 0: + break + mut_op = self.rand_mutation(tv, rnd) + mutations.append(mut_op) + tv = self.apply_mutation(tv, *mut_op) + return tv, mutations diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/scheduler.py b/tests/generators/compliance_runners/fork_choice/instantiators/scheduler.py new file mode 100644 index 0000000000..ea95cc15bc --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/scheduler.py @@ -0,0 +1,131 @@ +from dataclasses import dataclass, field + + +@dataclass(order=True, init=False) +class QueueItem: + effective_slot: int + is_attestation: bool + message: object = field(compare=False) + dependencies: list = field(compare=False) + is_from_block: bool = field(compare=False) + + def __init__(self, message, is_attestation, is_from_block=False): + self.message = message + self.is_attestation = is_attestation + if is_attestation: + data = message.data + self.effective_slot = data.slot + 1 + self.dependencies = [data.beacon_block_root, data.target.root] + self.is_from_block = is_from_block + else: + block = message.message + self.effective_slot = block.slot + self.dependencies = [block.parent_root] + self.is_from_block = False + + +class MessageScheduler: + def __init__(self, spec, store): + self.spec = spec + self.store = store + self.message_queue = [] + + def is_early_message(self, item: QueueItem) -> bool: + current_slot = self.spec.get_current_slot(self.store) + return item.effective_slot < current_slot or any( + root not in self.store.blocks for root in item.dependencies + ) + + def enque_message(self, item: QueueItem): + self.message_queue.append(item) + + def drain_queue( + self, + ) -> list[QueueItem]: + messages = self.message_queue[:] + self.message_queue.clear() + return messages + + def process_queue(self) -> tuple[bool, list]: + applied_events = [] + updated = False + for item in self.drain_queue(): + if self.is_early_message(item): + self.enque_message(item) + elif item.is_attestation: + if self.process_attestation(item.message): + applied_events.append(("attestation", item.message, True)) + else: + updated_, events_ = self.process_block(item.message, recovery=True) + if updated_: + updated = True + applied_events.extend(events_) + assert ("block", item.message, True) in events_ + return updated, applied_events + + def purge_queue(self) -> list: + applied_events = [] + while True: + updated, events = self.process_queue() + applied_events.extend(events) + if updated: + continue + else: + return applied_events + + def process_tick(self, time) -> list: + applied_events = [] + SECONDS_PER_SLOT = self.spec.config.SECONDS_PER_SLOT + assert time >= self.store.time + tick_slot = (time - self.store.genesis_time) // SECONDS_PER_SLOT + while self.spec.get_current_slot(self.store) < tick_slot: + previous_time = ( + self.store.genesis_time + + (self.spec.get_current_slot(self.store) + 1) * SECONDS_PER_SLOT + ) + self.spec.on_tick(self.store, previous_time) + applied_events.append( + ("tick", previous_time, self.spec.get_current_slot(self.store) < tick_slot) + ) + applied_events.extend(self.purge_queue()) + return applied_events + + def process_attestation(self, attestation, is_from_block=False): + try: + self.spec.on_attestation(self.store, attestation, is_from_block) + return True + except AssertionError: + item = QueueItem(attestation, True, is_from_block) + if self.is_early_message(item): + self.enque_message(item) + return False + + def process_slashing(self, slashing): + try: + self.spec.on_attester_slashing(self.store, slashing) + return True + except AssertionError: + return False + + def process_block_messages(self, signed_block): + block = signed_block.message + for attestation in block.body.attestations: + self.process_attestation(attestation, is_from_block=True) + for attester_slashing in block.body.attester_slashings: + self.process_slashing(attester_slashing) + + def process_block(self, signed_block, recovery=False) -> tuple[bool, list]: + applied_events = [] + try: + self.spec.on_block(self.store, signed_block) + valid = True + applied_events.append(("block", signed_block, recovery)) + except AssertionError: + item = QueueItem(signed_block, False) + if self.is_early_message(item): + self.enque_message(item) + valid = False + if valid: + applied_events.extend(self.purge_queue()) + self.process_block_messages(signed_block) + return valid, applied_events diff --git a/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py b/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py new file mode 100644 index 0000000000..d5c74c53ec --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/instantiators/test_case.py @@ -0,0 +1,358 @@ +import random +from collections.abc import Iterable +from dataclasses import dataclass +from os import path +from typing import Any + +from ruamel.yaml import YAML + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.test.context import ( + spec_state_test, + with_altair_and_later, +) +from eth2spec.test.helpers.fork_choice import ( + get_attestation_file_name, + get_attester_slashing_file_name, + get_block_file_name, + on_tick_and_append_step, + output_store_checks, +) +from eth2spec.utils import bls + +from .block_cover import gen_block_cover_test_data +from .block_tree import gen_block_tree_test_data +from .helpers import ( + FCTestData, + filter_out_duplicate_messages, + make_events, + yield_fork_choice_test_events, +) +from .mutation_operators import MutationOps +from .scheduler import MessageScheduler + +BLS_ACTIVE = False +GENERATOR_NAME = "fork_choice_compliance" +SUITE_NAME = "pyspec_tests" + + +@dataclass(eq=True, frozen=True) +class FCTestKind: + pass + + +@dataclass(eq=True, frozen=True) +class BlockTreeTestKind(FCTestKind): + with_attester_slashings: bool + with_invalid_messages: bool + + +@dataclass(eq=True, frozen=True) +class BlockCoverTestKind(FCTestKind): + pass + + +@dataclass +class FCTestDNA: + kind: FCTestKind + solution: Any + variation_seed: int + mutation_seed: int | None + + +@dataclass(init=False) +class PlainFCTestCase(TestCase): + test_dna: FCTestDNA + bls_active: bool + debug: bool + + def __init__(self, test_dna, bls_active=False, debug=False, **kwds): + super().__init__( + fork_name=kwds["fork_name"], + preset_name=kwds["preset_name"], + runner_name=kwds["runner_name"], + handler_name=kwds["handler_name"], + suite_name=kwds["suite_name"], + case_name=kwds["case_name"], + case_fn=self.mutation_case_fn, + ) + self.test_dna = test_dna + self.bls_active = bls_active + self.debug = debug + + def mutation_case_fn(self): + test_kind = self.test_dna.kind + phase, preset = self.fork_name, self.preset_name + bls_active, debug = self.bls_active, self.debug + solution, seed = self.test_dna.solution, self.test_dna.variation_seed + mut_seed = self.test_dna.mutation_seed + return yield_mutation_test_case( + generator_mode=True, + phase=phase, + preset=preset, + bls_active=bls_active, + debug=debug, + seed=seed, + mut_seed=mut_seed, + test_kind=test_kind, + solution=solution, + ) + + +def get_test_data(spec, state, test_kind, solution, debug, seed): + if isinstance(test_kind, BlockTreeTestKind): + with_attester_slashings = test_kind.with_attester_slashings + with_invalid_messages = test_kind.with_invalid_messages + sm_links = solution["sm_links"] + block_parents = solution["block_parents"] + test_data = gen_block_tree_test_data( + spec, + state, + debug, + seed, + sm_links, + block_parents, + with_attester_slashings, + with_invalid_messages, + ) + elif isinstance(test_kind, BlockCoverTestKind): + model_params = solution + test_data, _ = gen_block_cover_test_data(spec, state, model_params, debug, seed) + else: + raise ValueError(f"Unknown FC test kind {test_kind}") + return test_data + + +@with_altair_and_later +@spec_state_test +def yield_mutation_test_case(spec, state, test_kind, solution, debug, seed, mut_seed): + test_data = get_test_data(spec, state, test_kind, solution, debug, seed) + events = make_events(spec, test_data) + store = spec.get_forkchoice_store(test_data.anchor_state, test_data.anchor_block) + + if mut_seed is None: + return yield_fork_choice_test_events(spec, store, test_data, events, debug) + else: + test_vector = events_to_test_vector(events) + mops = MutationOps(store.time, spec.config.SECONDS_PER_SLOT) + mutated_vector, mutations = mops.rand_mutations(test_vector, 4, random.Random(mut_seed)) + + test_data.meta["mut_seed"] = mut_seed + test_data.meta["mutations"] = mutations + + mutated_events = test_vector_to_events(mutated_vector) + + return yield_test_parts(spec, store, test_data, mutated_events) + + +def events_to_test_vector(events) -> list[Any]: + test_vector = [] + current_time = None + for event in events: + event_kind, data, _ = event + if event_kind == "tick": + current_time = data + else: + if event_kind == "block": + event_id = data + elif event_kind == "attestation": + event_id = data + elif event_kind == "attester_slashing": + event_id = data + else: + assert False, event_kind + test_vector.append((current_time, (event_kind, event_id))) + return test_vector + + +def test_vector_to_events(test_vector): + events = [] + current_time = None + for time, (event_kind, data) in test_vector: + if time != current_time: + current_time = time + events.append(("tick", time, None)) + events.append((event_kind, data, None)) + return events + + +@filter_out_duplicate_messages +def yield_test_parts(spec, store, test_data: FCTestData, events): + record_recovery_messages = True + + for k, v in test_data.meta.items(): + yield k, "meta", v + + yield "anchor_state", test_data.anchor_state + yield "anchor_block", test_data.anchor_block + + for message in test_data.blocks: + block = message.payload + yield get_block_file_name(block), block + + for message in test_data.atts: + attestation = message.payload + yield get_attestation_file_name(attestation), attestation + + for message in test_data.slashings: + attester_slashing = message.payload + yield get_attester_slashing_file_name(attester_slashing), attester_slashing + + test_steps = [] + scheduler = MessageScheduler(spec, store) + + # record first tick + on_tick_and_append_step(spec, store, store.time, test_steps) + + for kind, data, _ in events: + if kind == "tick": + time = data + if time > store.time: + applied_events = scheduler.process_tick(time) + if record_recovery_messages: + for event_kind, event_data, recovery in applied_events: + if event_kind == "tick": + test_steps.append({"tick": int(event_data)}) + elif event_kind == "block": + assert recovery + _block_id = get_block_file_name(event_data) + test_steps.append({"block": _block_id, "valid": True}) + elif event_kind == "attestation": + assert recovery + _attestation_id = get_attestation_file_name(event_data) + if _attestation_id not in test_data.atts: + yield _attestation_id, event_data + test_steps.append({"attestation": _attestation_id, "valid": True}) + else: + assert False + else: + assert False + if time > store.time: + # inside a slot + on_tick_and_append_step(spec, store, time, test_steps) + else: + assert time == store.time + output_store_checks(spec, store, test_steps) + elif kind == "block": + block = data + block_id = get_block_file_name(block) + valid, applied_events = scheduler.process_block(block) + if record_recovery_messages: + if valid: + for event_kind, event_data, recovery in applied_events: + if event_kind == "block": + _block_id = get_block_file_name(event_data) + if recovery: + test_steps.append({"block": _block_id, "valid": True}) + else: + test_steps.append({"block": _block_id, "valid": True}) + elif event_kind == "attestation": + _attestation_id = get_attestation_file_name(event_data) + if recovery: + if _attestation_id not in test_data.atts: + yield _attestation_id, event_data + test_steps.append({"attestation": _attestation_id, "valid": True}) + else: + assert False + test_steps.append({"attestation": _attestation_id, "valid": True}) + else: + assert False + else: + assert len(applied_events) == 0 + test_steps.append({"block": block_id, "valid": valid}) + else: + assert False + test_steps.append({"block": block_id, "valid": valid}) + block_root = block.message.hash_tree_root() + assert valid == (block_root in store.blocks) + + output_store_checks(spec, store, test_steps) + elif kind == "attestation": + attestation = data + att_id = get_attestation_file_name(attestation) + valid = scheduler.process_attestation(attestation, is_from_block=False) + test_steps.append({"attestation": att_id, "valid": valid}) + output_store_checks(spec, store, test_steps) + elif kind == "attester_slashing": + attester_slashing = data + slashing_id = get_attester_slashing_file_name(attester_slashing) + valid = scheduler.process_slashing(attester_slashing) + test_steps.append({"attester_slashing": slashing_id, "valid": valid}) + output_store_checks(spec, store, test_steps) + else: + raise ValueError(f"not implemented {kind}") + next_slot_time = ( + store.genesis_time + (spec.get_current_slot(store) + 1) * spec.config.SECONDS_PER_SLOT + ) + on_tick_and_append_step(spec, store, next_slot_time, test_steps) + output_store_checks(spec, store, test_steps, with_viable_for_head_weights=True) + + yield "steps", test_steps + + +def prepare_bls(): + bls.use_milagro() + + +def get_test_kind(test_type, with_attester_slashings, with_invalid_messages): + if test_type == "block_tree": + return BlockTreeTestKind(with_attester_slashings, with_invalid_messages) + elif test_type == "block_cover": + return BlockCoverTestKind() + else: + raise ValueError(f"Unsupported test type: {test_type}") + + +def _load_yaml(path: str): + with open(path) as f: + yaml = YAML(typ="safe") + return yaml.load(f) + + +def enumerate_test_dnas(config_dir, test_name, params) -> Iterable[tuple[str, FCTestData]]: + test_type = params["test_type"] + instances_path = params["instances"] + initial_seed = params["seed"] + nr_variations = params["nr_variations"] + nr_mutations = params["nr_mutations"] + with_attester_slashings = params.get("with_attester_slashings", False) + with_invalid_messages = params.get("with_invalid_messages", False) + + solutions = _load_yaml(path.join(config_dir, instances_path)) + test_kind = get_test_kind(test_type, with_attester_slashings, with_invalid_messages) + + seeds = [initial_seed] + if nr_variations > 1: + rnd = random.Random(initial_seed) + seeds = [rnd.randint(1, 10000) for _ in range(nr_variations)] + seeds[0] = initial_seed + + for i, solution in enumerate(solutions): + for seed in seeds: + for j in range(1 + nr_mutations): + test_dna = FCTestDNA(test_kind, solution, seed, None if j == 0 else seed + j - 1) + case_name = test_name + "_" + str(i) + "_" + str(seed) + "_" + str(j) + yield case_name, test_dna + + +def enumerate_test_cases(config_path, forks, presets, debug): + config_dir = path.dirname(config_path) + test_gen_config = _load_yaml(config_path) + + for test_name, params in test_gen_config.items(): + if debug: + print(test_name) + for fork_name in forks: + for preset_name in presets: + for case_name, test_dna in enumerate_test_dnas(config_dir, test_name, params): + yield PlainFCTestCase( + test_dna=test_dna, + bls_active=BLS_ACTIVE, + debug=debug, + fork_name=fork_name, + preset_name=preset_name, + runner_name=GENERATOR_NAME, + handler_name=test_name, + suite_name=SUITE_NAME, + case_name=case_name, + ) diff --git a/tests/generators/compliance_runners/fork_choice/model/Block_cover.mzn b/tests/generators/compliance_runners/fork_choice/model/Block_cover.mzn new file mode 100644 index 0000000000..70c045ec87 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/model/Block_cover.mzn @@ -0,0 +1,67 @@ +include "globals.mzn"; + +int: AE; +int: MB = 4; + +set of int: EPOCH = AE..(AE+MB-1); +set of int: BLOCK = 0..MB; + +type BS = record(EPOCH: e, EPOCH: je, EPOCH: uje); + + +array[BLOCK] of var EPOCH: es; +array[BLOCK] of var EPOCH: pjes; +array[BLOCK] of var EPOCH: cjes; +array[BLOCK] of var EPOCH: ujes; +array[BLOCK] of var bool: prevs; +array[BLOCK] of var bool: currs; +array[BLOCK] of var BLOCK: parents; + +function var EPOCH: n_e(var BLOCK: b) = es[b] + 1; +function var EPOCH: n_pje(var BLOCK: b) = cjes[b]; +function var EPOCH: n_cje(var BLOCK: b) = + if currs[b] then + es[b] + elseif prevs[b] then + es[b] - 1 + else + cjes[b] + endif; + +constraint es[0] == AE; +constraint pjes[0] == AE /\ cjes[0] == AE; +constraint forall(b in BLOCK)(if b > 0 then parents[b] < b else parents[b] == b endif); + +constraint forall(b in BLOCK where b > 0)(n_e(parents[b]) == es[b] /\ n_pje(parents[b]) == pjes[b] /\ n_cje(parents[b]) == cjes[b]); +constraint forall(b in BLOCK)(ujes[b] == n_cje(b)); + +function var EPOCH: get_vse(var BLOCK: b) = + if es[b] < curr_e then n_cje(b) else cjes[b] endif; + +var EPOCH: curr_e; +var EPOCH: store_je; + +constraint forall(b in BLOCK)(es[b] <= curr_e); +constraint forall(b in BLOCK)(get_vse(b) <= store_je); +constraint exists(b in BLOCK)(get_vse(b) == store_je); + +predicate is_leaf(var BLOCK: b) = not exists(child in BLOCK where child > b /\ child <= max_block)(parents[child] == b); + +var BLOCK: target_block; +var BLOCK: max_block; + +constraint get_vse(max_block) == store_je; +constraint target_block <= max_block; + +bool: store_je_eq_zero; +bool: store_fe_eq_zero = true; +bool: block_vse_eq_store_je; +bool: block_vse_plus_two_ge_curr_e; +bool: block_is_leaf; +bool: block_is_store_jb_descendant = true; +bool: block_is_store_fb_descendant = true; + +constraint block_vse_eq_store_je <-> get_vse(target_block) == store_je; +constraint block_vse_plus_two_ge_curr_e <-> get_vse(target_block) + 2 >= curr_e; +constraint block_is_leaf <-> is_leaf(target_block); +constraint store_je_eq_zero <-> store_je == 0; diff --git a/tests/generators/compliance_runners/fork_choice/model/Block_tree.mzn b/tests/generators/compliance_runners/fork_choice/model/Block_tree.mzn new file mode 100644 index 0000000000..9c94be1d33 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/model/Block_tree.mzn @@ -0,0 +1,14 @@ +include "globals.mzn"; + +int: NB; % num of blocks +int: MC; % max children + +set of int: BLOCKS = 0..(NB-1); + +array[BLOCKS] of var BLOCKS: parent; + +function var int: count_children(var BLOCKS: block) = + sum(ch in BLOCKS)(if ch != 0 /\ parent[ch] == block then 1 else 0 endif); + +constraint forall(b in BLOCKS)(if b != 0 then parent[b] < b else parent[b] == b endif); +constraint forall(b in BLOCKS)(count_children(b) <= MC); diff --git a/tests/generators/compliance_runners/fork_choice/model/SM_links.mzn b/tests/generators/compliance_runners/fork_choice/model/SM_links.mzn new file mode 100644 index 0000000000..93b6cfba78 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/model/SM_links.mzn @@ -0,0 +1,22 @@ +include "globals.mzn"; + +int: AE; % anchor epoch +int: NE; % num of epochs +int: NL; % num of super-majority links + +set of int: EPOCH = AE..(AE+NE-1); +set of int: LINKS = 0..(NL-1); + +array[LINKS] of var EPOCH: sources; +array[LINKS] of var EPOCH: targets; + +predicate surround_vote(var LINKS: a, var LINKS: b) = + sources[a] < sources[b] /\ targets[b] < targets[a]; + +constraint forall(i in LINKS)(sources[i] < targets[i]); +constraint forall(i in LINKS)(sources[i] == AE \/ member(targets, sources[i])); +constraint strictly_increasing(targets); +constraint forall(i,j in LINKS where i != j)(not surround_vote(i,j)); + +% Exclude (1, 2) SM link which is unreachable for the Gasper protocol +constraint forall(i in LINKS)(not (sources[i] == 1 /\ targets[i] == 2)); diff --git a/tests/generators/compliance_runners/fork_choice/requirements.txt b/tests/generators/compliance_runners/fork_choice/requirements.txt new file mode 100644 index 0000000000..e1811775ec --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/requirements.txt @@ -0,0 +1,3 @@ +pytest>=4.4 +minizinc>=0.9.0 +../../../../[generator] diff --git a/tests/generators/compliance_runners/fork_choice/sample_attester_slashings.yaml b/tests/generators/compliance_runners/fork_choice/sample_attester_slashings.yaml new file mode 100644 index 0000000000..a3f0669faa --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/sample_attester_slashings.yaml @@ -0,0 +1,153 @@ +- block_parents: [0, 0, 1, 2, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: &id003 [0, 0, 0, 0] + sm_links: &id002 + - [0, 1] + - [0, 2] + - [0, 3] +- block_parents: &id004 [0, 0, 1, 0] + sm_links: *id002 +- block_parents: &id006 [0, 0, 0, 1] + sm_links: *id002 +- block_parents: &id007 [0, 0, 1, 1] + sm_links: *id002 +- block_parents: *id003 + sm_links: &id005 + - [0, 1] + - [0, 2] + - [1, 3] +- block_parents: *id004 + sm_links: *id005 +- block_parents: *id006 + sm_links: *id005 +- block_parents: *id007 + sm_links: *id005 +- block_parents: *id003 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [2, 3] +- block_parents: *id004 + sm_links: *id008 +- block_parents: *id006 + sm_links: *id008 +- block_parents: *id007 + sm_links: *id008 +- block_parents: &id010 [0, 0, 0, 0] + sm_links: &id009 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id011 [0, 0, 1, 0] + sm_links: *id009 +- block_parents: &id013 [0, 0, 0, 1] + sm_links: *id009 +- block_parents: &id014 [0, 0, 1, 1] + sm_links: *id009 +- block_parents: *id010 + sm_links: &id012 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id011 + sm_links: *id012 +- block_parents: *id013 + sm_links: *id012 +- block_parents: *id014 + sm_links: *id012 +- block_parents: *id010 + sm_links: &id015 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id015 +- block_parents: *id013 + sm_links: *id015 +- block_parents: *id014 + sm_links: *id015 +- block_parents: *id010 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id016 +- block_parents: *id013 + sm_links: *id016 +- block_parents: *id014 + sm_links: *id016 +- block_parents: *id010 + sm_links: &id017 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id011 + sm_links: *id017 +- block_parents: *id013 + sm_links: *id017 +- block_parents: *id014 + sm_links: *id017 +- block_parents: *id010 + sm_links: &id018 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id018 +- block_parents: *id013 + sm_links: *id018 +- block_parents: *id014 + sm_links: *id018 +- block_parents: *id010 + sm_links: &id019 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id019 +- block_parents: *id013 + sm_links: *id019 +- block_parents: *id014 + sm_links: *id019 +- block_parents: *id010 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id020 +- block_parents: *id013 + sm_links: *id020 +- block_parents: *id014 + sm_links: *id020 +- block_parents: *id010 + sm_links: &id021 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id021 +- block_parents: *id013 + sm_links: *id021 +- block_parents: *id014 + sm_links: *id021 diff --git a/tests/generators/compliance_runners/fork_choice/sample_block_cover.yaml b/tests/generators/compliance_runners/fork_choice/sample_block_cover.yaml new file mode 100644 index 0000000000..d04d3b1408 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/sample_block_cover.yaml @@ -0,0 +1,162 @@ +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 1 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 2 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 1 + target_block: 2 +- block_epochs: [0, 1, 1, 2, 2] + current_epoch: 2 + current_justifications: [false, true, false, false, false] + parents: [0, 0, 0, 1, 1] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false, false, false] + store_justified_epoch: 1 + target_block: 1 +- block_epochs: [0, 1, 1] + current_epoch: 2 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 1 + target_block: 1 +- block_epochs: [0, 1] + current_epoch: 2 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 1 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 3 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 1 + target_block: 1 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 1 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 diff --git a/tests/generators/compliance_runners/fork_choice/sample_block_tree.yaml b/tests/generators/compliance_runners/fork_choice/sample_block_tree.yaml new file mode 100644 index 0000000000..1ac1eafb79 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/sample_block_tree.yaml @@ -0,0 +1,191 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] +- block_parents: &id003 [0, 0, 1, 2, 3, 4, 5, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: &id005 [0, 0, 1, 2, 3, 4, 4, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: &id006 [0, 0, 1, 2, 3, 4, 5, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 3] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id005 + sm_links: *id004 +- block_parents: *id006 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id007 + - [0, 2] + - [0, 3] +- block_parents: *id003 + sm_links: *id007 +- block_parents: *id005 + sm_links: *id007 +- block_parents: *id006 + sm_links: *id007 +- block_parents: *id002 + sm_links: &id008 + - [0, 1] + - [1, 3] +- block_parents: *id003 + sm_links: *id008 +- block_parents: *id005 + sm_links: *id008 +- block_parents: *id006 + sm_links: *id008 +- block_parents: *id002 + sm_links: &id009 + - [0, 2] + - [2, 3] +- block_parents: *id003 + sm_links: *id009 +- block_parents: *id005 + sm_links: *id009 +- block_parents: *id006 + sm_links: *id009 +- block_parents: &id011 [0, 0, 0, 0] + sm_links: &id010 + - [0, 1] + - [0, 2] + - [0, 3] +- block_parents: &id012 [0, 0, 1, 0] + sm_links: *id010 +- block_parents: &id014 [0, 0, 0, 1] + sm_links: *id010 +- block_parents: &id015 [0, 0, 1, 1] + sm_links: *id010 +- block_parents: *id011 + sm_links: &id013 + - [0, 1] + - [0, 2] + - [1, 3] +- block_parents: *id012 + sm_links: *id013 +- block_parents: *id014 + sm_links: *id013 +- block_parents: *id015 + sm_links: *id013 +- block_parents: *id011 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [2, 3] +- block_parents: *id012 + sm_links: *id016 +- block_parents: *id014 + sm_links: *id016 +- block_parents: *id015 + sm_links: *id016 +- block_parents: &id018 [0, 0, 0, 0] + sm_links: &id017 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id019 [0, 0, 1, 0] + sm_links: *id017 +- block_parents: &id021 [0, 0, 0, 1] + sm_links: *id017 +- block_parents: &id022 [0, 0, 1, 1] + sm_links: *id017 +- block_parents: *id018 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id019 + sm_links: *id020 +- block_parents: *id021 + sm_links: *id020 +- block_parents: *id022 + sm_links: *id020 +- block_parents: *id018 + sm_links: &id023 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id019 + sm_links: *id023 +- block_parents: *id021 + sm_links: *id023 +- block_parents: *id022 + sm_links: *id023 +- block_parents: *id018 + sm_links: &id024 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id019 + sm_links: *id024 +- block_parents: *id021 + sm_links: *id024 +- block_parents: *id022 + sm_links: *id024 +- block_parents: *id018 + sm_links: &id025 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id019 + sm_links: *id025 +- block_parents: *id021 + sm_links: *id025 +- block_parents: *id022 + sm_links: *id025 +- block_parents: *id018 + sm_links: &id026 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id019 + sm_links: *id026 +- block_parents: *id021 + sm_links: *id026 +- block_parents: *id022 + sm_links: *id026 +- block_parents: *id018 + sm_links: &id027 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id019 + sm_links: *id027 +- block_parents: *id021 + sm_links: *id027 +- block_parents: *id022 + sm_links: *id027 +- block_parents: *id018 + sm_links: &id028 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id019 + sm_links: *id028 +- block_parents: *id021 + sm_links: *id028 +- block_parents: *id022 + sm_links: *id028 +- block_parents: *id018 + sm_links: &id029 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id019 + sm_links: *id029 +- block_parents: *id021 + sm_links: *id029 +- block_parents: *id022 + sm_links: *id029 diff --git a/tests/generators/compliance_runners/fork_choice/sample_invalid_messages.yaml b/tests/generators/compliance_runners/fork_choice/sample_invalid_messages.yaml new file mode 100644 index 0000000000..a3f0669faa --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/sample_invalid_messages.yaml @@ -0,0 +1,153 @@ +- block_parents: [0, 0, 1, 2, 3, 4, 4, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 3, 2, 2, 1, 1, 0, 0] + sm_links: *id001 +- block_parents: &id003 [0, 0, 0, 0] + sm_links: &id002 + - [0, 1] + - [0, 2] + - [0, 3] +- block_parents: &id004 [0, 0, 1, 0] + sm_links: *id002 +- block_parents: &id006 [0, 0, 0, 1] + sm_links: *id002 +- block_parents: &id007 [0, 0, 1, 1] + sm_links: *id002 +- block_parents: *id003 + sm_links: &id005 + - [0, 1] + - [0, 2] + - [1, 3] +- block_parents: *id004 + sm_links: *id005 +- block_parents: *id006 + sm_links: *id005 +- block_parents: *id007 + sm_links: *id005 +- block_parents: *id003 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [2, 3] +- block_parents: *id004 + sm_links: *id008 +- block_parents: *id006 + sm_links: *id008 +- block_parents: *id007 + sm_links: *id008 +- block_parents: &id010 [0, 0, 0, 0] + sm_links: &id009 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id011 [0, 0, 1, 0] + sm_links: *id009 +- block_parents: &id013 [0, 0, 0, 1] + sm_links: *id009 +- block_parents: &id014 [0, 0, 1, 1] + sm_links: *id009 +- block_parents: *id010 + sm_links: &id012 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id011 + sm_links: *id012 +- block_parents: *id013 + sm_links: *id012 +- block_parents: *id014 + sm_links: *id012 +- block_parents: *id010 + sm_links: &id015 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id015 +- block_parents: *id013 + sm_links: *id015 +- block_parents: *id014 + sm_links: *id015 +- block_parents: *id010 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id016 +- block_parents: *id013 + sm_links: *id016 +- block_parents: *id014 + sm_links: *id016 +- block_parents: *id010 + sm_links: &id017 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id011 + sm_links: *id017 +- block_parents: *id013 + sm_links: *id017 +- block_parents: *id014 + sm_links: *id017 +- block_parents: *id010 + sm_links: &id018 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id018 +- block_parents: *id013 + sm_links: *id018 +- block_parents: *id014 + sm_links: *id018 +- block_parents: *id010 + sm_links: &id019 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id019 +- block_parents: *id013 + sm_links: *id019 +- block_parents: *id014 + sm_links: *id019 +- block_parents: *id010 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id011 + sm_links: *id020 +- block_parents: *id013 + sm_links: *id020 +- block_parents: *id014 + sm_links: *id020 +- block_parents: *id010 + sm_links: &id021 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id011 + sm_links: *id021 +- block_parents: *id013 + sm_links: *id021 +- block_parents: *id014 + sm_links: *id021 diff --git a/tests/generators/compliance_runners/fork_choice/small/block_cover.yaml b/tests/generators/compliance_runners/fork_choice/small/block_cover.yaml new file mode 100644 index 0000000000..6f8ad60ce6 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/small/block_cover.yaml @@ -0,0 +1,216 @@ +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 1 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 1 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, true] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 3 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 diff --git a/tests/generators/compliance_runners/fork_choice/small/block_tree_other.yaml b/tests/generators/compliance_runners/fork_choice/small/block_tree_other.yaml new file mode 100644 index 0000000000..e39c4ebc06 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/small/block_tree_other.yaml @@ -0,0 +1,12 @@ +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 3, 2, 1, 0] + sm_links: *id001 diff --git a/tests/generators/compliance_runners/fork_choice/small/block_tree_tree.yaml b/tests/generators/compliance_runners/fork_choice/small/block_tree_tree.yaml new file mode 100644 index 0000000000..a2a58b7eae --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/small/block_tree_tree.yaml @@ -0,0 +1,312 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [0, 3] +- block_parents: &id003 [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 2] + - [0, 4] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id005 + - [0, 1] + - [0, 2] + - [1, 3] +- block_parents: *id003 + sm_links: *id005 +- block_parents: *id002 + sm_links: &id006 + - [0, 1] + - [0, 2] + - [2, 3] +- block_parents: *id003 + sm_links: *id006 +- block_parents: *id002 + sm_links: &id007 + - [0, 1] + - [0, 2] + - [1, 4] +- block_parents: *id003 + sm_links: *id007 +- block_parents: *id002 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [2, 4] +- block_parents: *id003 + sm_links: *id008 +- block_parents: *id002 + sm_links: &id009 + - [0, 1] + - [0, 3] + - [0, 4] +- block_parents: *id003 + sm_links: *id009 +- block_parents: *id002 + sm_links: &id010 + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: *id003 + sm_links: *id010 +- block_parents: *id002 + sm_links: &id011 + - [0, 1] + - [0, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id011 +- block_parents: *id002 + sm_links: &id012 + - [0, 1] + - [0, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id012 +- block_parents: *id002 + sm_links: &id013 + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id013 +- block_parents: *id002 + sm_links: &id014 + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id014 +- block_parents: *id002 + sm_links: &id015 + - [0, 1] + - [1, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id015 +- block_parents: *id002 + sm_links: &id016 + - [0, 1] + - [1, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id016 +- block_parents: *id002 + sm_links: &id017 + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id017 +- block_parents: *id002 + sm_links: &id018 + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id018 +- block_parents: [0, 0, 1, 0, 0] + sm_links: &id019 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 0, 1, 0] + sm_links: *id019 +- block_parents: [0, 0, 1, 1, 0] + sm_links: *id019 +- block_parents: [0, 0, 0, 2, 0] + sm_links: *id019 +- block_parents: [0, 0, 1, 2, 0] + sm_links: *id019 +- block_parents: [0, 0, 0, 0, 1] + sm_links: *id019 +- block_parents: [0, 0, 1, 0, 1] + sm_links: *id019 +- block_parents: [0, 0, 0, 1, 1] + sm_links: *id019 +- block_parents: [0, 0, 1, 1, 1] + sm_links: *id019 +- block_parents: [0, 0, 0, 2, 1] + sm_links: *id019 +- block_parents: [0, 0, 1, 2, 1] + sm_links: *id019 +- block_parents: [0, 0, 0, 0, 2] + sm_links: *id019 +- block_parents: [0, 0, 1, 0, 2] + sm_links: *id019 +- block_parents: [0, 0, 0, 1, 2] + sm_links: *id019 +- block_parents: [0, 0, 1, 1, 2] + sm_links: *id019 +- block_parents: [0, 0, 0, 2, 2] + sm_links: *id019 +- block_parents: [0, 0, 1, 2, 2] + sm_links: *id019 +- block_parents: [0, 0, 0, 0, 3] + sm_links: *id019 +- block_parents: [0, 0, 1, 0, 3] + sm_links: *id019 +- block_parents: [0, 0, 0, 1, 3] + sm_links: *id019 +- block_parents: [0, 0, 1, 1, 3] + sm_links: *id019 +- block_parents: [0, 0, 0, 2, 3] + sm_links: *id019 +- block_parents: [0, 0, 1, 2, 3] + sm_links: *id019 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0] + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 3, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 2, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 2, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 2, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 2, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 2, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 2, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 4, 3, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 5, 3, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 3, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 6, 3, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 3, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 4, 3, 1, 0] + sm_links: *id020 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 4, 3, 1, 0] + sm_links: *id020 diff --git a/tests/generators/compliance_runners/fork_choice/small/block_tree_tree_2.yaml b/tests/generators/compliance_runners/fork_choice/small/block_tree_tree_2.yaml new file mode 100644 index 0000000000..907dcd18fd --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/small/block_tree_tree_2.yaml @@ -0,0 +1,1252 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id003 [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 5] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id005 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id005 +- block_parents: *id002 + sm_links: &id006 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id006 +- block_parents: *id002 + sm_links: &id007 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id007 +- block_parents: *id002 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id008 +- block_parents: *id002 + sm_links: &id009 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id009 +- block_parents: *id002 + sm_links: &id010 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id010 +- block_parents: *id002 + sm_links: &id011 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id011 +- block_parents: *id002 + sm_links: &id012 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id012 +- block_parents: *id002 + sm_links: &id013 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id013 +- block_parents: *id002 + sm_links: &id014 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id014 +- block_parents: *id002 + sm_links: &id015 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id015 +- block_parents: *id002 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id016 +- block_parents: *id002 + sm_links: &id017 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id017 +- block_parents: *id002 + sm_links: &id018 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id018 +- block_parents: *id002 + sm_links: &id019 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id019 +- block_parents: *id002 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id020 +- block_parents: *id002 + sm_links: &id021 + - [0, 1] + - [0, 2] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id021 +- block_parents: *id002 + sm_links: &id022 + - [0, 1] + - [0, 2] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id022 +- block_parents: *id002 + sm_links: &id023 + - [0, 1] + - [0, 2] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id023 +- block_parents: *id002 + sm_links: &id024 + - [0, 1] + - [0, 2] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id024 +- block_parents: *id002 + sm_links: &id025 + - [0, 1] + - [0, 2] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id025 +- block_parents: *id002 + sm_links: &id026 + - [0, 1] + - [0, 2] + - [1, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id026 +- block_parents: *id002 + sm_links: &id027 + - [0, 1] + - [0, 2] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id027 +- block_parents: *id002 + sm_links: &id028 + - [0, 1] + - [0, 2] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id028 +- block_parents: *id002 + sm_links: &id029 + - [0, 1] + - [0, 2] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id029 +- block_parents: *id002 + sm_links: &id030 + - [0, 1] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id030 +- block_parents: *id002 + sm_links: &id031 + - [0, 2] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id031 +- block_parents: *id002 + sm_links: &id032 + - [0, 1] + - [0, 3] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id032 +- block_parents: *id002 + sm_links: &id033 + - [0, 1] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id033 +- block_parents: *id002 + sm_links: &id034 + - [0, 1] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id034 +- block_parents: *id002 + sm_links: &id035 + - [0, 2] + - [0, 3] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id035 +- block_parents: *id002 + sm_links: &id036 + - [0, 2] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id036 +- block_parents: *id002 + sm_links: &id037 + - [0, 2] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id037 +- block_parents: *id002 + sm_links: &id038 + - [0, 1] + - [0, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id038 +- block_parents: *id002 + sm_links: &id039 + - [0, 1] + - [0, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id039 +- block_parents: *id002 + sm_links: &id040 + - [0, 1] + - [0, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id040 +- block_parents: *id002 + sm_links: &id041 + - [0, 1] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id041 +- block_parents: *id002 + sm_links: &id042 + - [0, 1] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id042 +- block_parents: *id002 + sm_links: &id043 + - [0, 2] + - [0, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id043 +- block_parents: *id002 + sm_links: &id044 + - [0, 2] + - [0, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id044 +- block_parents: *id002 + sm_links: &id045 + - [0, 2] + - [0, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id045 +- block_parents: *id002 + sm_links: &id046 + - [0, 2] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id046 +- block_parents: *id002 + sm_links: &id047 + - [0, 2] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id047 +- block_parents: *id002 + sm_links: &id048 + - [0, 1] + - [1, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id048 +- block_parents: *id002 + sm_links: &id049 + - [0, 1] + - [1, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id049 +- block_parents: *id002 + sm_links: &id050 + - [0, 1] + - [1, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id050 +- block_parents: *id002 + sm_links: &id051 + - [0, 1] + - [1, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id051 +- block_parents: *id002 + sm_links: &id052 + - [0, 1] + - [1, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id052 +- block_parents: *id002 + sm_links: &id053 + - [0, 2] + - [2, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id053 +- block_parents: *id002 + sm_links: &id054 + - [0, 2] + - [2, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id054 +- block_parents: *id002 + sm_links: &id055 + - [0, 2] + - [2, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id055 +- block_parents: *id002 + sm_links: &id056 + - [0, 2] + - [2, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id056 +- block_parents: *id002 + sm_links: &id057 + - [0, 2] + - [2, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id057 +- block_parents: [0, 0, 1, 0, 0, 0] + sm_links: &id058 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 0, 1, 0, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 0, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 0, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 0, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 1, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 2, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 0] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 0, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 1, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 2, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 1] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 0, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 1, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 2, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 2] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 0, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 1, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 2, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 3] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 0, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 1, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 2, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 0, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 0, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 1, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 1, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 0, 2, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 4] + sm_links: *id058 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0] + sm_links: &id059 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 3, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 3, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 4, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 4, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 2, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 2, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 2, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 2, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 2, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 2, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 4, 3, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 5, 3, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 3, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 6, 3, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 3, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 2, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 3, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 2, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 3, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 2, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 3, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 4, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 2, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 2, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 3, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 3, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 3, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 3, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 2, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 2, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 2, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 3, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 3, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 3, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 2, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 2, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 2, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 2, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 2, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 3, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 4, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 4, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 5, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 3, 5, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 2, 4, 5, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 3, 2, 4, 6, 4, 1, 0] + sm_links: *id059 +- block_parents: [0, 0, 1, 2, 3, 2, 3, 4, 6, 4, 1, 0] + sm_links: *id059 diff --git a/tests/generators/compliance_runners/fork_choice/small/test_gen.yaml b/tests/generators/compliance_runners/fork_choice/small/test_gen.yaml new file mode 100644 index 0000000000..41a7ad9332 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/small/test_gen.yaml @@ -0,0 +1,38 @@ +block_tree_test: + test_type: block_tree + instances: block_tree_tree.yaml # 128 + seed: 123 + nr_variations: 2 + nr_mutations: 1 +block_weight_test: + test_type: block_tree + instances: block_tree_other.yaml # 4 + seed: 123 + nr_variations: 32 + nr_mutations: 1 +shuffling_test: + test_type: block_tree + instances: block_tree_other.yaml # 4 + seed: 123 + nr_variations: 2 + nr_mutations: 31 +attester_slashing_test: + test_type: block_tree + instances: block_tree_other.yaml # 4 + seed: 123 + nr_variations: 8 + nr_mutations: 3 + with_attester_slashings: true +invalid_message_test: + test_type: block_tree + instances: block_tree_other.yaml # 4 + seed: 123 + nr_variations: 16 + nr_mutations: 1 + with_invalid_messages: true +block_cover_test: + test_type: block_cover + instances: block_cover.yaml # 24 + seed: 456 + nr_variations: 2 + nr_mutations: 3 diff --git a/tests/generators/compliance_runners/fork_choice/standard/block_cover.yaml b/tests/generators/compliance_runners/fork_choice/standard/block_cover.yaml new file mode 100644 index 0000000000..331079855a --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/standard/block_cover.yaml @@ -0,0 +1,540 @@ +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 1 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 1 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1, 1] + current_epoch: 1 + current_justifications: [false, false, false, false] + parents: [0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1, 1, 1] + current_epoch: 1 + current_justifications: [false, false, false, false, false] + parents: [0, 0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false, false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 1 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, true] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, true] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 3 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1] + current_epoch: 3 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, true, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1, 1, 1] + current_epoch: 3 + current_justifications: [false, false, false, false] + parents: [0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false, false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 3 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3, 3] + current_epoch: 3 + current_justifications: [false, false, false, false] + parents: [0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3, 3, 3] + current_epoch: 3 + current_justifications: [false, false, false, false, false] + parents: [0, 0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, false] + parents: [0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3, 3] + current_epoch: 5 + current_justifications: [false, false, false, false] + parents: [0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3, 3, 3] + current_epoch: 5 + current_justifications: [false, false, false, false, false] + parents: [0, 0, 0, 0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false, false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, true] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, true] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 diff --git a/tests/generators/compliance_runners/fork_choice/standard/block_tree_other.yaml b/tests/generators/compliance_runners/fork_choice/standard/block_tree_other.yaml new file mode 100644 index 0000000000..ffdc7dc409 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/standard/block_tree_other.yaml @@ -0,0 +1,20 @@ +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 4, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 4, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 4, 2, 1, 0] + sm_links: *id001 diff --git a/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree.yaml b/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree.yaml new file mode 100644 index 0000000000..0a698b7aa7 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree.yaml @@ -0,0 +1,2280 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id003 [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id005 [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id006 [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id007 [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 5] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id005 + sm_links: *id004 +- block_parents: *id006 + sm_links: *id004 +- block_parents: *id007 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id008 +- block_parents: *id005 + sm_links: *id008 +- block_parents: *id006 + sm_links: *id008 +- block_parents: *id007 + sm_links: *id008 +- block_parents: *id002 + sm_links: &id009 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id009 +- block_parents: *id005 + sm_links: *id009 +- block_parents: *id006 + sm_links: *id009 +- block_parents: *id007 + sm_links: *id009 +- block_parents: *id002 + sm_links: &id010 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id010 +- block_parents: *id005 + sm_links: *id010 +- block_parents: *id006 + sm_links: *id010 +- block_parents: *id007 + sm_links: *id010 +- block_parents: *id002 + sm_links: &id011 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id011 +- block_parents: *id005 + sm_links: *id011 +- block_parents: *id006 + sm_links: *id011 +- block_parents: *id007 + sm_links: *id011 +- block_parents: *id002 + sm_links: &id012 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id012 +- block_parents: *id005 + sm_links: *id012 +- block_parents: *id006 + sm_links: *id012 +- block_parents: *id007 + sm_links: *id012 +- block_parents: *id002 + sm_links: &id013 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id013 +- block_parents: *id005 + sm_links: *id013 +- block_parents: *id006 + sm_links: *id013 +- block_parents: *id007 + sm_links: *id013 +- block_parents: *id002 + sm_links: &id014 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id014 +- block_parents: *id005 + sm_links: *id014 +- block_parents: *id006 + sm_links: *id014 +- block_parents: *id007 + sm_links: *id014 +- block_parents: *id002 + sm_links: &id015 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id015 +- block_parents: *id005 + sm_links: *id015 +- block_parents: *id006 + sm_links: *id015 +- block_parents: *id007 + sm_links: *id015 +- block_parents: *id002 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id016 +- block_parents: *id005 + sm_links: *id016 +- block_parents: *id006 + sm_links: *id016 +- block_parents: *id007 + sm_links: *id016 +- block_parents: *id002 + sm_links: &id017 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id017 +- block_parents: *id005 + sm_links: *id017 +- block_parents: *id006 + sm_links: *id017 +- block_parents: *id007 + sm_links: *id017 +- block_parents: *id002 + sm_links: &id018 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id018 +- block_parents: *id005 + sm_links: *id018 +- block_parents: *id006 + sm_links: *id018 +- block_parents: *id007 + sm_links: *id018 +- block_parents: *id002 + sm_links: &id019 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id019 +- block_parents: *id005 + sm_links: *id019 +- block_parents: *id006 + sm_links: *id019 +- block_parents: *id007 + sm_links: *id019 +- block_parents: *id002 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id020 +- block_parents: *id005 + sm_links: *id020 +- block_parents: *id006 + sm_links: *id020 +- block_parents: *id007 + sm_links: *id020 +- block_parents: *id002 + sm_links: &id021 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id021 +- block_parents: *id005 + sm_links: *id021 +- block_parents: *id006 + sm_links: *id021 +- block_parents: *id007 + sm_links: *id021 +- block_parents: *id002 + sm_links: &id022 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id022 +- block_parents: *id005 + sm_links: *id022 +- block_parents: *id006 + sm_links: *id022 +- block_parents: *id007 + sm_links: *id022 +- block_parents: *id002 + sm_links: &id023 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id023 +- block_parents: *id005 + sm_links: *id023 +- block_parents: *id006 + sm_links: *id023 +- block_parents: *id007 + sm_links: *id023 +- block_parents: *id002 + sm_links: &id024 + - [0, 1] + - [0, 2] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id024 +- block_parents: *id005 + sm_links: *id024 +- block_parents: *id006 + sm_links: *id024 +- block_parents: *id007 + sm_links: *id024 +- block_parents: *id002 + sm_links: &id025 + - [0, 1] + - [0, 2] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id025 +- block_parents: *id005 + sm_links: *id025 +- block_parents: *id006 + sm_links: *id025 +- block_parents: *id007 + sm_links: *id025 +- block_parents: *id002 + sm_links: &id026 + - [0, 1] + - [0, 2] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id026 +- block_parents: *id005 + sm_links: *id026 +- block_parents: *id006 + sm_links: *id026 +- block_parents: *id007 + sm_links: *id026 +- block_parents: *id002 + sm_links: &id027 + - [0, 1] + - [0, 2] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id027 +- block_parents: *id005 + sm_links: *id027 +- block_parents: *id006 + sm_links: *id027 +- block_parents: *id007 + sm_links: *id027 +- block_parents: *id002 + sm_links: &id028 + - [0, 1] + - [0, 2] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id028 +- block_parents: *id005 + sm_links: *id028 +- block_parents: *id006 + sm_links: *id028 +- block_parents: *id007 + sm_links: *id028 +- block_parents: *id002 + sm_links: &id029 + - [0, 1] + - [0, 2] + - [1, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id029 +- block_parents: *id005 + sm_links: *id029 +- block_parents: *id006 + sm_links: *id029 +- block_parents: *id007 + sm_links: *id029 +- block_parents: *id002 + sm_links: &id030 + - [0, 1] + - [0, 2] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id030 +- block_parents: *id005 + sm_links: *id030 +- block_parents: *id006 + sm_links: *id030 +- block_parents: *id007 + sm_links: *id030 +- block_parents: *id002 + sm_links: &id031 + - [0, 1] + - [0, 2] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id031 +- block_parents: *id005 + sm_links: *id031 +- block_parents: *id006 + sm_links: *id031 +- block_parents: *id007 + sm_links: *id031 +- block_parents: *id002 + sm_links: &id032 + - [0, 1] + - [0, 2] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id032 +- block_parents: *id005 + sm_links: *id032 +- block_parents: *id006 + sm_links: *id032 +- block_parents: *id007 + sm_links: *id032 +- block_parents: *id002 + sm_links: &id033 + - [0, 1] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id033 +- block_parents: *id005 + sm_links: *id033 +- block_parents: *id006 + sm_links: *id033 +- block_parents: *id007 + sm_links: *id033 +- block_parents: *id002 + sm_links: &id034 + - [0, 2] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id034 +- block_parents: *id005 + sm_links: *id034 +- block_parents: *id006 + sm_links: *id034 +- block_parents: *id007 + sm_links: *id034 +- block_parents: *id002 + sm_links: &id035 + - [0, 1] + - [0, 3] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id035 +- block_parents: *id005 + sm_links: *id035 +- block_parents: *id006 + sm_links: *id035 +- block_parents: *id007 + sm_links: *id035 +- block_parents: *id002 + sm_links: &id036 + - [0, 1] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id036 +- block_parents: *id005 + sm_links: *id036 +- block_parents: *id006 + sm_links: *id036 +- block_parents: *id007 + sm_links: *id036 +- block_parents: *id002 + sm_links: &id037 + - [0, 1] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id037 +- block_parents: *id005 + sm_links: *id037 +- block_parents: *id006 + sm_links: *id037 +- block_parents: *id007 + sm_links: *id037 +- block_parents: *id002 + sm_links: &id038 + - [0, 2] + - [0, 3] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id038 +- block_parents: *id005 + sm_links: *id038 +- block_parents: *id006 + sm_links: *id038 +- block_parents: *id007 + sm_links: *id038 +- block_parents: *id002 + sm_links: &id039 + - [0, 2] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id039 +- block_parents: *id005 + sm_links: *id039 +- block_parents: *id006 + sm_links: *id039 +- block_parents: *id007 + sm_links: *id039 +- block_parents: *id002 + sm_links: &id040 + - [0, 2] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id040 +- block_parents: *id005 + sm_links: *id040 +- block_parents: *id006 + sm_links: *id040 +- block_parents: *id007 + sm_links: *id040 +- block_parents: *id002 + sm_links: &id041 + - [0, 1] + - [0, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id041 +- block_parents: *id005 + sm_links: *id041 +- block_parents: *id006 + sm_links: *id041 +- block_parents: *id007 + sm_links: *id041 +- block_parents: *id002 + sm_links: &id042 + - [0, 1] + - [0, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id042 +- block_parents: *id005 + sm_links: *id042 +- block_parents: *id006 + sm_links: *id042 +- block_parents: *id007 + sm_links: *id042 +- block_parents: *id002 + sm_links: &id043 + - [0, 1] + - [0, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id043 +- block_parents: *id005 + sm_links: *id043 +- block_parents: *id006 + sm_links: *id043 +- block_parents: *id007 + sm_links: *id043 +- block_parents: *id002 + sm_links: &id044 + - [0, 1] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id044 +- block_parents: *id005 + sm_links: *id044 +- block_parents: *id006 + sm_links: *id044 +- block_parents: *id007 + sm_links: *id044 +- block_parents: *id002 + sm_links: &id045 + - [0, 1] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id045 +- block_parents: *id005 + sm_links: *id045 +- block_parents: *id006 + sm_links: *id045 +- block_parents: *id007 + sm_links: *id045 +- block_parents: *id002 + sm_links: &id046 + - [0, 2] + - [0, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id046 +- block_parents: *id005 + sm_links: *id046 +- block_parents: *id006 + sm_links: *id046 +- block_parents: *id007 + sm_links: *id046 +- block_parents: *id002 + sm_links: &id047 + - [0, 2] + - [0, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id047 +- block_parents: *id005 + sm_links: *id047 +- block_parents: *id006 + sm_links: *id047 +- block_parents: *id007 + sm_links: *id047 +- block_parents: *id002 + sm_links: &id048 + - [0, 2] + - [0, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id048 +- block_parents: *id005 + sm_links: *id048 +- block_parents: *id006 + sm_links: *id048 +- block_parents: *id007 + sm_links: *id048 +- block_parents: *id002 + sm_links: &id049 + - [0, 2] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id049 +- block_parents: *id005 + sm_links: *id049 +- block_parents: *id006 + sm_links: *id049 +- block_parents: *id007 + sm_links: *id049 +- block_parents: *id002 + sm_links: &id050 + - [0, 2] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id050 +- block_parents: *id005 + sm_links: *id050 +- block_parents: *id006 + sm_links: *id050 +- block_parents: *id007 + sm_links: *id050 +- block_parents: *id002 + sm_links: &id051 + - [0, 1] + - [1, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id051 +- block_parents: *id005 + sm_links: *id051 +- block_parents: *id006 + sm_links: *id051 +- block_parents: *id007 + sm_links: *id051 +- block_parents: *id002 + sm_links: &id052 + - [0, 1] + - [1, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id052 +- block_parents: *id005 + sm_links: *id052 +- block_parents: *id006 + sm_links: *id052 +- block_parents: *id007 + sm_links: *id052 +- block_parents: *id002 + sm_links: &id053 + - [0, 1] + - [1, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id053 +- block_parents: *id005 + sm_links: *id053 +- block_parents: *id006 + sm_links: *id053 +- block_parents: *id007 + sm_links: *id053 +- block_parents: *id002 + sm_links: &id054 + - [0, 1] + - [1, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id054 +- block_parents: *id005 + sm_links: *id054 +- block_parents: *id006 + sm_links: *id054 +- block_parents: *id007 + sm_links: *id054 +- block_parents: *id002 + sm_links: &id055 + - [0, 1] + - [1, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id055 +- block_parents: *id005 + sm_links: *id055 +- block_parents: *id006 + sm_links: *id055 +- block_parents: *id007 + sm_links: *id055 +- block_parents: *id002 + sm_links: &id056 + - [0, 2] + - [2, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id056 +- block_parents: *id005 + sm_links: *id056 +- block_parents: *id006 + sm_links: *id056 +- block_parents: *id007 + sm_links: *id056 +- block_parents: *id002 + sm_links: &id057 + - [0, 2] + - [2, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id057 +- block_parents: *id005 + sm_links: *id057 +- block_parents: *id006 + sm_links: *id057 +- block_parents: *id007 + sm_links: *id057 +- block_parents: *id002 + sm_links: &id058 + - [0, 2] + - [2, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id058 +- block_parents: *id005 + sm_links: *id058 +- block_parents: *id006 + sm_links: *id058 +- block_parents: *id007 + sm_links: *id058 +- block_parents: *id002 + sm_links: &id059 + - [0, 2] + - [2, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id059 +- block_parents: *id005 + sm_links: *id059 +- block_parents: *id006 + sm_links: *id059 +- block_parents: *id007 + sm_links: *id059 +- block_parents: *id002 + sm_links: &id060 + - [0, 2] + - [2, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id060 +- block_parents: *id005 + sm_links: *id060 +- block_parents: *id006 + sm_links: *id060 +- block_parents: *id007 + sm_links: *id060 +- block_parents: [0, 0, 1, 0, 0, 0] + sm_links: &id061 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 0, 1, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 1, 0] + sm_links: &id062 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id063 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 diff --git a/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree_2.yaml b/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree_2.yaml new file mode 100644 index 0000000000..8a5deccc6d --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/standard/block_tree_tree_2.yaml @@ -0,0 +1,8424 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 4] +- block_parents: &id003 [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id005 [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id006 [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: &id007 [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 2] + - [0, 3] + - [0, 5] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id005 + sm_links: *id004 +- block_parents: *id006 + sm_links: *id004 +- block_parents: *id007 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id008 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id008 +- block_parents: *id005 + sm_links: *id008 +- block_parents: *id006 + sm_links: *id008 +- block_parents: *id007 + sm_links: *id008 +- block_parents: *id002 + sm_links: &id009 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id009 +- block_parents: *id005 + sm_links: *id009 +- block_parents: *id006 + sm_links: *id009 +- block_parents: *id007 + sm_links: *id009 +- block_parents: *id002 + sm_links: &id010 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id010 +- block_parents: *id005 + sm_links: *id010 +- block_parents: *id006 + sm_links: *id010 +- block_parents: *id007 + sm_links: *id010 +- block_parents: *id002 + sm_links: &id011 + - [0, 1] + - [0, 2] + - [0, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id011 +- block_parents: *id005 + sm_links: *id011 +- block_parents: *id006 + sm_links: *id011 +- block_parents: *id007 + sm_links: *id011 +- block_parents: *id002 + sm_links: &id012 + - [0, 1] + - [0, 2] + - [0, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id012 +- block_parents: *id005 + sm_links: *id012 +- block_parents: *id006 + sm_links: *id012 +- block_parents: *id007 + sm_links: *id012 +- block_parents: *id002 + sm_links: &id013 + - [0, 1] + - [0, 2] + - [0, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id013 +- block_parents: *id005 + sm_links: *id013 +- block_parents: *id006 + sm_links: *id013 +- block_parents: *id007 + sm_links: *id013 +- block_parents: *id002 + sm_links: &id014 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 4] +- block_parents: *id003 + sm_links: *id014 +- block_parents: *id005 + sm_links: *id014 +- block_parents: *id006 + sm_links: *id014 +- block_parents: *id007 + sm_links: *id014 +- block_parents: *id002 + sm_links: &id015 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id015 +- block_parents: *id005 + sm_links: *id015 +- block_parents: *id006 + sm_links: *id015 +- block_parents: *id007 + sm_links: *id015 +- block_parents: *id002 + sm_links: &id016 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id016 +- block_parents: *id005 + sm_links: *id016 +- block_parents: *id006 + sm_links: *id016 +- block_parents: *id007 + sm_links: *id016 +- block_parents: *id002 + sm_links: &id017 + - [0, 1] + - [0, 2] + - [1, 3] + - [1, 5] +- block_parents: *id003 + sm_links: *id017 +- block_parents: *id005 + sm_links: *id017 +- block_parents: *id006 + sm_links: *id017 +- block_parents: *id007 + sm_links: *id017 +- block_parents: *id002 + sm_links: &id018 + - [0, 1] + - [0, 2] + - [1, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id018 +- block_parents: *id005 + sm_links: *id018 +- block_parents: *id006 + sm_links: *id018 +- block_parents: *id007 + sm_links: *id018 +- block_parents: *id002 + sm_links: &id019 + - [0, 1] + - [0, 2] + - [1, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id019 +- block_parents: *id005 + sm_links: *id019 +- block_parents: *id006 + sm_links: *id019 +- block_parents: *id007 + sm_links: *id019 +- block_parents: *id002 + sm_links: &id020 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 4] +- block_parents: *id003 + sm_links: *id020 +- block_parents: *id005 + sm_links: *id020 +- block_parents: *id006 + sm_links: *id020 +- block_parents: *id007 + sm_links: *id020 +- block_parents: *id002 + sm_links: &id021 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: *id003 + sm_links: *id021 +- block_parents: *id005 + sm_links: *id021 +- block_parents: *id006 + sm_links: *id021 +- block_parents: *id007 + sm_links: *id021 +- block_parents: *id002 + sm_links: &id022 + - [0, 1] + - [0, 2] + - [2, 3] + - [2, 5] +- block_parents: *id003 + sm_links: *id022 +- block_parents: *id005 + sm_links: *id022 +- block_parents: *id006 + sm_links: *id022 +- block_parents: *id007 + sm_links: *id022 +- block_parents: *id002 + sm_links: &id023 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 5] +- block_parents: *id003 + sm_links: *id023 +- block_parents: *id005 + sm_links: *id023 +- block_parents: *id006 + sm_links: *id023 +- block_parents: *id007 + sm_links: *id023 +- block_parents: *id002 + sm_links: &id024 + - [0, 1] + - [0, 2] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id024 +- block_parents: *id005 + sm_links: *id024 +- block_parents: *id006 + sm_links: *id024 +- block_parents: *id007 + sm_links: *id024 +- block_parents: *id002 + sm_links: &id025 + - [0, 1] + - [0, 2] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id025 +- block_parents: *id005 + sm_links: *id025 +- block_parents: *id006 + sm_links: *id025 +- block_parents: *id007 + sm_links: *id025 +- block_parents: *id002 + sm_links: &id026 + - [0, 1] + - [0, 2] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id026 +- block_parents: *id005 + sm_links: *id026 +- block_parents: *id006 + sm_links: *id026 +- block_parents: *id007 + sm_links: *id026 +- block_parents: *id002 + sm_links: &id027 + - [0, 1] + - [0, 2] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id027 +- block_parents: *id005 + sm_links: *id027 +- block_parents: *id006 + sm_links: *id027 +- block_parents: *id007 + sm_links: *id027 +- block_parents: *id002 + sm_links: &id028 + - [0, 1] + - [0, 2] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id028 +- block_parents: *id005 + sm_links: *id028 +- block_parents: *id006 + sm_links: *id028 +- block_parents: *id007 + sm_links: *id028 +- block_parents: *id002 + sm_links: &id029 + - [0, 1] + - [0, 2] + - [1, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id029 +- block_parents: *id005 + sm_links: *id029 +- block_parents: *id006 + sm_links: *id029 +- block_parents: *id007 + sm_links: *id029 +- block_parents: *id002 + sm_links: &id030 + - [0, 1] + - [0, 2] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id030 +- block_parents: *id005 + sm_links: *id030 +- block_parents: *id006 + sm_links: *id030 +- block_parents: *id007 + sm_links: *id030 +- block_parents: *id002 + sm_links: &id031 + - [0, 1] + - [0, 2] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id031 +- block_parents: *id005 + sm_links: *id031 +- block_parents: *id006 + sm_links: *id031 +- block_parents: *id007 + sm_links: *id031 +- block_parents: *id002 + sm_links: &id032 + - [0, 1] + - [0, 2] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id032 +- block_parents: *id005 + sm_links: *id032 +- block_parents: *id006 + sm_links: *id032 +- block_parents: *id007 + sm_links: *id032 +- block_parents: *id002 + sm_links: &id033 + - [0, 1] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id033 +- block_parents: *id005 + sm_links: *id033 +- block_parents: *id006 + sm_links: *id033 +- block_parents: *id007 + sm_links: *id033 +- block_parents: *id002 + sm_links: &id034 + - [0, 2] + - [0, 3] + - [0, 4] + - [0, 5] +- block_parents: *id003 + sm_links: *id034 +- block_parents: *id005 + sm_links: *id034 +- block_parents: *id006 + sm_links: *id034 +- block_parents: *id007 + sm_links: *id034 +- block_parents: *id002 + sm_links: &id035 + - [0, 1] + - [0, 3] + - [0, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id035 +- block_parents: *id005 + sm_links: *id035 +- block_parents: *id006 + sm_links: *id035 +- block_parents: *id007 + sm_links: *id035 +- block_parents: *id002 + sm_links: &id036 + - [0, 1] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id036 +- block_parents: *id005 + sm_links: *id036 +- block_parents: *id006 + sm_links: *id036 +- block_parents: *id007 + sm_links: *id036 +- block_parents: *id002 + sm_links: &id037 + - [0, 1] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id037 +- block_parents: *id005 + sm_links: *id037 +- block_parents: *id006 + sm_links: *id037 +- block_parents: *id007 + sm_links: *id037 +- block_parents: *id002 + sm_links: &id038 + - [0, 2] + - [0, 3] + - [0, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id038 +- block_parents: *id005 + sm_links: *id038 +- block_parents: *id006 + sm_links: *id038 +- block_parents: *id007 + sm_links: *id038 +- block_parents: *id002 + sm_links: &id039 + - [0, 2] + - [0, 3] + - [0, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id039 +- block_parents: *id005 + sm_links: *id039 +- block_parents: *id006 + sm_links: *id039 +- block_parents: *id007 + sm_links: *id039 +- block_parents: *id002 + sm_links: &id040 + - [0, 2] + - [0, 3] + - [0, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id040 +- block_parents: *id005 + sm_links: *id040 +- block_parents: *id006 + sm_links: *id040 +- block_parents: *id007 + sm_links: *id040 +- block_parents: *id002 + sm_links: &id041 + - [0, 1] + - [0, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id041 +- block_parents: *id005 + sm_links: *id041 +- block_parents: *id006 + sm_links: *id041 +- block_parents: *id007 + sm_links: *id041 +- block_parents: *id002 + sm_links: &id042 + - [0, 1] + - [0, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id042 +- block_parents: *id005 + sm_links: *id042 +- block_parents: *id006 + sm_links: *id042 +- block_parents: *id007 + sm_links: *id042 +- block_parents: *id002 + sm_links: &id043 + - [0, 1] + - [0, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id043 +- block_parents: *id005 + sm_links: *id043 +- block_parents: *id006 + sm_links: *id043 +- block_parents: *id007 + sm_links: *id043 +- block_parents: *id002 + sm_links: &id044 + - [0, 1] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id044 +- block_parents: *id005 + sm_links: *id044 +- block_parents: *id006 + sm_links: *id044 +- block_parents: *id007 + sm_links: *id044 +- block_parents: *id002 + sm_links: &id045 + - [0, 1] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id045 +- block_parents: *id005 + sm_links: *id045 +- block_parents: *id006 + sm_links: *id045 +- block_parents: *id007 + sm_links: *id045 +- block_parents: *id002 + sm_links: &id046 + - [0, 2] + - [0, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id046 +- block_parents: *id005 + sm_links: *id046 +- block_parents: *id006 + sm_links: *id046 +- block_parents: *id007 + sm_links: *id046 +- block_parents: *id002 + sm_links: &id047 + - [0, 2] + - [0, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id047 +- block_parents: *id005 + sm_links: *id047 +- block_parents: *id006 + sm_links: *id047 +- block_parents: *id007 + sm_links: *id047 +- block_parents: *id002 + sm_links: &id048 + - [0, 2] + - [0, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id048 +- block_parents: *id005 + sm_links: *id048 +- block_parents: *id006 + sm_links: *id048 +- block_parents: *id007 + sm_links: *id048 +- block_parents: *id002 + sm_links: &id049 + - [0, 2] + - [0, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id049 +- block_parents: *id005 + sm_links: *id049 +- block_parents: *id006 + sm_links: *id049 +- block_parents: *id007 + sm_links: *id049 +- block_parents: *id002 + sm_links: &id050 + - [0, 2] + - [0, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id050 +- block_parents: *id005 + sm_links: *id050 +- block_parents: *id006 + sm_links: *id050 +- block_parents: *id007 + sm_links: *id050 +- block_parents: *id002 + sm_links: &id051 + - [0, 1] + - [1, 3] + - [1, 4] + - [1, 5] +- block_parents: *id003 + sm_links: *id051 +- block_parents: *id005 + sm_links: *id051 +- block_parents: *id006 + sm_links: *id051 +- block_parents: *id007 + sm_links: *id051 +- block_parents: *id002 + sm_links: &id052 + - [0, 1] + - [1, 3] + - [1, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id052 +- block_parents: *id005 + sm_links: *id052 +- block_parents: *id006 + sm_links: *id052 +- block_parents: *id007 + sm_links: *id052 +- block_parents: *id002 + sm_links: &id053 + - [0, 1] + - [1, 3] + - [1, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id053 +- block_parents: *id005 + sm_links: *id053 +- block_parents: *id006 + sm_links: *id053 +- block_parents: *id007 + sm_links: *id053 +- block_parents: *id002 + sm_links: &id054 + - [0, 1] + - [1, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id054 +- block_parents: *id005 + sm_links: *id054 +- block_parents: *id006 + sm_links: *id054 +- block_parents: *id007 + sm_links: *id054 +- block_parents: *id002 + sm_links: &id055 + - [0, 1] + - [1, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id055 +- block_parents: *id005 + sm_links: *id055 +- block_parents: *id006 + sm_links: *id055 +- block_parents: *id007 + sm_links: *id055 +- block_parents: *id002 + sm_links: &id056 + - [0, 2] + - [2, 3] + - [2, 4] + - [2, 5] +- block_parents: *id003 + sm_links: *id056 +- block_parents: *id005 + sm_links: *id056 +- block_parents: *id006 + sm_links: *id056 +- block_parents: *id007 + sm_links: *id056 +- block_parents: *id002 + sm_links: &id057 + - [0, 2] + - [2, 3] + - [2, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id057 +- block_parents: *id005 + sm_links: *id057 +- block_parents: *id006 + sm_links: *id057 +- block_parents: *id007 + sm_links: *id057 +- block_parents: *id002 + sm_links: &id058 + - [0, 2] + - [2, 3] + - [2, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id058 +- block_parents: *id005 + sm_links: *id058 +- block_parents: *id006 + sm_links: *id058 +- block_parents: *id007 + sm_links: *id058 +- block_parents: *id002 + sm_links: &id059 + - [0, 2] + - [2, 3] + - [3, 4] + - [3, 5] +- block_parents: *id003 + sm_links: *id059 +- block_parents: *id005 + sm_links: *id059 +- block_parents: *id006 + sm_links: *id059 +- block_parents: *id007 + sm_links: *id059 +- block_parents: *id002 + sm_links: &id060 + - [0, 2] + - [2, 3] + - [3, 4] + - [4, 5] +- block_parents: *id003 + sm_links: *id060 +- block_parents: *id005 + sm_links: *id060 +- block_parents: *id006 + sm_links: *id060 +- block_parents: *id007 + sm_links: *id060 +- block_parents: [0, 0, 1, 0, 0, 0] + sm_links: &id061 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 0, 1, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 0] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 1] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 2] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 3] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 0, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 1, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 2, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 0, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 0, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 1, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 0, 2, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 2, 3, 4] + sm_links: *id061 +- block_parents: [0, 0, 1, 1, 1, 0, 0] + sm_links: &id062 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 1, 0, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 0] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 1] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 2] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 3] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 4] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 0, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 1, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 2, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 3, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 0, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 1, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 2, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 0, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 0, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 1, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 1, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 0, 2, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 5] + sm_links: *id062 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id063 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 4, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 5, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 7, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 4, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 4, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 4, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 4, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 4, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 4, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 4, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 4, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 5, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 5, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 5, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 6, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 6, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 6, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 7, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 7, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 4, 6, 7, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 3, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 3, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 5, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 5, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 5, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 5, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 5, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 6, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 7, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 8, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 4, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 3, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 3, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 3, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 3, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 4, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 4, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 4, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 4, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 4, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 5, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 5, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 4, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 6, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 4, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 7, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 4, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 5, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 6, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 7, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 3, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 3, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 3, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 4, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 4, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 4, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 4, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 4, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 3, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 4, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 4, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 3, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 4, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 5, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 5, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 3, 6, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 4, 6, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 8, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 5, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 5, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 6, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 6, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 6, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 7, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 7, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 7, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 5, 8, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 6, 8, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 8, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 7, 8, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 5, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 5, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 6, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 6, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 6, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 6, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 7, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 7, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 7, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 7, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 7, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 5, 8, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 6, 8, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 6, 8, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 5, 7, 8, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 3, 5, 6, 7, 8, 4, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 3, 5, 9, 4, 2, 1, 0] + sm_links: *id063 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 3, 5, 9, 4, 2, 1, 0] + sm_links: *id063 diff --git a/tests/generators/compliance_runners/fork_choice/standard/test_gen.yaml b/tests/generators/compliance_runners/fork_choice/standard/test_gen.yaml new file mode 100644 index 0000000000..0ad53ff280 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/standard/test_gen.yaml @@ -0,0 +1,38 @@ +block_tree_test: + test_type: block_tree + instances: block_tree_tree.yaml # 1024 + seed: 123 + nr_variations: 2 + nr_mutations: 1 +block_weight_test: + test_type: block_tree + instances: block_tree_other.yaml # 8 + seed: 123 + nr_variations: 64 + nr_mutations: 3 +shuffling_test: + test_type: block_tree + instances: block_tree_other.yaml # 8 + seed: 6673 + nr_variations: 4 + nr_mutations: 63 +attester_slashing_test: + test_type: block_tree + instances: block_tree_other.yaml # 8 + seed: 123 + nr_variations: 16 + nr_mutations: 7 + with_attester_slashings: true +invalid_message_test: + test_type: block_tree + instances: block_tree_other.yaml # 8 + seed: 123 + nr_variations: 32 + nr_mutations: 3 + with_invalid_messages: true +block_cover_test: + test_type: block_cover + instances: block_cover.yaml # 60 + seed: 456 + nr_variations: 5 + nr_mutations: 9 diff --git a/tests/generators/compliance_runners/fork_choice/test_gen.py b/tests/generators/compliance_runners/fork_choice/test_gen.py new file mode 100644 index 0000000000..4af376b694 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/test_gen.py @@ -0,0 +1,78 @@ +from os import path + +from eth2spec.gen_helpers.gen_base import gen_runner +from eth2spec.gen_helpers.gen_base.args import create_arg_parser +from eth2spec.test.helpers.constants import ELECTRA, MINIMAL + +from .instantiators.test_case import enumerate_test_cases, prepare_bls + +default_forks = [ELECTRA] +default_presets = [MINIMAL] + + +def main(): + arg_parser = create_arg_parser() + + arg_parser.add_argument( + "--fc-gen-debug", + dest="fc_gen_debug", + action="store_true", + default=False, + required=False, + help="If set provides debug output and enable additional checks for generated chains", + ) + arg_parser.add_argument( + "--fc-gen-config", + dest="fc_gen_config", + type=str, + required=False, + choices=["tiny", "small", "standard"], + help="Name of test generator configuration: tiny, small or standard", + ) + arg_parser.add_argument( + "--fc-gen-config-path", + dest="fc_gen_config_path", + type=str, + required=False, + help="Path to a file with test generator configurations", + ) + arg_parser.add_argument( + "--fc-gen-multi-processing", + dest="fc_gen_multi_processing", + action="store_true", + default=False, + required=False, + help="If set generates tests in the multi-processing mode", + ) + + # change default value for `threads` to detect whether it is explicitly set + default_threads = arg_parser.get_default("threads") + arg_parser.set_defaults(threads=0) + + args = arg_parser.parse_args() + + if args.fc_gen_multi_processing or args.threads != 0: + if args.threads == 0: + args.threads = default_threads + print("generating tests in multi-processing mode") + else: + args.threads = 1 + print("generating tests in single process mode") + + forks = default_forks if args.forks == [] else args.forks + presets = default_presets if args.presets == [] else args.presets + + if args.fc_gen_config_path is not None: + config_path = args.fc_gen_config_path + elif args.fc_gen_config is not None: + config_path = path.join(path.dirname(__file__), args.fc_gen_config, "test_gen.yaml") + else: + raise ValueError("Neither neither fc-gen-config not fc-gen-config-path specified") + + prepare_bls() + test_cases = enumerate_test_cases(config_path, forks, presets, args.fc_gen_debug) + gen_runner.run_generator(test_cases, args) + + +if __name__ == "__main__": + main() diff --git a/tests/generators/compliance_runners/fork_choice/test_run.py b/tests/generators/compliance_runners/fork_choice/test_run.py new file mode 100644 index 0000000000..d312f61861 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/test_run.py @@ -0,0 +1,194 @@ +import argparse +import os +from collections import namedtuple +from collections.abc import Iterable +from glob import glob +from pathlib import Path + +from pathos.multiprocessing import ProcessingPool as Pool +from ruamel.yaml import YAML +from snappy import uncompress +from tqdm import tqdm + +from eth2spec.test.helpers.specs import spec_targets +from eth2spec.utils import bls + +bls.bls_active = False + + +def read_yaml(fp): + with open(fp) as f: + yaml = YAML(typ="safe") + return yaml.load(f.read()) + + +def read_ssz_snappy(fp): + with open(fp, "rb") as f: + res = uncompress(f.read()) + return res + + +def get_test_case(spec, td): + def get_prefix(p): + return p[p.rindex("/") + 1 : p.rindex(".")] + + return ( + read_yaml(f"{td}/meta.yaml"), + spec.BeaconBlock.decode_bytes(read_ssz_snappy(f"{td}/anchor_block.ssz_snappy")), + spec.BeaconState.decode_bytes(read_ssz_snappy(f"{td}/anchor_state.ssz_snappy")), + { + get_prefix(b): spec.SignedBeaconBlock.decode_bytes(read_ssz_snappy(b)) + for b in glob(f"{td}/block_*.ssz_snappy") + }, + { + get_prefix(b): spec.Attestation.decode_bytes(read_ssz_snappy(b)) + for b in glob(f"{td}/attestation_*.ssz_snappy") + }, + { + get_prefix(b): spec.AttesterSlashing.decode_bytes(read_ssz_snappy(b)) + for b in glob(f"{td}/attester_slashing_*.ssz_snappy") + }, + read_yaml(f"{td}/steps.yaml"), + ) + + +TestInfo = namedtuple( + "TestInfo", + [ + "preset", + "fork", + "test_dir", + ], +) + + +def run_test(test_info): + preset, fork, test_dir = test_info + spec = spec_targets[preset][fork] + meta, anchor_block, anchor_state, blocks, atts, slashings, steps = get_test_case(spec, test_dir) + store = spec.get_forkchoice_store(anchor_state, anchor_block) + for step in steps: + if "tick" in step: + time = step["tick"] + spec.on_tick(store, time) + elif "block" in step: + block_id = step["block"] + valid = step.get("valid", True) + signed_block = blocks[block_id] + if valid: + spec.on_block(store, signed_block) + for block_att in signed_block.message.body.attestations: + try: + spec.on_attestation(store, block_att, is_from_block=True) + except AssertionError: + pass + for block_att_slashing in signed_block.message.body.attester_slashings: + try: + spec.on_attester_slashing(store, block_att_slashing) + except AssertionError: + pass + else: + try: + spec.on_block(store, signed_block) + assert False + except AssertionError: + pass + elif "attestation" in step: + att_id = step["attestation"] + valid = step.get("valid", True) + attestation = atts[att_id] + if valid: + spec.on_attestation(store, attestation, is_from_block=False) + else: + try: + spec.on_attestation(store, attestation, is_from_block=False) + assert False + except AssertionError: + pass + elif "attester_slashing" in step: + slashing_id = step["attester_slashing"] + valid = step.get("valid", True) + assert valid + slashing = slashings[slashing_id] + spec.on_attester_slashing(store, slashing) + elif "checks" in step: + checks = step["checks"] + for check, value in checks.items(): + if check == "time": + expected_time = value + assert store.time == expected_time + elif check == "head": + assert str(spec.get_head(store)) == value["root"] + elif check == "proposer_boost_root": + assert str(store.proposer_boost_root) == str(value) + elif check == "justified_checkpoint": + checkpoint = store.justified_checkpoint + assert checkpoint.epoch == value["epoch"] + assert str(checkpoint.root) == str(value["root"]) + elif check == "finalized_checkpoint": + checkpoint = store.finalized_checkpoint + assert checkpoint.epoch == value["epoch"] + assert str(checkpoint.root) == str(value["root"]) + elif check == "viable_for_head_roots_and_weights": + filtered_block_roots = spec.get_filtered_block_tree(store).keys() + leaves_viable_for_head = [ + root + for root in filtered_block_roots + if not any( + c for c in filtered_block_roots if store.blocks[c].parent_root == root + ) + ] + viable_for_head_roots_and_weights = { + str(viable_for_head_root): int(spec.get_weight(store, viable_for_head_root)) + for viable_for_head_root in leaves_viable_for_head + } + expected = {kv["root"]: kv["weight"] for kv in value} + assert expected == viable_for_head_roots_and_weights + else: + assert False + else: + assert False + + +def gather_tests(tests_dir) -> Iterable[TestInfo]: + for preset in [p.name for p in Path(tests_dir).glob("*") if p.name in spec_targets]: + for fork in [ + f.name for f in (Path(tests_dir) / preset).glob("*") if f.name in spec_targets[preset] + ]: + print(f"{preset}/{fork}") + for test_dir in sorted( + [td for td in (Path(tests_dir) / preset / fork).glob("*/*/*/*")] + ): + yield TestInfo(preset, fork, test_dir) + + +def runt_tests_parallel(tests_dir, num_proc=os.cpu_count()): + def runner(test_info: TestInfo): + try: + run_test(test_info) + except Exception as e: + raise e + + tests = list(gather_tests(tests_dir)) + with Pool(processes=num_proc) as pool: + for _ in tqdm(pool.imap(runner, tests), total=len(tests)): + pass + + +def run_tests(tests_dir): + for test_info in gather_tests(tests_dir): + print(test_info.test_dir) + run_test(test_info) + + +def main(): + arg_parser = argparse.ArgumentParser() + arg_parser.add_argument( + "-i", "--test-dir", dest="test_dir", required=True, help="directory with generated tests" + ) + args = arg_parser.parse_args() + runt_tests_parallel(args.test_dir) + + +if __name__ == "__main__": + main() diff --git a/tests/generators/compliance_runners/fork_choice/tiny/block_cover.yaml b/tests/generators/compliance_runners/fork_choice/tiny/block_cover.yaml new file mode 100644 index 0000000000..aa6d88f7d4 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/tiny/block_cover.yaml @@ -0,0 +1,108 @@ +- block_epochs: [0] + current_epoch: 1 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 1 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [0, 1] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: true} + previous_justifications: [false, false] + store_justified_epoch: 0 + target_block: 0 +- block_epochs: [2] + current_epoch: 3 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 3 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2] + current_epoch: 5 + current_justifications: [false] + parents: [0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, false] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: true, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 2 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 4 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 4 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: true, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 +- block_epochs: [2, 3, 3] + current_epoch: 5 + current_justifications: [false, false, true] + parents: [0, 0, 0] + predicates: {block_is_leaf: true, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false, false] + store_justified_epoch: 3 + target_block: 1 +- block_epochs: [2, 3] + current_epoch: 5 + current_justifications: [false, true] + parents: [0, 0] + predicates: {block_is_leaf: false, block_vse_eq_store_je: false, block_vse_plus_two_ge_curr_e: false, + store_je_eq_zero: false} + previous_justifications: [false, false] + store_justified_epoch: 3 + target_block: 0 diff --git a/tests/generators/compliance_runners/fork_choice/tiny/block_tree_other.yaml b/tests/generators/compliance_runners/fork_choice/tiny/block_tree_other.yaml new file mode 100644 index 0000000000..edd147b557 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/tiny/block_tree_other.yaml @@ -0,0 +1,10 @@ +- block_parents: [0, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 4, 5, 3, 2, 1, 0] + sm_links: *id001 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 5, 3, 2, 1, 0] + sm_links: *id001 diff --git a/tests/generators/compliance_runners/fork_choice/tiny/block_tree_tree.yaml b/tests/generators/compliance_runners/fork_choice/tiny/block_tree_tree.yaml new file mode 100644 index 0000000000..ca4f470ac8 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/tiny/block_tree_tree.yaml @@ -0,0 +1,37 @@ +- block_parents: &id002 [0, 0, 1, 2, 3, 2, 1, 0] + sm_links: &id001 + - [0, 1] + - [0, 2] + - [0, 3] +- block_parents: &id003 [0, 0, 1, 2, 2, 3, 1, 0] + sm_links: *id001 +- block_parents: &id005 [0, 0, 1, 2, 3, 3, 1, 0] + sm_links: *id001 +- block_parents: *id002 + sm_links: &id004 + - [0, 1] + - [0, 2] + - [1, 3] +- block_parents: *id003 + sm_links: *id004 +- block_parents: *id005 + sm_links: *id004 +- block_parents: *id002 + sm_links: &id006 + - [0, 1] + - [0, 2] + - [2, 3] +- block_parents: *id003 + sm_links: *id006 +- block_parents: *id005 + sm_links: *id006 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 6, 5, 4, 3, 2, 1, 0] + sm_links: &id007 + - [0, 1] + - [0, 2] + - [2, 3] + - [3, 4] +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 6, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id007 +- block_parents: [0, 0, 1, 2, 3, 4, 5, 6, 7, 7, 5, 4, 3, 2, 1, 0] + sm_links: *id007 diff --git a/tests/generators/compliance_runners/fork_choice/tiny/test_gen.yaml b/tests/generators/compliance_runners/fork_choice/tiny/test_gen.yaml new file mode 100644 index 0000000000..140f705230 --- /dev/null +++ b/tests/generators/compliance_runners/fork_choice/tiny/test_gen.yaml @@ -0,0 +1,32 @@ +block_tree_test: + test_type: block_tree + instances: block_tree_tree.yaml # 12 + seed: 123 + nr_variations: 2 + nr_mutations: 1 +block_weight_test: + test_type: block_tree + instances: block_tree_other.yaml # 3 + seed: 123 + nr_variations: 5 + nr_mutations: 0 +attester_slashing_test: + test_type: block_tree + instances: block_tree_other.yaml # 3 + seed: 123 + nr_variations: 2 + nr_mutations: 1 + with_attester_slashings: true +invalid_message_test: + test_type: block_tree + instances: block_tree_other.yaml # 3 + seed: 123 + nr_variations: 2 + nr_mutations: 1 + with_invalid_messages: true +block_cover_test: + test_type: block_cover + instances: block_cover.yaml # 12 + seed: 456 + nr_variations: 2 + nr_mutations: 1 diff --git a/tests/generators/epoch_processing/README.md b/tests/generators/epoch_processing/README.md deleted file mode 100644 index 662b0b516d..0000000000 --- a/tests/generators/epoch_processing/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Epoch processing - -Epoch processing covers the sub-transitions during an epoch change. - -An epoch-processing test-runner can consume these sub-transition test-suites, - and handle different kinds of epoch sub-transitions by processing the cases using the specified test handler. - -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/epoch_processing/main.py b/tests/generators/epoch_processing/main.py deleted file mode 100644 index 4c1a68d0a7..0000000000 --- a/tests/generators/epoch_processing/main.py +++ /dev/null @@ -1,43 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.epoch_processing.test_process_' + key for key in [ - 'justification_and_finalization', - 'rewards_and_penalties', - 'registry_updates', - 'slashings', - 'eth1_data_reset', - 'effective_balance_updates', - 'slashings_reset', - 'randao_mixes_reset', - 'historical_roots_update', - 'participation_record_updates', - ]} - altair_mods = { - **{key: 'eth2spec.test.altair.epoch_processing.test_process_' + key for key in [ - 'inactivity_updates', - 'participation_flag_updates', - 'sync_committee_updates', - ]}, - **phase_0_mods, - } # also run the previous phase 0 tests - - # No epoch-processing changes in Merge and previous testing repeats with new types, so no additional tests required. - merge_mods = altair_mods - - # TODO Custody Game testgen is disabled for now - # custody_game_mods = {**{key: 'eth2spec.test.custody_game.epoch_processing.test_process_' + key for key in [ - # 'reveal_deadlines', - # 'challenge_deadlines', - # 'custody_final_updates', - # ]}, **phase_0_mods} # also run the previous phase 0 tests (but against custody game spec) - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="epoch_processing", all_mods=all_mods) diff --git a/tests/generators/epoch_processing/requirements.txt b/tests/generators/epoch_processing/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/epoch_processing/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/finality/README.md b/tests/generators/finality/README.md deleted file mode 100644 index dec5819c68..0000000000 --- a/tests/generators/finality/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Finality tests - -Finality tests cover regular state-transitions in a common block-list format to test finality rules. - -Information on the format of the tests can be found in the [finality test formats documentation](../../formats/finality/README.md). diff --git a/tests/generators/finality/main.py b/tests/generators/finality/main.py deleted file mode 100644 index dbc58a8090..0000000000 --- a/tests/generators/finality/main.py +++ /dev/null @@ -1,16 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE - - -if __name__ == "__main__": - phase_0_mods = {'finality': 'eth2spec.test.phase0.finality.test_finality'} - altair_mods = phase_0_mods # No additional Altair specific finality tests - merge_mods = altair_mods # No additional Merge specific finality tests - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="finality", all_mods=all_mods) diff --git a/tests/generators/finality/requirements.txt b/tests/generators/finality/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/finality/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/fork_choice/README.md b/tests/generators/fork_choice/README.md deleted file mode 100644 index e67b115ba1..0000000000 --- a/tests/generators/fork_choice/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Fork choice tests - -Fork choice tests cover the different forking cases with fork choice helper functions. - -Information on the format of the tests can be found in the [fork choice test formats documentation](../../formats/fork_choice/README.md). diff --git a/tests/generators/fork_choice/main.py b/tests/generators/fork_choice/main.py deleted file mode 100644 index 1481741203..0000000000 --- a/tests/generators/fork_choice/main.py +++ /dev/null @@ -1,21 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.fork_choice.test_' + key for key in [ - 'get_head', - 'on_block', - ]} - # No additional Altair specific finality tests, yet. - altair_mods = phase_0_mods - # No specific Merge tests yet. - merge_mods = altair_mods - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="fork_choice", all_mods=all_mods) diff --git a/tests/generators/fork_choice/requirements.txt b/tests/generators/fork_choice/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/fork_choice/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/forks/main.py b/tests/generators/forks/main.py deleted file mode 100644 index 7be79847da..0000000000 --- a/tests/generators/forks/main.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Iterable - -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MINIMAL, MAINNET -from eth2spec.test.helpers.typing import SpecForkName, PresetBaseName -from eth2spec.test.altair.fork import test_altair_fork_basic, test_altair_fork_random -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests - - -def create_provider(tests_src, preset_name: PresetBaseName, - phase: SpecForkName, fork_name: SpecForkName) -> gen_typing.TestProvider: - - def prepare_fn() -> None: - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - return generate_from_tests( - runner_name='fork', - handler_name='fork', - src=tests_src, - fork_name=fork_name, - preset_name=preset_name, - phase=phase, - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - gen_runner.run_generator("forks", [ - create_provider(test_altair_fork_basic, MINIMAL, PHASE0, ALTAIR), - create_provider(test_altair_fork_basic, MAINNET, PHASE0, ALTAIR), - create_provider(test_altair_fork_random, MINIMAL, PHASE0, ALTAIR), - create_provider(test_altair_fork_random, MAINNET, PHASE0, ALTAIR), - ]) diff --git a/tests/generators/forks/requirements.txt b/tests/generators/forks/requirements.txt deleted file mode 100644 index 735f863faa..0000000000 --- a/tests/generators/forks/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] \ No newline at end of file diff --git a/tests/generators/genesis/README.md b/tests/generators/genesis/README.md deleted file mode 100644 index e270f6e35e..0000000000 --- a/tests/generators/genesis/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Genesis test generator - -Genesis tests cover the initialization and validity-based launch trigger for the Beacon Chain genesis state. - -Information on the format of the tests can be found in the [genesis test formats documentation](../../formats/genesis/README.md). - diff --git a/tests/generators/genesis/main.py b/tests/generators/genesis/main.py deleted file mode 100644 index 8e0294bf0c..0000000000 --- a/tests/generators/genesis/main.py +++ /dev/null @@ -1,16 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.genesis.test_' + key for key in [ - 'initialization', - 'validity', - ]} - altair_mods = phase_0_mods - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - } - - run_state_test_generators(runner_name="genesis", all_mods=all_mods) diff --git a/tests/generators/genesis/requirements.txt b/tests/generators/genesis/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/genesis/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/main.py b/tests/generators/main.py new file mode 100644 index 0000000000..a15e213eaa --- /dev/null +++ b/tests/generators/main.py @@ -0,0 +1,20 @@ +import importlib +import os + +from eth2spec.gen_helpers.gen_base import gen_runner + +if __name__ == "__main__": + current_dir = os.path.dirname(__file__) + runners_dir = os.path.join(current_dir, "runners") + + test_cases = [] + for filename in os.listdir(runners_dir): + if not filename.endswith(".py"): + continue + module_name = filename.replace(".py", "") + full_module = f"tests.generators.runners.{module_name}" + mod = importlib.import_module(full_module) + assert hasattr(mod, "get_test_cases"), full_module + test_cases.extend(mod.get_test_cases()) + + gen_runner.run_generator(test_cases) diff --git a/tests/generators/operations/README.md b/tests/generators/operations/README.md deleted file mode 100644 index a5d48c11b4..0000000000 --- a/tests/generators/operations/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Operations - -Operations (or "transactions" in previous spec iterations), - are atomic changes to the state, introduced by embedding in blocks. - -An operation test-runner can consume these operation test-suites, - and handle different kinds of operations by processing the cases using the specified test handler. - -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/operations/main.py b/tests/generators/operations/main.py deleted file mode 100644 index 3844ce1868..0000000000 --- a/tests/generators/operations/main.py +++ /dev/null @@ -1,44 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.block_processing.test_process_' + key for key in [ - 'attestation', - 'attester_slashing', - 'block_header', - 'deposit', - 'proposer_slashing', - 'voluntary_exit', - ]} - altair_mods = { - **{'sync_aggregate': [ - 'eth2spec.test.altair.block_processing.sync_aggregate.test_process_' + key - for key in ['sync_aggregate', 'sync_aggregate_random'] - ]}, - **phase_0_mods, - } # also run the previous phase 0 tests - - merge_mods = { - **{key: 'eth2spec.test.merge.block_processing.test_process_' + key for key in [ - 'execution_payload', - ]}, - **altair_mods, - } - - # TODO Custody Game testgen is disabled for now - # custody_game_mods = {**{key: 'eth2spec.test.custody_game.block_processing.test_process_' + key for key in [ - # 'attestation', - # 'chunk_challenge', - # 'custody_key_reveal', - # 'custody_slashing', - # 'early_derived_secret_reveal', - # ]}, **phase_0_mods} # also run the previous phase 0 tests (but against custody game spec) - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="operations", all_mods=all_mods) diff --git a/tests/generators/operations/requirements.txt b/tests/generators/operations/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/operations/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/random/Makefile b/tests/generators/random/Makefile deleted file mode 100644 index 1b518bfde7..0000000000 --- a/tests/generators/random/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -all: - if ! test -d venv; then python3 -m venv venv; fi; - . ./venv/bin/activate - pip3 install -r requirements.txt - rm -f ../../core/pyspec/eth2spec/test/phase0/random/test_random.py - rm -f ../../core/pyspec/eth2spec/test/altair/random/test_random.py - python3 generate.py phase0 > ../../core/pyspec/eth2spec/test/phase0/random/test_random.py - python3 generate.py altair > ../../core/pyspec/eth2spec/test/altair/random/test_random.py diff --git a/tests/generators/random/README.md b/tests/generators/random/README.md deleted file mode 100644 index fd17284412..0000000000 --- a/tests/generators/random/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Randomized tests - -Randomized tests in the format of `sanity` blocks tests, with randomized operations. - -Information on the format of the tests can be found in the [sanity test formats documentation](../../formats/sanity/README.md). - -# To generate test sources - -```bash -$ make -``` - -The necessary commands are in the `Makefile`, as the only target. - -The generated files are committed to the repo so you should not need to do this. - -# To run tests - -Each of the generated test does produce a `pytest` test instance but by default is -currently skipped. Running the test via the generator (see next) will trigger any errors -that would arise during the running of `pytest`. - -# To generate spec tests (from the generated files) - -Run the test generator in the usual way. - -E.g. from the root of this repo, you can run: - -```bash -$ make gen_random -``` diff --git a/tests/generators/random/generate.py b/tests/generators/random/generate.py deleted file mode 100644 index b880cb12ba..0000000000 --- a/tests/generators/random/generate.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -This test format currently uses code generation to assemble the tests -as the current test infra does not have a facility to dynamically -generate tests that can be seen by ``pytest``. - -This will likley change in future releases of the testing infra. - -NOTE: To add additional scenarios, add test cases below in ``_generate_randomized_scenarios``. -""" - -import sys -import random -import warnings -from typing import Callable -import itertools - -from eth2spec.test.utils.randomized_block_tests import ( - no_block, - no_op_validation, - randomize_state, - randomize_state_altair, - random_block, - random_block_altair_with_cycling_sync_committee_participation, - last_slot_in_epoch, - random_slot_in_epoch, - penultimate_slot_in_epoch, - epoch_transition, - slot_transition, - transition_with_random_block, - transition_to_leaking, - transition_without_leak, -) -from eth2spec.test.helpers.constants import PHASE0, ALTAIR - - -# Ensure this many blocks are present in *each* randomized scenario -BLOCK_TRANSITIONS_COUNT = 2 - - -def _normalize_transition(transition): - """ - Provide "empty" or "no op" sub-transitions - to a given transition. - """ - if isinstance(transition, Callable): - transition = transition() - if "epochs_to_skip" not in transition: - transition["epochs_to_skip"] = 0 - if "slots_to_skip" not in transition: - transition["slots_to_skip"] = 0 - if "block_producer" not in transition: - transition["block_producer"] = no_block - if "validation" not in transition: - transition["validation"] = no_op_validation - return transition - - -def _normalize_scenarios(scenarios): - """ - "Normalize" a "scenario" so that a producer of a test case - does not need to provide every expected key/value. - """ - for scenario in scenarios: - transitions = scenario["transitions"] - for i, transition in enumerate(transitions): - transitions[i] = _normalize_transition(transition) - - -def _flatten(t): - leak_transition = t[0] - result = [leak_transition] - for transition_batch in t[1]: - for transition in transition_batch: - if isinstance(transition, tuple): - for subtransition in transition: - result.append(subtransition) - else: - result.append(transition) - return result - - -def _generate_randomized_scenarios(block_randomizer): - """ - Generates a set of randomized testing scenarios. - Return a sequence of "scenarios" where each scenario: - 1. Provides some setup - 2. Provides a sequence of transitions that mutate the state in some way, - possibly yielding blocks along the way - NOTE: scenarios are "normalized" with empty/no-op elements before returning - to the test generation to facilitate brevity when writing scenarios by hand. - NOTE: the main block driver builds a block for the **next** slot, so - the slot transitions are offset by -1 to target certain boundaries. - """ - # go forward 0 or 1 epochs - epochs_set = ( - epoch_transition(n=0), - epoch_transition(n=1), - ) - # within those epochs, go forward to: - slots_set = ( - # the first slot in an epoch (see note in docstring about offsets...) - slot_transition(last_slot_in_epoch), - # the second slot in an epoch - slot_transition(n=0), - # some random number of slots, but not at epoch boundaries - slot_transition(random_slot_in_epoch), - # the last slot in an epoch (see note in docstring about offsets...) - slot_transition(penultimate_slot_in_epoch), - ) - # and produce a block... - blocks_set = ( - transition_with_random_block(block_randomizer), - ) - - rng = random.Random(1447) - all_skips = list(itertools.product(epochs_set, slots_set)) - randomized_skips = ( - rng.sample(all_skips, len(all_skips)) - for _ in range(BLOCK_TRANSITIONS_COUNT) - ) - - # build a set of block transitions from combinations of sub-transitions - transitions_generator = ( - itertools.product(prefix, blocks_set) - for prefix in randomized_skips - ) - block_transitions = zip(*transitions_generator) - - # and preface each set of block transitions with the possible leak transitions - leak_transitions = ( - transition_without_leak, - transition_to_leaking, - ) - scenarios = [ - {"transitions": _flatten(t)} - for t in itertools.product(leak_transitions, block_transitions) - ] - _normalize_scenarios(scenarios) - return scenarios - - -def _id_from_scenario(test_description): - """ - Construct a name for the scenario based its data. - """ - def _to_id_part(prefix, x): - suffix = str(x) - if isinstance(x, Callable): - suffix = x.__name__ - return f"{prefix}{suffix}" - - def _id_from_transition(transition): - return ",".join(( - _to_id_part("epochs:", transition["epochs_to_skip"]), - _to_id_part("slots:", transition["slots_to_skip"]), - _to_id_part("with-block:", transition["block_producer"]) - )) - - return "|".join(map(_id_from_transition, test_description["transitions"])) - - -test_imports_template = """\"\"\" -This module is generated from the ``random`` test generator. -Please do not edit this file manually. -See the README for that generator for more information. -\"\"\" - -from eth2spec.test.helpers.constants import {phase} -from eth2spec.test.context import ( - misc_balances_in_default_range_with_many_validators, - with_phases, - zero_activation_threshold, - only_generator, -) -from eth2spec.test.context import ( - always_bls, - spec_test, - with_custom_state, - single_phase, -) -from eth2spec.test.utils.randomized_block_tests import ( - run_generated_randomized_test, -)""" - -test_template = """ -@only_generator(\"randomized test for broad coverage, not point-to-point CI\") -@with_phases([{phase}]) -@with_custom_state( - balances_fn=misc_balances_in_default_range_with_many_validators, - threshold_fn=zero_activation_threshold -) -@spec_test -@single_phase -@always_bls -def test_randomized_{index}(spec, state): - # scenario as high-level, informal text: -{name_as_comment} - scenario = {scenario} # noqa: E501 - yield from run_generated_randomized_test( - spec, - state, - scenario, - )""" - - -def _to_comment(name, indent_level): - parts = name.split("|") - indentation = " " * indent_level - parts = [ - indentation + "# " + part for part in parts - ] - return "\n".join(parts) - - -def run_generate_tests_to_std_out(phase, state_randomizer, block_randomizer): - scenarios = _generate_randomized_scenarios(block_randomizer) - test_content = {"phase": phase.upper()} - test_imports = test_imports_template.format(**test_content) - test_file = [test_imports] - for index, scenario in enumerate(scenarios): - # required for setup phase - scenario["state_randomizer"] = state_randomizer.__name__ - - # need to pass name, rather than function reference... - transitions = scenario["transitions"] - for transition in transitions: - for name, value in transition.items(): - if isinstance(value, Callable): - transition[name] = value.__name__ - - test_content = test_content.copy() - name = _id_from_scenario(scenario) - test_content["name_as_comment"] = _to_comment(name, 1) - test_content["index"] = index - test_content["scenario"] = scenario - test_instance = test_template.format(**test_content) - test_file.append(test_instance) - print("\n\n".join(test_file)) - - -if __name__ == "__main__": - did_generate = False - if PHASE0 in sys.argv: - did_generate = True - run_generate_tests_to_std_out( - PHASE0, - state_randomizer=randomize_state, - block_randomizer=random_block, - ) - if ALTAIR in sys.argv: - did_generate = True - run_generate_tests_to_std_out( - ALTAIR, - state_randomizer=randomize_state_altair, - block_randomizer=random_block_altair_with_cycling_sync_committee_participation, - ) - if not did_generate: - warnings.warn("no phase given for test generation") diff --git a/tests/generators/random/main.py b/tests/generators/random/main.py deleted file mode 100644 index f6f1b18476..0000000000 --- a/tests/generators/random/main.py +++ /dev/null @@ -1,18 +0,0 @@ -from eth2spec.test.helpers.constants import PHASE0, ALTAIR -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.random.test_' + key for key in [ - 'random', - ]} - altair_mods = {key: 'eth2spec.test.altair.random.test_' + key for key in [ - 'random', - ]} - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - } - - run_state_test_generators(runner_name="random", all_mods=all_mods) diff --git a/tests/generators/random/requirements.txt b/tests/generators/random/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/random/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/rewards/README.md b/tests/generators/rewards/README.md deleted file mode 100644 index 60f106836a..0000000000 --- a/tests/generators/rewards/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Rewards - -Rewards covers the sub-functions of `process_rewards_and_penalties` for granular testing of components of the rewards function. - -A rewards test-runner can consume these sub-transition test-suites, - and handle different kinds of epoch sub-transitions by processing the cases using the specified test handler. - -Information on the format of the tests can be found in the [rewards test formats documentation](../../formats/rewards/README.md). diff --git a/tests/generators/rewards/main.py b/tests/generators/rewards/main.py deleted file mode 100644 index 6b6a7bc6f0..0000000000 --- a/tests/generators/rewards/main.py +++ /dev/null @@ -1,25 +0,0 @@ -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.rewards.test_' + key for key in [ - 'basic', - 'leak', - 'random', - ]} - # No additional altair specific rewards tests, yet. - altair_mods = phase_0_mods - - # No additional merge specific rewards tests, yet. - # Note: Block rewards are non-epoch rewards and are tested as part of block processing tests. - # Transaction fees are part of the execution-layer. - merge_mods = altair_mods - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="rewards", all_mods=all_mods) diff --git a/tests/generators/rewards/requirements.txt b/tests/generators/rewards/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/rewards/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/runners/bls.py b/tests/generators/runners/bls.py new file mode 100644 index 0000000000..2b8d91bb8d --- /dev/null +++ b/tests/generators/runners/bls.py @@ -0,0 +1,275 @@ +""" +BLS test vectors generator +""" + +from collections.abc import Iterable + +import milagro_bls_binding as milagro_bls +from eth_utils import encode_hex + +from eth2spec.altair import spec +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.test.helpers.constants import ALTAIR +from eth2spec.utils import bls + +############################################################################### +# Helper functions +############################################################################### + + +def hex_to_int(x: str) -> int: + return int(x, 16) + + +def expect_exception(func, *args): + try: + func(*args) + except Exception: + pass + else: + raise Exception("should have raised exception") + + +############################################################################### +# Precomputed constants +############################################################################### + + +MESSAGES = [ + bytes(b"\x00" * 32), + bytes(b"\x56" * 32), + bytes(b"\xab" * 32), +] +SAMPLE_MESSAGE = b"\x12" * 32 + +PRIVKEYS = [ + # Curve order is 256, so private keys use 32 bytes at most. + # Also, not all integers are valid private keys. Therefore, using pre-generated keys. + hex_to_int( + "0x00000000000000000000000000000000263dbd792f5b1be47ed85f8938c0f29586af0d3ac7b977f21c278fe1462040e3" + ), + hex_to_int( + "0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138" + ), + hex_to_int( + "0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216" + ), +] + +ZERO_PUBKEY = b"\x00" * 48 +G1_POINT_AT_INFINITY = b"\xc0" + b"\x00" * 47 + +ZERO_SIGNATURE = b"\x00" * 96 +G2_POINT_AT_INFINITY = b"\xc0" + b"\x00" * 95 + +ZERO_PRIVKEY = 0 +ZERO_PRIVKEY_BYTES = b"\x00" * 32 + + +############################################################################### +# Test cases for eth_aggregate_pubkeys +############################################################################### + + +def case_eth_aggregate_pubkeys(): + def get_test_runner(input_getter): + def _runner(): + pubkeys = input_getter() + try: + aggregate_pubkey = None + aggregate_pubkey = spec.eth_aggregate_pubkeys(pubkeys) + except Exception: + expect_exception(milagro_bls._AggregatePKs, pubkeys) + if aggregate_pubkey is not None: + assert aggregate_pubkey == milagro_bls._AggregatePKs(pubkeys) + return [ + ( + "data", + "data", + { + "input": [encode_hex(pubkey) for pubkey in pubkeys], + "output": ( + encode_hex(aggregate_pubkey) if aggregate_pubkey is not None else None + ), + }, + ) + ] + + return _runner + + # Valid pubkey + for i, privkey in enumerate(PRIVKEYS): + + def get_inputs(privkey=privkey): + return [bls.SkToPk(privkey)] + + yield f"eth_aggregate_pubkeys_valid_{i}", get_test_runner(get_inputs) + + # Valid pubkeys + if True: + + def get_inputs(): + return [bls.SkToPk(privkey) for privkey in PRIVKEYS] + + yield "eth_aggregate_pubkeys_valid_pubkeys", get_test_runner(get_inputs) + + # Invalid pubkeys -- len(pubkeys) == 0 + if True: + + def get_inputs(): + return [] + + yield "eth_aggregate_pubkeys_empty_list", get_test_runner(get_inputs) + + # Invalid pubkeys -- [ZERO_PUBKEY] + if True: + + def get_inputs(): + return [ZERO_PUBKEY] + + yield "eth_aggregate_pubkeys_zero_pubkey", get_test_runner(get_inputs) + + # Invalid pubkeys -- G1 point at infinity + if True: + + def get_inputs(): + return [G1_POINT_AT_INFINITY] + + yield "eth_aggregate_pubkeys_infinity_pubkey", get_test_runner(get_inputs) + + # Invalid pubkeys -- b'\x40\x00\x00\x00....\x00' pubkey + if True: + + def get_inputs(): + return [b"\x40" + b"\00" * 47] + + yield "eth_aggregate_pubkeys_x40_pubkey", get_test_runner(get_inputs) + + +############################################################################### +# Test cases for eth_fast_aggregate_verify +############################################################################### + + +def case_eth_fast_aggregate_verify(): + def get_test_runner(input_getter): + def _runner(): + pubkeys, message, aggregate_signature = input_getter() + try: + ok = None + ok = spec.eth_fast_aggregate_verify(pubkeys, message, aggregate_signature) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "pubkeys": [encode_hex(pubkey) for pubkey in pubkeys], + "message": encode_hex(message), + "signature": encode_hex(aggregate_signature), + }, + "output": ok if ok is not None else None, + }, + ) + ] + + return _runner + + # Valid signature + for i, message in enumerate(MESSAGES): + + def get_inputs(i=i, message=message): + privkeys = PRIVKEYS[: i + 1] + sigs = [bls.Sign(privkey, message) for privkey in privkeys] + aggregate_signature = bls.Aggregate(sigs) + pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] + return pubkeys, message, aggregate_signature + + yield f"eth_fast_aggregate_verify_valid_{i}", get_test_runner(get_inputs) + + # Invalid signature -- extra pubkey + for i, message in enumerate(MESSAGES): + + def get_inputs(i=i, message=message): + privkeys = PRIVKEYS[: i + 1] + sigs = [bls.Sign(privkey, message) for privkey in privkeys] + aggregate_signature = bls.Aggregate(sigs) + # Add an extra pubkey to the end + pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] + [bls.SkToPk(PRIVKEYS[-1])] + return pubkeys, message, aggregate_signature + + yield f"eth_fast_aggregate_verify_extra_pubkey_{i}", get_test_runner(get_inputs) + + # Invalid signature -- tampered with signature + for i, message in enumerate(MESSAGES): + + def get_inputs(i=i, message=message): + privkeys = PRIVKEYS[: i + 1] + sigs = [bls.Sign(privkey, message) for privkey in privkeys] + aggregate_signature = bls.Aggregate(sigs) + pubkeys = [bls.SkToPk(privkey) for privkey in privkeys] + # Tamper with the signature + tampered_signature = aggregate_signature[:-4] + b"\xff\xff\xff\xff" + return pubkeys, message, tampered_signature + + yield f"eth_fast_aggregate_verify_tampered_signature_{i}", get_test_runner(get_inputs) + + # NOTE: Unlike `FastAggregateVerify`, len(pubkeys) == 0 and signature == G2_POINT_AT_INFINITY is VALID + if True: + + def get_inputs(): + return [], MESSAGES[-1], G2_POINT_AT_INFINITY + + yield ( + "eth_fast_aggregate_verify_na_pubkeys_and_infinity_signature", + get_test_runner(get_inputs), + ) + + # Invalid pubkeys and signature -- len(pubkeys) == 0 and signature == 0x00... + if True: + + def get_inputs(): + return [], MESSAGES[-1], ZERO_SIGNATURE + + yield "eth_fast_aggregate_verify_na_pubkeys_and_zero_signature", get_test_runner(get_inputs) + + # Invalid pubkeys and signature -- pubkeys contains point at infinity + if True: + + def get_inputs(): + pubkeys = [bls.SkToPk(privkey) for privkey in PRIVKEYS] + pubkeys_with_infinity = pubkeys + [G1_POINT_AT_INFINITY] + signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] + aggregate_signature = bls.Aggregate(signatures) + return pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature + + yield "eth_fast_aggregate_verify_infinity_pubkey", get_test_runner(get_inputs) + + +############################################################################### +# Main logic +############################################################################### + + +def get_test_cases() -> Iterable[TestCase]: + test_cases = [] + handlers = { + "eth_aggregate_pubkeys": case_eth_aggregate_pubkeys, + "eth_fast_aggregate_verify": case_eth_fast_aggregate_verify, + } + for method, fn in handlers.items(): + for case_name, case_fn in fn(): + test_cases.append( + TestCase( + fork_name=ALTAIR, + preset_name="general", + runner_name="bls", + handler_name=method, + suite_name="bls", + case_name=case_name, + case_fn=case_fn, + ) + ) + return test_cases diff --git a/tests/generators/runners/epoch_processing.py b/tests/generators/runners/epoch_processing.py new file mode 100644 index 0000000000..6329a41a71 --- /dev/null +++ b/tests/generators/runners/epoch_processing.py @@ -0,0 +1,17 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def handler_name_fn(mod): + handler_name = mod.split(".")[-1] + if handler_name == "test_apply_pending_deposit": + return "pending_deposits" + handler_name = handler_name.replace("test_process_", "") + handler_name = handler_name.replace("test_apply_", "") + return handler_name + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("epoch_processing", handler_name_fn=handler_name_fn) diff --git a/tests/generators/runners/finality.py b/tests/generators/runners/finality.py new file mode 100644 index 0000000000..58ab93a271 --- /dev/null +++ b/tests/generators/runners/finality.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("finality") diff --git a/tests/generators/runners/fork_choice.py b/tests/generators/runners/fork_choice.py new file mode 100644 index 0000000000..2db47f61c3 --- /dev/null +++ b/tests/generators/runners/fork_choice.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("fork_choice") diff --git a/tests/generators/runners/forks.py b/tests/generators/runners/forks.py new file mode 100644 index 0000000000..be679397d4 --- /dev/null +++ b/tests/generators/runners/forks.py @@ -0,0 +1,25 @@ +from collections.abc import Iterable +from importlib import import_module + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests, get_expected_modules +from eth2spec.test.helpers.constants import ALL_PRESETS, POST_FORK_OF + + +def get_test_cases() -> Iterable[TestCase]: + test_cases = [] + for preset in ALL_PRESETS: + for prefork, postfork in POST_FORK_OF.items(): + for mod in get_expected_modules("fork"): + tests_src = import_module(mod) + test_cases.extend( + generate_from_tests( + runner_name="fork", + handler_name="fork", + src=tests_src, + fork_name=postfork, + preset_name=preset, + phase=prefork, + ) + ) + return test_cases diff --git a/tests/generators/runners/genesis.py b/tests/generators/runners/genesis.py new file mode 100644 index 0000000000..fe1a8db6ec --- /dev/null +++ b/tests/generators/runners/genesis.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("genesis") diff --git a/tests/generators/runners/kzg_4844.py b/tests/generators/runners/kzg_4844.py new file mode 100644 index 0000000000..ff05e9cd3e --- /dev/null +++ b/tests/generators/runners/kzg_4844.py @@ -0,0 +1,651 @@ +""" +KZG test vectors generator for EIP-4844 +""" + +from collections.abc import Iterable +from functools import cache + +from eth_utils import encode_hex + +from eth2spec.deneb import spec +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.test.helpers.constants import DENEB +from eth2spec.test.utils.kzg_tests import ( + BLOB_ALL_TWOS, + BLOB_ALL_ZEROS, + BLOB_RANDOM_VALID1, + bls_add_one, + encode_hex_list, + G1, + INVALID_BLOBS, + INVALID_FIELD_ELEMENTS, + INVALID_G1_POINTS, + VALID_BLOBS, + VALID_FIELD_ELEMENTS, +) + +############################################################################### +# Test helpers +############################################################################### + + +@cache +def cached_blob_to_kzg_commitment(blob): + return spec.blob_to_kzg_commitment(blob) + + +@cache +def cached_compute_blob_kzg_proof(blob, commitment): + return spec.compute_blob_kzg_proof(blob, commitment) + + +############################################################################### +# Test cases for blob_to_kzg_commitment +############################################################################### + + +def case_blob_to_kzg_commitment(): + def get_test_runner(blob): + def _runner(): + try: + commitment = None + commitment = cached_blob_to_kzg_commitment(blob) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": {"blob": encode_hex(blob)}, + "output": encode_hex(commitment) if commitment is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + yield f"blob_to_kzg_commitment_case_valid_blob_{index}", get_test_runner(blob) + + # Edge case: Invalid blobs + for index, blob in enumerate(INVALID_BLOBS): + yield f"blob_to_kzg_commitment_case_invalid_blob_{index}", get_test_runner(blob) + + +############################################################################### +# Test cases for compute_kzg_proof +############################################################################### + + +def case_compute_kzg_proof(): + def get_test_runner(blob, z): + def _runner(): + try: + proof, y = None, None + proof, y = spec.compute_kzg_proof(blob, z) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "blob": encode_hex(blob), + "z": encode_hex(z), + }, + "output": (encode_hex(proof), encode_hex(y)) if proof is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for i, blob in enumerate(VALID_BLOBS): + for j, z in enumerate(VALID_FIELD_ELEMENTS): + yield f"compute_kzg_proof_case_valid_blob_{i}_{j}", get_test_runner(blob, z) + + # Edge case: Invalid blobs + for index, blob in enumerate(INVALID_BLOBS): + z = VALID_FIELD_ELEMENTS[0] + yield f"compute_kzg_proof_case_invalid_blob_{index}", get_test_runner(blob, z) + + # Edge case: Invalid z + for index, z in enumerate(INVALID_FIELD_ELEMENTS): + blob = VALID_BLOBS[4] + yield f"compute_kzg_proof_case_invalid_z_{index}", get_test_runner(blob, z) + + +############################################################################### +# Test cases for verify_kzg_proof +############################################################################### + + +def case_verify_kzg_proof(): + def get_test_runner(input_getter): + def _runner(): + commitment, z, y, proof = input_getter() + try: + ok = None + ok = spec.verify_kzg_proof(commitment, z, y, proof) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "commitment": encode_hex(commitment), + "z": encode_hex(z), + "y": encode_hex(y), + "proof": encode_hex(proof), + }, + "output": ok if ok is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for i, blob in enumerate(VALID_BLOBS): + for j, z in enumerate(VALID_FIELD_ELEMENTS): + + def get_inputs(blob=blob, z=z): + proof, y = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_correct_proof_{i}_{j}", get_test_runner(get_inputs) + + # Incorrect proofs + for i, blob in enumerate(VALID_BLOBS): + for j, z in enumerate(VALID_FIELD_ELEMENTS): + + def get_inputs(blob=blob, z=z): + proof_orig, y = spec.compute_kzg_proof(blob, z) + proof = bls_add_one(proof_orig) + commitment = cached_blob_to_kzg_commitment(blob) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_incorrect_proof_{i}_{j}", get_test_runner(get_inputs) + + # Incorrect `G1_POINT_AT_INFINITY` proof + for index, z in enumerate(VALID_FIELD_ELEMENTS): + + def get_inputs(z=z): + blob = BLOB_RANDOM_VALID1 + _, y = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return commitment, z, y, proof + + yield ( + f"verify_kzg_proof_case_incorrect_proof_point_at_infinity_{index}", + get_test_runner(get_inputs), + ) + + # Correct `G1_POINT_AT_INFINITY` proof for zero poly + for index, z in enumerate(VALID_FIELD_ELEMENTS): + + def get_inputs(z=z): + blob = BLOB_ALL_ZEROS + _, y = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return commitment, z, y, proof + + yield ( + f"verify_kzg_proof_case_correct_proof_point_at_infinity_for_zero_poly_{index}", + get_test_runner(get_inputs), + ) + + # Correct `G1_POINT_AT_INFINITY` proof for poly of all twos + for index, z in enumerate(VALID_FIELD_ELEMENTS): + + def get_inputs(z=z): + blob = BLOB_ALL_TWOS + _, y = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return commitment, z, y, proof + + yield ( + f"verify_kzg_proof_case_correct_proof_point_at_infinity_for_twos_poly_{index}", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid commitment + for index, commitment in enumerate(INVALID_G1_POINTS): + + def get_inputs(commitment=commitment): + blob, z = VALID_BLOBS[2], VALID_FIELD_ELEMENTS[1] + proof, y = spec.compute_kzg_proof(blob, z) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_invalid_commitment_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid z + for index, z in enumerate(INVALID_FIELD_ELEMENTS): + + def get_inputs(z=z): + blob, validz = VALID_BLOBS[4], VALID_FIELD_ELEMENTS[1] + proof, y = spec.compute_kzg_proof(blob, validz) + commitment = cached_blob_to_kzg_commitment(blob) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_invalid_z_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid y + for index, y in enumerate(INVALID_FIELD_ELEMENTS): + + def get_inputs(y=y): + blob, z = VALID_BLOBS[4], VALID_FIELD_ELEMENTS[1] + proof, _ = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_invalid_y_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid proof + for index, proof in enumerate(INVALID_G1_POINTS): + + def get_inputs(proof=proof): + blob, z = VALID_BLOBS[2], VALID_FIELD_ELEMENTS[1] + _, y = spec.compute_kzg_proof(blob, z) + commitment = cached_blob_to_kzg_commitment(blob) + return commitment, z, y, proof + + yield f"verify_kzg_proof_case_invalid_proof_{index}", get_test_runner(get_inputs) + + +############################################################################### +# Test cases for compute_blob_kzg_proof +############################################################################### + + +def case_compute_blob_kzg_proof(): + def get_test_runner(input_getter): + def _runner(): + blob, commitment = input_getter() + try: + proof = None + proof = cached_compute_blob_kzg_proof(blob, commitment) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "blob": encode_hex(blob), + "commitment": encode_hex(commitment), + }, + "output": encode_hex(proof) if proof is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + + def get_inputs(blob=blob): + commitment = cached_blob_to_kzg_commitment(blob) + return blob, commitment + + yield f"compute_blob_kzg_proof_case_valid_blob_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid blob + for index, blob in enumerate(INVALID_BLOBS): + + def get_inputs(blob=blob): + commitment = G1 + return blob, commitment + + yield f"compute_blob_kzg_proof_case_invalid_blob_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid commitment + for index, commitment in enumerate(INVALID_G1_POINTS): + + def get_inputs(commitment=commitment): + blob = VALID_BLOBS[1] + return blob, commitment + + yield f"compute_blob_kzg_proof_case_invalid_commitment_{index}", get_test_runner(get_inputs) + + +############################################################################### +# Test cases for verify_blob_kzg_proof +############################################################################### + + +def case_verify_blob_kzg_proof(): + def get_test_runner(input_getter): + def _runner(): + blob, commitment, proof = input_getter() + try: + ok = None + ok = spec.verify_blob_kzg_proof(blob, commitment, proof) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "blob": encode_hex(blob), + "commitment": encode_hex(commitment), + "proof": encode_hex(proof), + }, + "output": ok if ok is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + + def get_inputs(blob=blob): + commitment = cached_blob_to_kzg_commitment(blob) + proof = cached_compute_blob_kzg_proof(blob, commitment) + return blob, commitment, proof + + yield f"verify_blob_kzg_proof_case_correct_proof_{index}", get_test_runner(get_inputs) + + # Incorrect proofs + for index, blob in enumerate(VALID_BLOBS): + + def get_inputs(blob=blob): + commitment = cached_blob_to_kzg_commitment(blob) + proof = bls_add_one(cached_compute_blob_kzg_proof(blob, commitment)) + return blob, commitment, proof + + yield f"verify_blob_kzg_proof_case_incorrect_proof_{index}", get_test_runner(get_inputs) + + # Incorrect `G1_POINT_AT_INFINITY` proof + if True: + + def get_inputs(): + blob = BLOB_RANDOM_VALID1 + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return blob, commitment, proof + + yield ( + "verify_blob_kzg_proof_case_incorrect_proof_point_at_infinity", + get_test_runner(get_inputs), + ) + + # Correct `G1_POINT_AT_INFINITY` proof and commitment for zero poly + if True: + + def get_inputs(): + blob = BLOB_ALL_ZEROS + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return blob, commitment, proof + + yield ( + "verify_blob_kzg_proof_case_correct_proof_point_at_infinity_for_zero_poly", + get_test_runner(get_inputs), + ) + + # Correct `G1_POINT_AT_INFINITY` proof for all twos poly + if True: + + def get_inputs(): + blob = BLOB_ALL_TWOS + commitment = cached_blob_to_kzg_commitment(blob) + proof = spec.G1_POINT_AT_INFINITY + return blob, commitment, proof + + yield ( + "verify_blob_kzg_proof_case_correct_proof_point_at_infinity_for_twos_poly", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid blob + for index, blob in enumerate(INVALID_BLOBS): + + def get_inputs(blob=blob): + proof = G1 + commitment = G1 + return blob, commitment, proof + + yield f"verify_blob_kzg_proof_case_invalid_blob_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid commitment + for index, commitment in enumerate(INVALID_G1_POINTS): + + def get_inputs(commitment=commitment): + blob = VALID_BLOBS[1] + proof = G1 + return blob, commitment, proof + + yield f"verify_blob_kzg_proof_case_invalid_commitment_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid proof + for index, proof in enumerate(INVALID_G1_POINTS): + + def get_inputs(proof=proof): + blob = VALID_BLOBS[1] + commitment = G1 + return blob, commitment, proof + + yield f"verify_blob_kzg_proof_case_invalid_proof_{index}", get_test_runner(get_inputs) + + +############################################################################### +# Test cases for verify_blob_kzg_proof_batch +############################################################################### + + +def case_verify_blob_kzg_proof_batch(): + def get_test_runner(input_getter): + def _runner(): + blobs, commitments, proofs = input_getter() + try: + ok = None + ok = spec.verify_blob_kzg_proof_batch(blobs, commitments, proofs) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "blobs": encode_hex_list(blobs), + "commitments": encode_hex_list(commitments), + "proofs": encode_hex_list(proofs), + }, + "output": ok if ok is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for length in range(len(VALID_BLOBS)): + + def get_inputs(length=length): + blobs = VALID_BLOBS[:length] + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + return blobs, commitments, proofs + + yield f"verify_blob_kzg_proof_batch_case_{length}", get_test_runner(get_inputs) + + # Incorrect proof + if True: + + def get_inputs(): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Add one to the first proof, so that it's incorrect + proofs = [bls_add_one(proofs[0])] + proofs[1:] + return blobs, commitments, proofs + + yield ( + "verify_blob_kzg_proof_batch_case_incorrect_proof_add_one", + get_test_runner(get_inputs), + ) + + # Incorrect `G1_POINT_AT_INFINITY` proof + if True: + + def get_inputs(): + blobs = [BLOB_RANDOM_VALID1] + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + # Use the wrong proof + proofs = [spec.G1_POINT_AT_INFINITY] + return blobs, commitments, proofs + + yield ( + "verify_blob_kzg_proof_batch_case_incorrect_proof_point_at_infinity", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid blobs + for index, blob in enumerate(INVALID_BLOBS): + + def get_inputs(blob=blob): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Insert an invalid blob into the middle + blobs = VALID_BLOBS[:4] + [blob] + VALID_BLOBS[5:] + return blobs, commitments, proofs + + yield f"verify_blob_kzg_proof_batch_case_invalid_blob_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid commitment + for index, commitment in enumerate(INVALID_G1_POINTS): + + def get_inputs(commitment=commitment): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Replace first commitment with an invalid commitment + commitments = [commitment] + commitments[1:] + return blobs, commitments, proofs + + yield ( + f"verify_blob_kzg_proof_batch_case_invalid_commitment_{index}", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid proof + for index, proof in enumerate(INVALID_G1_POINTS): + + def get_inputs(proof=proof): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Replace first proof with an invalid proof + proofs = [proof] + proofs[1:] + return blobs, commitments, proofs + + yield f"verify_blob_kzg_proof_batch_case_invalid_proof_{index}", get_test_runner(get_inputs) + + # Edge case: Blob length different + if True: + + def get_inputs(): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Delete the last blob + blobs = blobs[:-1] + return blobs, commitments, proofs + + yield "verify_blob_kzg_proof_batch_case_blob_length_different", get_test_runner(get_inputs) + + # Edge case: Commitment length different + if True: + + def get_inputs(): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Delete the last commitment + commitments = commitments[:-1] + return blobs, commitments, proofs + + yield ( + "verify_blob_kzg_proof_batch_case_commitment_length_different", + get_test_runner(get_inputs), + ) + + # Edge case: Proof length different + if True: + + def get_inputs(): + blobs = VALID_BLOBS + commitments = [cached_blob_to_kzg_commitment(blob) for blob in blobs] + proofs = [ + cached_compute_blob_kzg_proof(blob, commitments[i]) for i, blob in enumerate(blobs) + ] + # Delete the last proof + proofs = proofs[:-1] + return blobs, commitments, proofs + + yield "verify_blob_kzg_proof_batch_case_proof_length_different", get_test_runner(get_inputs) + + +############################################################################### +# Main logic +############################################################################### + + +def get_test_cases() -> Iterable[TestCase]: + test_case_fns = [ + ("blob_to_kzg_commitment", case_blob_to_kzg_commitment), + ("compute_kzg_proof", case_compute_kzg_proof), + ("verify_kzg_proof", case_verify_kzg_proof), + ("compute_blob_kzg_proof", case_compute_blob_kzg_proof), + ("verify_blob_kzg_proof", case_verify_blob_kzg_proof), + ("verify_blob_kzg_proof_batch", case_verify_blob_kzg_proof_batch), + ] + + test_cases = [] + for handler_name, test_case_fn in test_case_fns: + for case_name, case_fn in test_case_fn(): + test_cases.append( + TestCase( + fork_name=DENEB, + preset_name="general", + runner_name="kzg", + handler_name=handler_name, + suite_name="kzg-mainnet", + case_name=case_name, + case_fn=case_fn, + ) + ) + return test_cases diff --git a/tests/generators/runners/kzg_7594.py b/tests/generators/runners/kzg_7594.py new file mode 100644 index 0000000000..5cf571fc03 --- /dev/null +++ b/tests/generators/runners/kzg_7594.py @@ -0,0 +1,612 @@ +""" +KZG test vectors generator for EIP-7594 +""" + +from collections.abc import Iterable +from functools import cache + +from eth_utils import encode_hex + +from eth2spec.fulu import spec +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.test.helpers.constants import FULU +from eth2spec.test.utils.kzg_tests import ( + bls_add_one, + CELL_RANDOM_VALID1, + CELL_RANDOM_VALID2, + encode_hex_list, + INVALID_BLOBS, + INVALID_G1_POINTS, + INVALID_INDIVIDUAL_CELL_BYTES, + VALID_BLOBS, +) + +############################################################################### +# Test helpers +############################################################################### + + +@cache +def cached_blob_to_kzg_commitment(blob): + return spec.blob_to_kzg_commitment(blob) + + +@cache +def cached_compute_cells_and_kzg_proofs(blob): + return spec.compute_cells_and_kzg_proofs(blob) + + +############################################################################### +# Test cases for compute_cells +############################################################################### + + +def case_compute_cells(): + def get_test_runner(blob): + def _runner(): + try: + cells = None + cells = spec.compute_cells(blob) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": {"blob": encode_hex(blob)}, + "output": encode_hex_list(cells) if cells is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + yield f"compute_cells_case_valid_{index}", get_test_runner(blob) + + # Edge case: Invalid blobs + for index, blob in enumerate(INVALID_BLOBS): + yield f"compute_cells_invalid_blob_{index}", get_test_runner(blob) + + +############################################################################### +# Test cases for compute_cells_and_kzg_proofs +############################################################################### + + +def case_compute_cells_and_kzg_proofs(): + def get_test_runner(blob): + def _runner(): + try: + cells, proofs = None, None + cells, proofs = cached_compute_cells_and_kzg_proofs(blob) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": {"blob": encode_hex(blob)}, + "output": ( + (encode_hex_list(cells), encode_hex_list(proofs)) + if cells is not None + else None + ), + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + yield f"compute_cells_and_kzg_proofs_case_valid_{index}", get_test_runner(blob) + + # Edge case: Invalid blobs + for index, blob in enumerate(INVALID_BLOBS): + yield f"compute_cells_and_kzg_proofs_case_invalid_blob_{index}", get_test_runner(blob) + + +############################################################################### +# Test cases for verify_cell_kzg_proof_batch +############################################################################### + + +def case_verify_cell_kzg_proof_batch(): + def get_test_runner(input_getter): + def _runner(): + commitments, cell_indices, cells, proofs = input_getter() + try: + ok = None + ok = spec.verify_cell_kzg_proof_batch(commitments, cell_indices, cells, proofs) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "commitments": encode_hex_list(commitments), + "cell_indices": cell_indices, + "cells": encode_hex_list(cells), + "proofs": encode_hex_list(proofs), + }, + "output": ok if ok is not None else None, + }, + ) + ] + + return _runner + + # Valid cases + for index, blob in enumerate(VALID_BLOBS): + + def get_inputs(blob=blob): + cells, proofs = cached_compute_cells_and_kzg_proofs(blob) + commitments = [cached_blob_to_kzg_commitment(blob) for _ in cells] + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB)) + return commitments, cell_indices, cells, proofs + + yield f"verify_cell_kzg_proof_batch_case_valid_{index}", get_test_runner(get_inputs) + + # Valid: zero cells + if True: + + def get_inputs(): + return [], [], [], [] + + yield "verify_cell_kzg_proof_batch_case_valid_zero_cells", get_test_runner(get_inputs) + + # Valid: Verify cells from multiple blobs + if True: + + def get_inputs(): + cells0, proofs0 = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[0]) + cells1, proofs1 = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[1]) + commitments = [ + cached_blob_to_kzg_commitment(VALID_BLOBS[0]), + cached_blob_to_kzg_commitment(VALID_BLOBS[1]), + ] + cell_indices = [0, 0] + cells = [cells0[0], cells1[0]] + proofs = [proofs0[0], proofs1[0]] + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_valid_multiple_blobs", get_test_runner(get_inputs) + + # Valid: Same cell multiple times + if True: + + def get_inputs(): + num_duplicates = 3 + commitments = [cached_blob_to_kzg_commitment(VALID_BLOBS[3])] * num_duplicates + cell_indices = [0] * num_duplicates + cells = [cached_compute_cells_and_kzg_proofs(VALID_BLOBS[3])[0][0]] * num_duplicates + proofs = [cached_compute_cells_and_kzg_proofs(VALID_BLOBS[3])[1][0]] * num_duplicates + return commitments, cell_indices, cells, proofs + + yield ( + "verify_cell_kzg_proof_batch_case_valid_same_cell_multiple_times", + get_test_runner(get_inputs), + ) + + # Incorrect commitment + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[5]) + cells, proofs = cells[:1], proofs[:1] + # Use the wrong commitment + commitments = [bls_add_one(cached_blob_to_kzg_commitment(VALID_BLOBS[5]))] + cell_indices = list(range(len(cells))) + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_incorrect_commitment", get_test_runner(get_inputs) + + # Incorrect cell + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[6]) + cells, proofs = cells[:1], proofs[:1] + commitments = [cached_blob_to_kzg_commitment(VALID_BLOBS[6])] + cell_indices = list(range(len(cells))) + # Change last cell so it's wrong + cells[-1] = CELL_RANDOM_VALID2 + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_incorrect_cell", get_test_runner(get_inputs) + + # Incorrect proof + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[0]) + cells, proofs = cells[:1], proofs[:1] + commitments = [cached_blob_to_kzg_commitment(VALID_BLOBS[0])] + cell_indices = list(range(len(cells))) + # Change last proof so it's wrong + proofs[-1] = bls_add_one(proofs[-1]) + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_incorrect_proof", get_test_runner(get_inputs) + + # Edge case: Invalid commitment + for index, commitment in enumerate(INVALID_G1_POINTS): + + def get_inputs(index=index, commitment=commitment): + cells, proofs = cached_compute_cells_and_kzg_proofs( + VALID_BLOBS[index % len(INVALID_G1_POINTS)] + ) + cells, proofs = cells[:1], proofs[:1] + # Set commitments to the invalid commitment + commitments = [commitment] + cell_indices = list(range(len(cells))) + return commitments, cell_indices, cells, proofs + + yield ( + f"verify_cell_kzg_proof_batch_case_invalid_commitment_{index}", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid cell_index + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[1]) + cells, proofs = cells[:1], proofs[:1] + commitments = [cached_blob_to_kzg_commitment(VALID_BLOBS[1])] + cell_indices = list(range(len(cells))) + # Set first cell index to an invalid value + cell_indices[0] = int(spec.CELLS_PER_EXT_BLOB) + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_invalid_cell_index", get_test_runner(get_inputs) + + # Edge case: Invalid cell + for index, cell in enumerate(INVALID_INDIVIDUAL_CELL_BYTES): + + def get_inputs(index=index, cell=cell): + cells, proofs = cached_compute_cells_and_kzg_proofs( + VALID_BLOBS[index % len(INVALID_INDIVIDUAL_CELL_BYTES)] + ) + cells, proofs = cells[:1], proofs[:1] + commitments = [ + cached_blob_to_kzg_commitment( + VALID_BLOBS[index % len(INVALID_INDIVIDUAL_CELL_BYTES)] + ) + ] + cell_indices = list(range(len(cells))) + # Set first cell to the invalid cell + cells[0] = cell + return commitments, cell_indices, cells, proofs + + yield f"verify_cell_kzg_proof_batch_case_invalid_cell_{index}", get_test_runner(get_inputs) + + # Edge case: Invalid proof + for index, proof in enumerate(INVALID_G1_POINTS): + + def get_inputs(index=index, proof=proof): + cells, proofs = cached_compute_cells_and_kzg_proofs( + VALID_BLOBS[index % len(INVALID_G1_POINTS)] + ) + cells, proofs = cells[:1], proofs[:1] + commitments = [ + cached_blob_to_kzg_commitment(VALID_BLOBS[index % len(INVALID_G1_POINTS)]) + ] + cell_indices = list(range(len(cells))) + # Set first proof to the invalid proof + proofs[0] = proof + return commitments, cell_indices, cells, proofs + + yield f"verify_cell_kzg_proof_batch_case_invalid_proof_{index}", get_test_runner(get_inputs) + + # Edge case: Missing a commitment + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[0]) + cells, proofs = cells[:2], proofs[:2] + # Do not include the second commitment + commitments = [cached_blob_to_kzg_commitment(VALID_BLOBS[0])] + cell_indices = list(range(len(cells))) + return commitments, cell_indices, cells, proofs + + yield ( + "verify_cell_kzg_proof_batch_case_invalid_missing_commitment", + get_test_runner(get_inputs), + ) + + # Edge case: Missing a cell index + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[2]) + cells, proofs = cells[:2], proofs[:2] + commitments = [ + cached_blob_to_kzg_commitment(VALID_BLOBS[2]), + cached_blob_to_kzg_commitment(VALID_BLOBS[2]), + ] + # Leave off one of the cell indices + cell_indices = list(range(len(cells) - 1)) + return commitments, cell_indices, cells, proofs + + yield ( + "verify_cell_kzg_proof_batch_case_invalid_missing_cell_index", + get_test_runner(get_inputs), + ) + + # Edge case: Missing a cell + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[3]) + cells, proofs = cells[:2], proofs[:2] + commitments = [ + cached_blob_to_kzg_commitment(VALID_BLOBS[3]), + cached_blob_to_kzg_commitment(VALID_BLOBS[3]), + ] + cell_indices = list(range(len(cells))) + # Remove the last proof + cells = cells[:-1] + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_invalid_missing_cell", get_test_runner(get_inputs) + + # Edge case: Missing a proof + if True: + + def get_inputs(): + cells, proofs = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[4]) + cells, proofs = cells[:2], proofs[:2] + commitments = [ + cached_blob_to_kzg_commitment(VALID_BLOBS[4]), + cached_blob_to_kzg_commitment(VALID_BLOBS[4]), + ] + cell_indices = list(range(len(cells))) + # Remove the last proof + proofs = proofs[:-1] + return commitments, cell_indices, cells, proofs + + yield "verify_cell_kzg_proof_batch_case_invalid_missing_proof", get_test_runner(get_inputs) + + +############################################################################### +# Test cases for recover_cells_and_kzg_proofs +############################################################################### + + +def case_recover_cells_and_kzg_proofs(): + def get_test_runner(input_getter): + def _runner(): + cell_indices, cells = input_getter() + try: + recovered_cells, recovered_proofs = None, None + recovered_cells, recovered_proofs = spec.recover_cells_and_kzg_proofs( + cell_indices, cells + ) + except Exception: + pass + return [ + ( + "data", + "data", + { + "input": { + "cell_indices": cell_indices, + "cells": encode_hex_list(cells), + }, + "output": ( + (encode_hex_list(recovered_cells), encode_hex_list(recovered_proofs)) + if recovered_cells is not None + else None + ), + }, + ) + ] + + return _runner + + # Valid: No missing cells + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[0]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB)) + return cell_indices, cells + + yield "recover_cells_and_kzg_proofs_case_valid_no_missing", get_test_runner(get_inputs) + + # Valid: Half missing cells (every other cell) + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[1]) + cell_indices = list(range(0, spec.CELLS_PER_EXT_BLOB, 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_valid_half_missing_every_other_cell", + get_test_runner(get_inputs), + ) + + # Valid: Half missing cells (first half) + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[2]) + cell_indices = list(range(0, spec.CELLS_PER_EXT_BLOB // 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_valid_half_missing_first_half", + get_test_runner(get_inputs), + ) + + # Valid: Half missing cells (second half) + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[3]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB // 2, spec.CELLS_PER_EXT_BLOB)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_valid_half_missing_second_half", + get_test_runner(get_inputs), + ) + + # Edge case: All cells are missing + if True: + + def get_inputs(): + return [], [] + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_all_cells_are_missing", + get_test_runner(get_inputs), + ) + + # Edge case: More than half missing + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[4]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB // 2 - 1)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_more_than_half_missing", + get_test_runner(get_inputs), + ) + + # Edge case: More cells provided than CELLS_PER_EXT_BLOB + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[5]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB)) + [0] + partial_cells = [cells[cell_index] for cell_index in cell_indices] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_more_cells_than_cells_per_ext_blob", + get_test_runner(get_inputs), + ) + + # Edge case: Invalid cell_index + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[6]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB // 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + # Replace first cell_index with an invalid value + cell_indices[0] = int(spec.CELLS_PER_EXT_BLOB) + return cell_indices, partial_cells + + yield "recover_cells_and_kzg_proofs_case_invalid_cell_index", get_test_runner(get_inputs) + + # Edge case: Invalid cell + for index, cell in enumerate(INVALID_INDIVIDUAL_CELL_BYTES): + + def get_inputs(cell=cell): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[6]) + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB // 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + # Replace first cell with an invalid value + partial_cells[0] = cell + return cell_indices, partial_cells + + yield f"recover_cells_and_kzg_proofs_case_invalid_cell_{index}", get_test_runner(get_inputs) + + # Edge case: More cell_indices than cells + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[0]) + cell_indices = list(range(0, spec.CELLS_PER_EXT_BLOB, 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + # Add another cell_index + cell_indices.append(int(spec.CELLS_PER_EXT_BLOB - 1)) + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_more_cell_indices_than_cells", + get_test_runner(get_inputs), + ) + + # Edge case: More cells than cell_indices + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[1]) + cell_indices = list(range(0, spec.CELLS_PER_EXT_BLOB, 2)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + # Add another cell + partial_cells.append(CELL_RANDOM_VALID1) + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_more_cells_than_cell_indices", + get_test_runner(get_inputs), + ) + + # Edge case: Duplicate cell_index + if True: + + def get_inputs(): + cells, _ = cached_compute_cells_and_kzg_proofs(VALID_BLOBS[2]) + # There will be 65 cells, where 64 are unique and 1 is a duplicate. + # Depending on the implementation, 63 & 1 might not fail for the right + # reason. For example, if the implementation assigns cells in an array + # via index, this would result in 63 cells and the test would fail due + # to insufficient cell count, not because of a duplicate cell. + cell_indices = list(range(spec.CELLS_PER_EXT_BLOB // 2 + 1)) + partial_cells = [cells[cell_index] for cell_index in cell_indices] + # Replace first cell_index with the second cell_index + cell_indices[0] = cell_indices[1] + return cell_indices, partial_cells + + yield ( + "recover_cells_and_kzg_proofs_case_invalid_duplicate_cell_index", + get_test_runner(get_inputs), + ) + + +############################################################################### +# Main logic +############################################################################### + + +def get_test_cases() -> Iterable[TestCase]: + test_case_fns = [ + ("compute_cells", case_compute_cells), + ("compute_cells_and_kzg_proofs", case_compute_cells_and_kzg_proofs), + ("verify_cell_kzg_proof_batch", case_verify_cell_kzg_proof_batch), + ("recover_cells_and_kzg_proofs", case_recover_cells_and_kzg_proofs), + ] + + test_cases = [] + for handler_name, test_case_fn in test_case_fns: + for case_name, case_fn in test_case_fn(): + test_cases.append( + TestCase( + fork_name=FULU, + preset_name="general", + runner_name="kzg", + handler_name=handler_name, + suite_name="kzg-mainnet", + case_name=case_name, + case_fn=case_fn, + ) + ) + return test_cases diff --git a/tests/generators/runners/light_client.py b/tests/generators/runners/light_client.py new file mode 100644 index 0000000000..cb099da83a --- /dev/null +++ b/tests/generators/runners/light_client.py @@ -0,0 +1,15 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def handler_name_fn(mod): + handler_name = mod.split(".")[-1] + if handler_name == "test_sync_protocol": + return "sync" + return handler_name.replace("test_", "") + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("light_client", handler_name_fn=handler_name_fn) diff --git a/tests/generators/runners/merkle_proof.py b/tests/generators/runners/merkle_proof.py new file mode 100644 index 0000000000..24f8f3ede7 --- /dev/null +++ b/tests/generators/runners/merkle_proof.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("merkle_proof") diff --git a/tests/generators/runners/networking.py b/tests/generators/runners/networking.py new file mode 100644 index 0000000000..cbe9adaab9 --- /dev/null +++ b/tests/generators/runners/networking.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("networking") diff --git a/tests/generators/runners/operations.py b/tests/generators/runners/operations.py new file mode 100644 index 0000000000..18333f8350 --- /dev/null +++ b/tests/generators/runners/operations.py @@ -0,0 +1,15 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def handler_name_fn(mod): + handler_name = mod.split(".")[-1] + if handler_name == "test_process_sync_aggregate_random": + return "sync_aggregate" + return handler_name.replace("test_process_", "") + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("operations", pkg="block_processing", handler_name_fn=handler_name_fn) diff --git a/tests/generators/runners/random.py b/tests/generators/runners/random.py new file mode 100644 index 0000000000..a2876b378f --- /dev/null +++ b/tests/generators/runners/random.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("random") diff --git a/tests/generators/runners/rewards.py b/tests/generators/runners/rewards.py new file mode 100644 index 0000000000..9152a3cb3b --- /dev/null +++ b/tests/generators/runners/rewards.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("rewards") diff --git a/tests/generators/runners/sanity.py b/tests/generators/runners/sanity.py new file mode 100644 index 0000000000..d561b0cd8b --- /dev/null +++ b/tests/generators/runners/sanity.py @@ -0,0 +1,19 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def handler_name_fn(mod): + handler_name = mod.split(".")[-1] + if handler_name == "test_deposit_transition": + return "blocks" + if handler_name == "test_lookahead": + return "blocks" + if handler_name == "test_lookahead_slots": + return "slots" + return handler_name.replace("test_", "") + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("sanity", handler_name_fn=handler_name_fn) diff --git a/tests/generators/runners/shuffling.py b/tests/generators/runners/shuffling.py new file mode 100644 index 0000000000..8d17215573 --- /dev/null +++ b/tests/generators/runners/shuffling.py @@ -0,0 +1,56 @@ +import random +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.phase0 import mainnet as spec_mainnet, minimal as spec_minimal +from eth2spec.test.helpers.constants import ALL_PRESETS, MAINNET, MINIMAL, PHASE0 + + +def generate_random_bytes(rng=random.Random(5566)): + random_bytes = bytes(rng.randint(0, 255) for _ in range(32)) + return random_bytes + + +def shuffling_case_fn(spec, seed, count): + yield ( + "mapping", + "data", + { + "seed": "0x" + seed.hex(), + "count": count, + "mapping": [int(spec.compute_shuffled_index(i, count, seed)) for i in range(count)], + }, + ) + + +def shuffling_case(spec, seed, count): + return f"shuffle_0x{seed.hex()}_{count}", lambda: shuffling_case_fn(spec, seed, count) + + +def shuffling_test_cases(spec): + # NOTE: somehow the random.Random generated seeds do not have pickle issue. + rng = random.Random(1234) + seeds = [generate_random_bytes(rng) for i in range(30)] + for seed in seeds: + for count in [0, 1, 2, 3, 5, 10, 33, 100, 1000, 9999]: + yield shuffling_case(spec, seed, count) + + +def get_test_cases() -> Iterable[TestCase]: + test_cases = [] + + for preset in ALL_PRESETS: + spec = {MAINNET: spec_mainnet, MINIMAL: spec_minimal}[preset] + for case_name, case_fn in shuffling_test_cases(spec): + test_cases.append( + TestCase( + fork_name=PHASE0, + preset_name=preset, + runner_name="shuffling", + handler_name="core", + suite_name="shuffle", + case_name=case_name, + case_fn=case_fn, + ) + ) + return test_cases diff --git a/tests/generators/runners/ssz_generic.py b/tests/generators/runners/ssz_generic.py new file mode 100644 index 0000000000..f3e5f8eaf9 --- /dev/null +++ b/tests/generators/runners/ssz_generic.py @@ -0,0 +1,46 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.test.helpers.constants import PHASE0 + +from .ssz_generic_cases import ( + ssz_basic_vector, + ssz_bitlist, + ssz_bitvector, + ssz_boolean, + ssz_container, + ssz_uints, +) + + +def get_test_cases() -> Iterable[TestCase]: + test_case_fns = [ + ("basic_vector", "valid", ssz_basic_vector.valid_cases), + ("basic_vector", "invalid", ssz_basic_vector.invalid_cases), + ("bitlist", "valid", ssz_bitlist.valid_cases), + ("bitlist", "invalid", ssz_bitlist.invalid_cases), + ("bitvector", "valid", ssz_bitvector.valid_cases), + ("bitvector", "invalid", ssz_bitvector.invalid_cases), + ("boolean", "valid", ssz_boolean.valid_cases), + ("boolean", "invalid", ssz_boolean.invalid_cases), + ("uints", "valid", ssz_uints.valid_cases), + ("uints", "invalid", ssz_uints.invalid_cases), + ("containers", "valid", ssz_container.valid_cases), + ("containers", "invalid", ssz_container.invalid_cases), + ] + + test_cases = [] + for handler_name, suite_name, test_case_fn in test_case_fns: + for case_name, case_fn in test_case_fn(): + test_cases.append( + TestCase( + fork_name=PHASE0, + preset_name="general", + runner_name="ssz_generic", + handler_name=handler_name, + suite_name=suite_name, + case_name=case_name, + case_fn=case_fn, + ) + ) + return test_cases diff --git a/tests/generators/runners/ssz_generic_cases/__init__.py b/tests/generators/runners/ssz_generic_cases/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/generators/runners/ssz_generic_cases/ssz_basic_vector.py b/tests/generators/runners/ssz_generic_cases/ssz_basic_vector.py new file mode 100644 index 0000000000..0caf172895 --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_basic_vector.py @@ -0,0 +1,114 @@ +from random import Random + +from eth2spec.debug.random_value import get_random_ssz_object, RandomizationMode +from eth2spec.utils.ssz.ssz_impl import serialize +from eth2spec.utils.ssz.ssz_typing import ( + BasicView, + boolean, + uint8, + uint16, + uint32, + uint64, + uint128, + uint256, + Vector, +) + +from .ssz_test_case import invalid_test_case, valid_test_case + + +def basic_vector_case_fn( + rng: Random, mode: RandomizationMode, elem_type: type[BasicView], length: int +): + return get_random_ssz_object( + rng, + Vector[elem_type, length], + max_bytes_length=length * 8, + max_list_length=length, + mode=mode, + chaos=False, + ) + + +BASIC_TYPES: dict[str, type[BasicView]] = { + "bool": boolean, + "uint8": uint8, + "uint16": uint16, + "uint32": uint32, + "uint64": uint64, + "uint128": uint128, + "uint256": uint256, +} + + +def valid_cases(): + rng = Random(1234) + for name, typ in BASIC_TYPES.items(): + random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] + if name != "bool": + random_modes.append(RandomizationMode.mode_random) + for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: + for mode in random_modes: + yield ( + f"vec_{name}_{length}_{mode.to_name()}", + valid_test_case( + lambda rng=rng, mode=mode, typ=typ, length=length: basic_vector_case_fn( + rng, mode, typ, length + ) + ), + ) + + +def invalid_cases(): + # zero length vectors are illegal + for name, typ in BASIC_TYPES.items(): + yield f"vec_{name}_0", invalid_test_case(lambda: b"") + + rng = Random(1234) + for name, typ in BASIC_TYPES.items(): + random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] + if name != "bool": + random_modes.append(RandomizationMode.mode_random) + for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: + yield f"vec_{name}_{length}_nil", invalid_test_case(lambda: b"") + for mode in random_modes: + if length == 1: + # empty bytes, no elements. It may seem valid, but empty fixed-size elements are not valid SSZ. + yield ( + f"vec_{name}_{length}_{mode.to_name()}_one_less", + invalid_test_case(lambda: b""), + ) + else: + yield ( + f"vec_{name}_{length}_{mode.to_name()}_one_less", + invalid_test_case( + lambda rng=rng, mode=mode, typ=typ, length=length: serialize( + basic_vector_case_fn(rng, mode, typ, length - 1) + ) + ), + ) + yield ( + f"vec_{name}_{length}_{mode.to_name()}_one_more", + invalid_test_case( + lambda rng=rng, mode=mode, typ=typ, length=length: serialize( + basic_vector_case_fn(rng, mode, typ, length + 1) + ) + ), + ) + yield ( + f"vec_{name}_{length}_{mode.to_name()}_one_byte_less", + invalid_test_case( + lambda rng=rng, mode=mode, typ=typ, length=length: serialize( + basic_vector_case_fn(rng, mode, typ, length) + )[:-1] + ), + ) + yield ( + f"vec_{name}_{length}_{mode.to_name()}_one_byte_more", + invalid_test_case( + lambda rng=rng, mode=mode, typ=typ, length=length: serialize( + basic_vector_case_fn(rng, mode, typ, length) + ) + + serialize(basic_vector_case_fn(rng, mode, uint8, 1)) + ), + ) diff --git a/tests/generators/runners/ssz_generic_cases/ssz_bitlist.py b/tests/generators/runners/ssz_generic_cases/ssz_bitlist.py new file mode 100644 index 0000000000..49b66ff520 --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_bitlist.py @@ -0,0 +1,65 @@ +from random import Random + +from eth2spec.debug.random_value import get_random_ssz_object, RandomizationMode +from eth2spec.utils.ssz.ssz_impl import serialize +from eth2spec.utils.ssz.ssz_typing import Bitlist + +from .ssz_test_case import invalid_test_case, valid_test_case + + +def bitlist_case_fn(rng: Random, mode: RandomizationMode, limit: int): + return get_random_ssz_object( + rng, + Bitlist[limit], + max_bytes_length=(limit // 8) + 1, + max_list_length=limit, + mode=mode, + chaos=False, + ) + + +def valid_cases(): + rng = Random(1234) + for size in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: + for variation in range(5): + for mode in [ + RandomizationMode.mode_nil_count, + RandomizationMode.mode_max_count, + RandomizationMode.mode_random, + RandomizationMode.mode_zero, + RandomizationMode.mode_max, + ]: + yield ( + f"bitlist_{size}_{mode.to_name()}_{variation}", + valid_test_case( + lambda rng=rng, mode=mode, size=size: bitlist_case_fn(rng, mode, size) + ), + ) + + +def invalid_cases(): + yield "bitlist_no_delimiter_empty", invalid_test_case(lambda: b"") + yield "bitlist_no_delimiter_zero_byte", invalid_test_case(lambda: b"\x00") + yield "bitlist_no_delimiter_zeroes", invalid_test_case(lambda: b"\x00\x00\x00") + rng = Random(1234) + for typ_limit, test_limit in [ + (1, 2), + (1, 8), + (1, 9), + (2, 3), + (3, 4), + (4, 5), + (5, 6), + (8, 9), + (32, 64), + (32, 33), + (512, 513), + ]: + yield ( + f"bitlist_{typ_limit}_but_{test_limit}", + invalid_test_case( + lambda rng=rng, test_limit=test_limit: serialize( + bitlist_case_fn(rng, RandomizationMode.mode_max_count, test_limit) + ) + ), + ) diff --git a/tests/generators/runners/ssz_generic_cases/ssz_bitvector.py b/tests/generators/runners/ssz_generic_cases/ssz_bitvector.py new file mode 100644 index 0000000000..6ddba9d2ed --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_bitvector.py @@ -0,0 +1,78 @@ +from random import Random + +from eth2spec.debug.random_value import get_random_ssz_object, RandomizationMode +from eth2spec.utils.ssz.ssz_impl import serialize +from eth2spec.utils.ssz.ssz_typing import Bitvector + +from .ssz_test_case import invalid_test_case, valid_test_case + + +def bitvector_case_fn( + rng: Random, mode: RandomizationMode, size: int, invalid_making_pos: int = None +): + bits = get_random_ssz_object( + rng, + Bitvector[size], + max_bytes_length=(size + 7) // 8, + max_list_length=size, + mode=mode, + chaos=False, + ) + if invalid_making_pos is not None and invalid_making_pos <= size: + already_invalid = False + for i in range(invalid_making_pos, size): + if bits[i]: + already_invalid = True + if not already_invalid: + bits[invalid_making_pos] = True + return bits + + +def valid_cases(): + rng = Random(1234) + for size in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: + for mode in [ + RandomizationMode.mode_random, + RandomizationMode.mode_zero, + RandomizationMode.mode_max, + ]: + yield ( + f"bitvec_{size}_{mode.to_name()}", + valid_test_case( + lambda rng=rng, mode=mode, size=size: bitvector_case_fn(rng, mode, size) + ), + ) + + +def invalid_cases(): + # zero length bitvecors are illegal + yield "bitvec_0", invalid_test_case(lambda: b"") + rng = Random(1234) + # Create a vector with test_size bits, but make the type typ_size instead, + # which is invalid when used with the given type size + # (and a bit set just after typ_size bits if necessary to avoid the valid 0 padding-but-same-last-byte case) + for typ_size, test_size in [ + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 6), + (8, 9), + (9, 8), + (16, 8), + (32, 33), + (512, 513), + ]: + for mode in [ + RandomizationMode.mode_random, + RandomizationMode.mode_zero, + RandomizationMode.mode_max, + ]: + yield ( + f"bitvec_{typ_size}_{mode.to_name()}_{test_size}", + invalid_test_case( + lambda rng=rng, mode=mode, test_size=test_size, typ_size=typ_size: serialize( + bitvector_case_fn(rng, mode, test_size, invalid_making_pos=typ_size) + ) + ), + ) diff --git a/tests/generators/runners/ssz_generic_cases/ssz_boolean.py b/tests/generators/runners/ssz_generic_cases/ssz_boolean.py new file mode 100644 index 0000000000..f42868ce80 --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_boolean.py @@ -0,0 +1,15 @@ +from eth2spec.utils.ssz.ssz_typing import boolean + +from .ssz_test_case import invalid_test_case, valid_test_case + + +def valid_cases(): + yield "true", valid_test_case(lambda: boolean(True)) + yield "false", valid_test_case(lambda: boolean(False)) + + +def invalid_cases(): + yield "byte_2", invalid_test_case(lambda: b"\x02") + yield "byte_rev_nibble", invalid_test_case(lambda: b"\x10") + yield "byte_0x80", invalid_test_case(lambda: b"\x80") + yield "byte_full", invalid_test_case(lambda: b"\xff") diff --git a/tests/generators/runners/ssz_generic_cases/ssz_container.py b/tests/generators/runners/ssz_generic_cases/ssz_container.py new file mode 100644 index 0000000000..a29a013add --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_container.py @@ -0,0 +1,211 @@ +from collections.abc import Callable, Sequence +from random import Random + +from eth2spec.debug.random_value import get_random_ssz_object, RandomizationMode +from eth2spec.utils.ssz.ssz_impl import serialize +from eth2spec.utils.ssz.ssz_typing import ( + Bitlist, + Bitvector, + byte, + ByteList, + Container, + List, + uint8, + uint16, + uint32, + uint64, + Vector, + View, +) + +from .ssz_test_case import invalid_test_case, valid_test_case + + +class SingleFieldTestStruct(Container): + A: byte + + +class SmallTestStruct(Container): + A: uint16 + B: uint16 + + +class FixedTestStruct(Container): + A: uint8 + B: uint64 + C: uint32 + + +class VarTestStruct(Container): + A: uint16 + B: List[uint16, 1024] + C: uint8 + + +class ComplexTestStruct(Container): + A: uint16 + B: List[uint16, 128] + C: uint8 + D: ByteList[256] + E: VarTestStruct + F: Vector[FixedTestStruct, 4] + G: Vector[VarTestStruct, 2] + + +class BitsStruct(Container): + A: Bitlist[5] + B: Bitvector[2] + C: Bitvector[1] + D: Bitlist[6] + E: Bitvector[8] + + +def container_case_fn(rng: Random, mode: RandomizationMode, typ: type[View], chaos: bool = False): + return get_random_ssz_object( + rng, typ, max_bytes_length=2000, max_list_length=2000, mode=mode, chaos=chaos + ) + + +PRESET_CONTAINERS: dict[str, tuple[type[View], Sequence[int]]] = { + "SingleFieldTestStruct": (SingleFieldTestStruct, []), + "SmallTestStruct": (SmallTestStruct, []), + "FixedTestStruct": (FixedTestStruct, []), + "VarTestStruct": (VarTestStruct, [2]), + "ComplexTestStruct": (ComplexTestStruct, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]), + "BitsStruct": (BitsStruct, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]), +} + + +def valid_cases(): + rng = Random(1234) + for name, (typ, offsets) in PRESET_CONTAINERS.items(): + for mode in [RandomizationMode.mode_zero, RandomizationMode.mode_max]: + yield ( + f"{name}_{mode.to_name()}", + valid_test_case( + lambda rng=rng, mode=mode, typ=typ: container_case_fn(rng, mode, typ) + ), + ) + + if len(offsets) == 0: + modes = [ + RandomizationMode.mode_random, + RandomizationMode.mode_zero, + RandomizationMode.mode_max, + ] + else: + modes = list(RandomizationMode) + + for mode in modes: + for variation in range(3): + yield ( + f"{name}_{mode.to_name()}_chaos_{variation}", + valid_test_case( + lambda rng=rng, mode=mode, typ=typ: container_case_fn( + rng, mode, typ, chaos=True + ) + ), + ) + # Notes: Below is the second wave of iteration, and only the random mode is selected + # for container without offset since ``RandomizationMode.mode_zero`` and ``RandomizationMode.mode_max`` + # are deterministic. + modes = [RandomizationMode.mode_random] if len(offsets) == 0 else list(RandomizationMode) + for mode in modes: + for variation in range(10): + yield ( + f"{name}_{mode.to_name()}_{variation}", + valid_test_case( + lambda rng=rng, mode=mode, typ=typ: container_case_fn(rng, mode, typ) + ), + ) + + +def mod_offset(b: bytes, offset_index: int, change: Callable[[int], int]): + return ( + b[:offset_index] + + ( + change(int.from_bytes(b[offset_index : offset_index + 4], byteorder="little")) + & 0xFFFFFFFF + ).to_bytes(length=4, byteorder="little") + + b[offset_index + 4 :] + ) + + +def invalid_cases(): + rng = Random(1234) + for name, (typ, offsets) in PRESET_CONTAINERS.items(): + # using mode_max_count, so that the extra byte cannot be picked up as normal list content + yield ( + f"{name}_extra_byte", + invalid_test_case( + lambda rng=rng, typ=typ: serialize( + container_case_fn(rng, RandomizationMode.mode_max_count, typ) + ) + + b"\xff" + ), + ) + + if len(offsets) != 0: + # Note: there are many more ways to have invalid offsets, + # these are just example to get clients started looking into hardening ssz. + for mode in [ + RandomizationMode.mode_random, + RandomizationMode.mode_nil_count, + RandomizationMode.mode_one_count, + RandomizationMode.mode_max_count, + ]: + for index, offset_index in enumerate(offsets): + yield ( + f"{name}_{mode.to_name()}_offset_{offset_index}_plus_one", + invalid_test_case( + lambda rng=rng, + mode=mode, + typ=typ, + offset_index=offset_index: mod_offset( + b=serialize(container_case_fn(rng, mode, typ)), + offset_index=offset_index, + change=lambda x: x + 1, + ) + ), + ) + yield ( + f"{name}_{mode.to_name()}_offset_{offset_index}_zeroed", + invalid_test_case( + lambda rng=rng, + mode=mode, + typ=typ, + offset_index=offset_index: mod_offset( + b=serialize(container_case_fn(rng, mode, typ)), + offset_index=offset_index, + change=lambda x: 0, + ) + ), + ) + if index == 0: + yield ( + f"{name}_{mode.to_name()}_offset_{offset_index}_minus_one", + invalid_test_case( + lambda rng=rng, + mode=mode, + typ=typ, + offset_index=offset_index: mod_offset( + b=serialize(container_case_fn(rng, mode, typ)), + offset_index=offset_index, + change=lambda x: x - 1, + ) + ), + ) + if mode == RandomizationMode.mode_max_count: + serialized = serialize(container_case_fn(rng, mode, typ)) + serialized = serialized + serialized[:2] + yield ( + f"{name}_{mode.to_name()}_last_offset_{offset_index}_overflow", + invalid_test_case(lambda serialized=serialized: serialized), + ) + if mode == RandomizationMode.mode_one_count: + serialized = serialize(container_case_fn(rng, mode, typ)) + serialized = serialized + serialized[:1] + yield ( + f"{name}_{mode.to_name()}_last_offset_{offset_index}_wrong_byte_length", + invalid_test_case(lambda serialized=serialized: serialized), + ) diff --git a/tests/generators/ssz_generic/ssz_test_case.py b/tests/generators/runners/ssz_generic_cases/ssz_test_case.py similarity index 72% rename from tests/generators/ssz_generic/ssz_test_case.py rename to tests/generators/runners/ssz_generic_cases/ssz_test_case.py index 6cef4960b9..243d6e67b1 100644 --- a/tests/generators/ssz_generic/ssz_test_case.py +++ b/tests/generators/runners/ssz_generic_cases/ssz_test_case.py @@ -1,7 +1,8 @@ -from eth2spec.utils.ssz.ssz_impl import serialize, hash_tree_root +from collections.abc import Callable + from eth2spec.debug.encode import encode +from eth2spec.utils.ssz.ssz_impl import hash_tree_root, serialize from eth2spec.utils.ssz.ssz_typing import View -from typing import Callable def valid_test_case(value_fn: Callable[[], View]): @@ -9,11 +10,13 @@ def case_fn(): value = value_fn() yield "value", "data", encode(value) yield "serialized", "ssz", serialize(value) - yield "root", "meta", '0x' + hash_tree_root(value).hex() + yield "root", "meta", "0x" + hash_tree_root(value).hex() + return case_fn def invalid_test_case(bytez_fn: Callable[[], bytes]): def case_fn(): yield "serialized", "ssz", bytez_fn() + return case_fn diff --git a/tests/generators/runners/ssz_generic_cases/ssz_uints.py b/tests/generators/runners/ssz_generic_cases/ssz_uints.py new file mode 100644 index 0000000000..1f7c5661a5 --- /dev/null +++ b/tests/generators/runners/ssz_generic_cases/ssz_uints.py @@ -0,0 +1,77 @@ +from random import Random + +from eth2spec.debug.random_value import get_random_ssz_object, RandomizationMode +from eth2spec.utils.ssz.ssz_typing import BasicView, uint8, uint16, uint32, uint64, uint128, uint256 + +from .ssz_test_case import invalid_test_case, valid_test_case + + +def uint_case_fn(rng: Random, mode: RandomizationMode, typ: type[BasicView]): + return get_random_ssz_object( + rng, typ, max_bytes_length=typ.type_byte_length(), max_list_length=1, mode=mode, chaos=False + ) + + +UINT_TYPES = [uint8, uint16, uint32, uint64, uint128, uint256] + + +def valid_cases(): + rng = Random(1234) + for uint_type in UINT_TYPES: + mode = RandomizationMode.mode_random + byte_len = uint_type.type_byte_length() + yield ( + f"uint_{byte_len * 8}_last_byte_empty", + valid_test_case( + lambda uint_type=uint_type, byte_len=byte_len: uint_type( + (2 ** ((byte_len - 1) * 8)) - 1 + ) + ), + ) + for variation in range(5): + yield ( + f"uint_{byte_len * 8}_{mode.to_name()}_{variation}", + valid_test_case( + lambda rng=rng, mode=mode, uint_type=uint_type: uint_case_fn( + rng, mode, uint_type + ) + ), + ) + for mode in [RandomizationMode.mode_zero, RandomizationMode.mode_max]: + yield ( + f"uint_{byte_len * 8}_{mode.to_name()}", + valid_test_case( + lambda rng=rng, mode=mode, uint_type=uint_type: uint_case_fn( + rng, mode, uint_type + ) + ), + ) + + +def invalid_cases(): + for uint_type in UINT_TYPES: + byte_len = uint_type.type_byte_length() + yield ( + f"uint_{byte_len * 8}_one_too_high", + invalid_test_case( + lambda byte_len=byte_len: (2 ** (byte_len * 8)).to_bytes(byte_len + 1, "little") + ), + ) + for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]: + byte_len = uint_type.type_byte_length() + yield ( + f"uint_{byte_len * 8}_one_byte_longer", + invalid_test_case( + lambda byte_len=byte_len: (2 ** (byte_len * 8) - 1).to_bytes(byte_len + 1, "little") + ), + ) + for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]: + byte_len = uint_type.type_byte_length() + yield ( + f"uint_{byte_len * 8}_one_byte_shorter", + invalid_test_case( + lambda byte_len=byte_len: (2 ** ((byte_len - 1) * 8) - 1).to_bytes( + byte_len - 1, "little" + ) + ), + ) diff --git a/tests/generators/ssz_generic/uint_test_cases.py b/tests/generators/runners/ssz_generic_cases/uint_test_cases.py similarity index 85% rename from tests/generators/ssz_generic/uint_test_cases.py rename to tests/generators/runners/ssz_generic_cases/uint_test_cases.py index 6d6492c9e9..1f37df1775 100644 --- a/tests/generators/ssz_generic/uint_test_cases.py +++ b/tests/generators/runners/ssz_generic_cases/uint_test_cases.py @@ -3,14 +3,14 @@ from eth_utils import ( to_tuple, ) +from renderers import ( + render_test_case, +) import ssz from ssz.sedes import ( UInt, ) -from renderers import ( - render_test_case, -) random.seed(0) @@ -48,13 +48,15 @@ def generate_random_uint_test_cases(): def generate_uint_wrong_length_test_cases(): for bit_size in BIT_SIZES: sedes = UInt(bit_size) - lengths = sorted({ - 0, - sedes.length // 2, - sedes.length - 1, - sedes.length + 1, - sedes.length * 2, - }) + lengths = sorted( + { + 0, + sedes.length // 2, + sedes.length - 1, + sedes.length + 1, + sedes.length * 2, + } + ) for length in lengths: for _ in range(RANDOM_TEST_CASES_PER_LENGTH): tags = tuple(["atomic", "uint", "wrong_length"]) @@ -72,7 +74,7 @@ def generate_uint_bounds_test_cases(): for bit_size in BIT_SIZES: sedes = UInt(bit_size) - for value, tag in ((0, "uint_lower_bound"), (2 ** bit_size - 1, "uint_upper_bound")): + for value, tag in ((0, "uint_lower_bound"), (2**bit_size - 1, "uint_upper_bound")): serial = ssz.encode(value, sedes) yield render_test_case( sedes=sedes, @@ -89,7 +91,7 @@ def generate_uint_out_of_bounds_test_cases(): for bit_size in BIT_SIZES: sedes = UInt(bit_size) - for value, tag in ((-1, "uint_underflow"), (2 ** bit_size, "uint_overflow")): + for value, tag in ((-1, "uint_underflow"), (2**bit_size, "uint_overflow")): yield render_test_case( sedes=sedes, valid=False, diff --git a/tests/generators/runners/ssz_static.py b/tests/generators/runners/ssz_static.py new file mode 100644 index 0000000000..91c88a51e5 --- /dev/null +++ b/tests/generators/runners/ssz_static.py @@ -0,0 +1,104 @@ +import hashlib +from collections.abc import Iterable +from inspect import getmembers, isclass +from random import Random + +from eth2spec.debug import encode, random_value +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase, TestCasePart +from eth2spec.test.context import spec_targets +from eth2spec.test.helpers.constants import MAINNET, MINIMAL, TESTGEN_FORKS +from eth2spec.utils.ssz.ssz_impl import ( + hash_tree_root, + serialize, +) +from eth2spec.utils.ssz.ssz_typing import Container + +MAX_BYTES_LENGTH = 1000 +MAX_LIST_LENGTH = 10 + + +def create_test_case( + seed: int, typ, mode: random_value.RandomizationMode, chaos: bool +) -> Iterable[TestCasePart]: + rng = Random(seed) + value = random_value.get_random_ssz_object( + rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos + ) + yield "value", "data", encode.encode(value) + yield "serialized", "ssz", serialize(value) + roots_data = {"root": "0x" + hash_tree_root(value).hex()} + yield "roots", "data", roots_data + + +def get_spec_ssz_types(spec): + return [ + (name, value) + for (name, value) in getmembers(spec, isclass) + if issubclass(value, Container) + and value != Container # only the subclasses, not the imported base class + ] + + +def deterministic_seed(**kwargs) -> int: + """Need this since hash() is not deterministic between runs.""" + m = hashlib.sha256() + for k, v in sorted(kwargs.items()): + m.update(f"{k}={v}".encode()) + return int.from_bytes(m.digest()[:8], "little") + + +def ssz_static_cases( + fork_name: str, + preset_name: str, + name, + ssz_type, + mode: random_value.RandomizationMode, + chaos: bool, + count: int, +): + random_mode_name = mode.to_name() + for i in range(count): + seed = deterministic_seed( + fork_name=fork_name, + preset_name=preset_name, + name=name, + ssz_type_name=ssz_type.__name__, + random_mode_name=random_mode_name, + chaos=chaos, + count=count, + i=i, + ) + + def case_fn(seed=seed): + """Need to bind to seed value.""" + return create_test_case(seed, ssz_type, mode, chaos) + + yield TestCase( + fork_name=fork_name, + preset_name=preset_name, + runner_name="ssz_static", + handler_name=name, + suite_name=f"ssz_{random_mode_name}{'_chaos' if chaos else ''}", + case_name=f"case_{i}", + case_fn=case_fn, + ) + + +def get_test_cases() -> Iterable[TestCase]: + settings = [] + for mode in random_value.RandomizationMode: + settings.append((MINIMAL, mode, False, 30)) + settings.append((MINIMAL, random_value.RandomizationMode.mode_random, True, 30)) + settings.append((MAINNET, random_value.RandomizationMode.mode_random, False, 5)) + + test_cases = [] + for fork in TESTGEN_FORKS: + for preset, mode, chaos, cases_if_random in settings: + count = cases_if_random if chaos or mode.is_changing() else 1 + spec = spec_targets[preset][fork] + for name, ssz_type in get_spec_ssz_types(spec): + test_cases.extend( + ssz_static_cases(fork, preset, name, ssz_type, mode, chaos, count) + ) + + return test_cases diff --git a/tests/generators/runners/sync.py b/tests/generators/runners/sync.py new file mode 100644 index 0000000000..26708bf4c6 --- /dev/null +++ b/tests/generators/runners/sync.py @@ -0,0 +1,8 @@ +from collections.abc import Iterable + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import get_test_cases_for + + +def get_test_cases() -> Iterable[TestCase]: + return get_test_cases_for("sync") diff --git a/tests/generators/runners/transition.py b/tests/generators/runners/transition.py new file mode 100644 index 0000000000..78cd8e579b --- /dev/null +++ b/tests/generators/runners/transition.py @@ -0,0 +1,25 @@ +from collections.abc import Iterable +from importlib import import_module + +from eth2spec.gen_helpers.gen_base.gen_typing import TestCase +from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests, get_expected_modules +from eth2spec.test.helpers.constants import ALL_PRESETS, POST_FORK_OF + + +def get_test_cases() -> Iterable[TestCase]: + test_cases = [] + for preset in ALL_PRESETS: + for prefork, postfork in POST_FORK_OF.items(): + for mod in get_expected_modules("transition"): + tests_src = import_module(mod) + test_cases.extend( + generate_from_tests( + runner_name="transition", + handler_name="core", + src=tests_src, + fork_name=postfork, + preset_name=preset, + phase=prefork, + ) + ) + return test_cases diff --git a/tests/generators/sanity/README.md b/tests/generators/sanity/README.md deleted file mode 100644 index cbc6aef06d..0000000000 --- a/tests/generators/sanity/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Sanity tests - -Sanity tests cover regular state-transitions in a common block-list format, to ensure the basics work. - -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 deleted file mode 100644 index f5a6ccb418..0000000000 --- a/tests/generators/sanity/main.py +++ /dev/null @@ -1,23 +0,0 @@ -from eth2spec.test.helpers.constants import PHASE0, ALTAIR, MERGE -from eth2spec.gen_helpers.gen_from_tests.gen import run_state_test_generators - - -if __name__ == "__main__": - phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ - 'blocks', - 'slots', - ]} - altair_mods = {**{key: 'eth2spec.test.altair.sanity.test_' + key for key in [ - 'blocks', - ]}, **phase_0_mods} - merge_mods = {**{key: 'eth2spec.test.merge.sanity.test_' + key for key in [ - 'blocks', - ]}, **altair_mods} - - all_mods = { - PHASE0: phase_0_mods, - ALTAIR: altair_mods, - MERGE: merge_mods, - } - - run_state_test_generators(runner_name="sanity", all_mods=all_mods) diff --git a/tests/generators/sanity/requirements.txt b/tests/generators/sanity/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/sanity/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/shuffling/README.md b/tests/generators/shuffling/README.md deleted file mode 100644 index 81ddaba15f..0000000000 --- a/tests/generators/shuffling/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Shuffling Tests - -Tests for the swap-or-not shuffling in the beacon chain. - -Tips for initial shuffling write: -- run with `round_count = 1` first, do the same with pyspec. -- start with permute index -- optimized shuffling implementations: - - vitalik, Python: https://github.com/ethereum/consensus-specs/pull/576#issue-250741806 - - protolambda, Go: https://github.com/protolambda/eth2-shuffle diff --git a/tests/generators/shuffling/main.py b/tests/generators/shuffling/main.py deleted file mode 100644 index b85fd42a20..0000000000 --- a/tests/generators/shuffling/main.py +++ /dev/null @@ -1,57 +0,0 @@ -from eth_utils import to_tuple -from typing import Iterable - -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.test.helpers.typing import PresetBaseName - -from eth2spec.phase0 import mainnet as spec_mainnet, minimal as spec_minimal -from eth2spec.test.helpers.constants import PHASE0, MINIMAL, MAINNET - - -def shuffling_case_fn(spec, seed, count): - yield 'mapping', 'data', { - 'seed': '0x' + seed.hex(), - 'count': count, - 'mapping': [int(spec.compute_shuffled_index(i, count, seed)) for i in range(count)] - } - - -def shuffling_case(spec, seed, count): - return f'shuffle_0x{seed.hex()}_{count}', lambda: shuffling_case_fn(spec, seed, count) - - -@to_tuple -def shuffling_test_cases(spec): - for seed in [spec.hash(seed_init_value.to_bytes(length=4, byteorder='little')) for seed_init_value in range(30)]: - for count in [0, 1, 2, 3, 5, 10, 33, 100, 1000, 9999]: - yield shuffling_case(spec, seed, count) - - -def create_provider(preset_name: PresetBaseName) -> gen_typing.TestProvider: - - def prepare_fn() -> None: - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - if preset_name == MAINNET: - spec = spec_mainnet - elif preset_name == MINIMAL: - spec = spec_minimal - else: - raise Exception(f"unrecognized preset: {preset_name}") - for (case_name, case_fn) in shuffling_test_cases(spec): - yield gen_typing.TestCase( - fork_name=PHASE0, - preset_name=preset_name, - runner_name='shuffling', - handler_name='core', - suite_name='shuffle', - case_name=case_name, - case_fn=case_fn - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - gen_runner.run_generator("shuffling", [create_provider(MINIMAL), create_provider(MAINNET)]) diff --git a/tests/generators/shuffling/requirements.txt b/tests/generators/shuffling/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/shuffling/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/ssz_generic/main.py b/tests/generators/ssz_generic/main.py deleted file mode 100644 index 2e96ce2e87..0000000000 --- a/tests/generators/ssz_generic/main.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Iterable -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -import ssz_basic_vector -import ssz_bitlist -import ssz_bitvector -import ssz_boolean -import ssz_uints -import ssz_container -from eth2spec.test.helpers.constants import PHASE0 - - -def create_provider(handler_name: str, suite_name: str, case_maker) -> gen_typing.TestProvider: - - def prepare_fn() -> None: - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - for (case_name, case_fn) in case_maker(): - yield gen_typing.TestCase( - fork_name=PHASE0, - preset_name="general", - runner_name='ssz_generic', - handler_name=handler_name, - suite_name=suite_name, - case_name=case_name, - case_fn=case_fn - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - gen_runner.run_generator("ssz_generic", [ - create_provider("basic_vector", "valid", ssz_basic_vector.valid_cases), - create_provider("basic_vector", "invalid", ssz_basic_vector.invalid_cases), - create_provider("bitlist", "valid", ssz_bitlist.valid_cases), - create_provider("bitlist", "invalid", ssz_bitlist.invalid_cases), - create_provider("bitvector", "valid", ssz_bitvector.valid_cases), - create_provider("bitvector", "invalid", ssz_bitvector.invalid_cases), - create_provider("boolean", "valid", ssz_boolean.valid_cases), - create_provider("boolean", "invalid", ssz_boolean.invalid_cases), - create_provider("uints", "valid", ssz_uints.valid_cases), - create_provider("uints", "invalid", ssz_uints.invalid_cases), - create_provider("containers", "valid", ssz_container.valid_cases), - create_provider("containers", "invalid", ssz_container.invalid_cases), - ]) diff --git a/tests/generators/ssz_generic/requirements.txt b/tests/generators/ssz_generic/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/ssz_generic/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/ssz_generic/ssz_basic_vector.py b/tests/generators/ssz_generic/ssz_basic_vector.py deleted file mode 100644 index 51dfd4ba1a..0000000000 --- a/tests/generators/ssz_generic/ssz_basic_vector.py +++ /dev/null @@ -1,65 +0,0 @@ -from ssz_test_case import invalid_test_case, valid_test_case -from eth2spec.utils.ssz.ssz_typing import boolean, uint8, uint16, uint32, uint64, uint128, uint256, Vector, BasicView -from eth2spec.utils.ssz.ssz_impl import serialize -from random import Random -from typing import Dict, Type -from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object - - -def basic_vector_case_fn(rng: Random, mode: RandomizationMode, elem_type: Type[BasicView], length: int): - return get_random_ssz_object(rng, Vector[elem_type, length], - max_bytes_length=length * 8, - max_list_length=length, - mode=mode, chaos=False) - - -BASIC_TYPES: Dict[str, Type[BasicView]] = { - 'bool': boolean, - 'uint8': uint8, - 'uint16': uint16, - 'uint32': uint32, - 'uint64': uint64, - 'uint128': uint128, - 'uint256': uint256 -} - - -def valid_cases(): - rng = Random(1234) - for (name, typ) in BASIC_TYPES.items(): - random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] - if name != 'bool': - random_modes.append(RandomizationMode.mode_random) - for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: - for mode in random_modes: - yield f'vec_{name}_{length}_{mode.to_name()}', \ - valid_test_case(lambda: basic_vector_case_fn(rng, mode, typ, length)) - - -def invalid_cases(): - # zero length vectors are illegal - for (name, typ) in BASIC_TYPES.items(): - yield f'vec_{name}_0', invalid_test_case(lambda: b'') - - rng = Random(1234) - for (name, typ) in BASIC_TYPES.items(): - random_modes = [RandomizationMode.mode_zero, RandomizationMode.mode_max] - if name != 'bool': - random_modes.append(RandomizationMode.mode_random) - for length in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: - yield f'vec_{name}_{length}_nil', invalid_test_case(lambda: b'') - for mode in random_modes: - if length == 1: - # empty bytes, no elements. It may seem valid, but empty fixed-size elements are not valid SSZ. - yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \ - invalid_test_case(lambda: b"") - else: - yield f'vec_{name}_{length}_{mode.to_name()}_one_less', \ - invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length - 1))) - yield f'vec_{name}_{length}_{mode.to_name()}_one_more', \ - invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length + 1))) - yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_less', \ - invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length))[:-1]) - yield f'vec_{name}_{length}_{mode.to_name()}_one_byte_more', \ - invalid_test_case(lambda: serialize(basic_vector_case_fn(rng, mode, typ, length)) - + serialize(basic_vector_case_fn(rng, mode, uint8, 1))) diff --git a/tests/generators/ssz_generic/ssz_bitlist.py b/tests/generators/ssz_generic/ssz_bitlist.py deleted file mode 100644 index d1a940eee8..0000000000 --- a/tests/generators/ssz_generic/ssz_bitlist.py +++ /dev/null @@ -1,37 +0,0 @@ -from ssz_test_case import invalid_test_case, valid_test_case -from eth2spec.utils.ssz.ssz_typing import Bitlist -from eth2spec.utils.ssz.ssz_impl import serialize -from random import Random -from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object - - -def bitlist_case_fn(rng: Random, mode: RandomizationMode, limit: int): - return get_random_ssz_object(rng, Bitlist[limit], - max_bytes_length=(limit // 8) + 1, - max_list_length=limit, - mode=mode, chaos=False) - - -def valid_cases(): - rng = Random(1234) - for size in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: - for variation in range(5): - for mode in [RandomizationMode.mode_nil_count, - RandomizationMode.mode_max_count, - RandomizationMode.mode_random, - RandomizationMode.mode_zero, - RandomizationMode.mode_max]: - yield f'bitlist_{size}_{mode.to_name()}_{variation}', \ - valid_test_case(lambda: bitlist_case_fn(rng, mode, size)) - - -def invalid_cases(): - yield 'bitlist_no_delimiter_empty', invalid_test_case(lambda: b'') - yield 'bitlist_no_delimiter_zero_byte', invalid_test_case(lambda: b'\x00') - yield 'bitlist_no_delimiter_zeroes', invalid_test_case(lambda: b'\x00\x00\x00') - rng = Random(1234) - for (typ_limit, test_limit) in [(1, 2), (1, 8), (1, 9), (2, 3), (3, 4), (4, 5), - (5, 6), (8, 9), (32, 64), (32, 33), (512, 513)]: - yield f'bitlist_{typ_limit}_but_{test_limit}', \ - invalid_test_case(lambda: serialize( - bitlist_case_fn(rng, RandomizationMode.mode_max_count, test_limit))) diff --git a/tests/generators/ssz_generic/ssz_bitvector.py b/tests/generators/ssz_generic/ssz_bitvector.py deleted file mode 100644 index d846140680..0000000000 --- a/tests/generators/ssz_generic/ssz_bitvector.py +++ /dev/null @@ -1,42 +0,0 @@ -from ssz_test_case import invalid_test_case, valid_test_case -from eth2spec.utils.ssz.ssz_typing import Bitvector -from eth2spec.utils.ssz.ssz_impl import serialize -from random import Random -from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object - - -def bitvector_case_fn(rng: Random, mode: RandomizationMode, size: int, invalid_making_pos: int=None): - bits = get_random_ssz_object(rng, Bitvector[size], - max_bytes_length=(size + 7) // 8, - max_list_length=size, - mode=mode, chaos=False) - if invalid_making_pos is not None and invalid_making_pos <= size: - already_invalid = False - for i in range(invalid_making_pos, size): - if bits[i]: - already_invalid = True - if not already_invalid: - bits[invalid_making_pos] = True - return bits - - -def valid_cases(): - rng = Random(1234) - for size in [1, 2, 3, 4, 5, 8, 16, 31, 512, 513]: - for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]: - yield f'bitvec_{size}_{mode.to_name()}', valid_test_case(lambda: bitvector_case_fn(rng, mode, size)) - - -def invalid_cases(): - # zero length bitvecors are illegal - yield 'bitvec_0', invalid_test_case(lambda: b'') - rng = Random(1234) - # Create a vector with test_size bits, but make the type typ_size instead, - # which is invalid when used with the given type size - # (and a bit set just after typ_size bits if necessary to avoid the valid 0 padding-but-same-last-byte case) - for (typ_size, test_size) in [(1, 2), (2, 3), (3, 4), (4, 5), - (5, 6), (8, 9), (9, 8), (16, 8), (32, 33), (512, 513)]: - for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]: - yield f'bitvec_{typ_size}_{mode.to_name()}_{test_size}', \ - invalid_test_case(lambda: serialize(bitvector_case_fn(rng, mode, test_size, - invalid_making_pos=typ_size))) diff --git a/tests/generators/ssz_generic/ssz_boolean.py b/tests/generators/ssz_generic/ssz_boolean.py deleted file mode 100644 index ec22c01be9..0000000000 --- a/tests/generators/ssz_generic/ssz_boolean.py +++ /dev/null @@ -1,14 +0,0 @@ -from ssz_test_case import valid_test_case, invalid_test_case -from eth2spec.utils.ssz.ssz_typing import boolean - - -def valid_cases(): - yield "true", valid_test_case(lambda: boolean(True)) - yield "false", valid_test_case(lambda: boolean(False)) - - -def invalid_cases(): - yield "byte_2", invalid_test_case(lambda: b'\x02') - yield "byte_rev_nibble", invalid_test_case(lambda: b'\x10') - yield "byte_0x80", invalid_test_case(lambda: b'\x80') - yield "byte_full", invalid_test_case(lambda: b'\xff') diff --git a/tests/generators/ssz_generic/ssz_container.py b/tests/generators/ssz_generic/ssz_container.py deleted file mode 100644 index 9cd155f76b..0000000000 --- a/tests/generators/ssz_generic/ssz_container.py +++ /dev/null @@ -1,120 +0,0 @@ -from ssz_test_case import invalid_test_case, valid_test_case -from eth2spec.utils.ssz.ssz_typing import View, Container, byte, uint8, uint16, \ - uint32, uint64, List, ByteList, Vector, Bitvector, Bitlist -from eth2spec.utils.ssz.ssz_impl import serialize -from random import Random -from typing import Dict, Tuple, Sequence, Callable, Type -from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object - - -class SingleFieldTestStruct(Container): - A: byte - - -class SmallTestStruct(Container): - A: uint16 - B: uint16 - - -class FixedTestStruct(Container): - A: uint8 - B: uint64 - C: uint32 - - -class VarTestStruct(Container): - A: uint16 - B: List[uint16, 1024] - C: uint8 - - -class ComplexTestStruct(Container): - A: uint16 - B: List[uint16, 128] - C: uint8 - D: ByteList[256] - E: VarTestStruct - F: Vector[FixedTestStruct, 4] - G: Vector[VarTestStruct, 2] - - -class BitsStruct(Container): - A: Bitlist[5] - B: Bitvector[2] - C: Bitvector[1] - D: Bitlist[6] - E: Bitvector[8] - - -def container_case_fn(rng: Random, mode: RandomizationMode, typ: Type[View]): - return get_random_ssz_object(rng, typ, - max_bytes_length=2000, - max_list_length=2000, - mode=mode, chaos=False) - - -PRESET_CONTAINERS: Dict[str, Tuple[Type[View], Sequence[int]]] = { - 'SingleFieldTestStruct': (SingleFieldTestStruct, []), - 'SmallTestStruct': (SmallTestStruct, []), - 'FixedTestStruct': (FixedTestStruct, []), - 'VarTestStruct': (VarTestStruct, [2]), - 'ComplexTestStruct': (ComplexTestStruct, [2, 2 + 4 + 1, 2 + 4 + 1 + 4]), - 'BitsStruct': (BitsStruct, [0, 4 + 1 + 1, 4 + 1 + 1 + 4]), -} - - -def valid_cases(): - rng = Random(1234) - for (name, (typ, offsets)) in PRESET_CONTAINERS.items(): - for mode in [RandomizationMode.mode_zero, RandomizationMode.mode_max]: - yield f'{name}_{mode.to_name()}', valid_test_case(lambda: container_case_fn(rng, mode, typ)) - random_modes = [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max] - if len(offsets) != 0: - random_modes.extend([RandomizationMode.mode_nil_count, - RandomizationMode.mode_one_count, - RandomizationMode.mode_max_count]) - for mode in random_modes: - for variation in range(10): - yield f'{name}_{mode.to_name()}_{variation}', \ - valid_test_case(lambda: container_case_fn(rng, mode, typ)) - for variation in range(3): - yield f'{name}_{mode.to_name()}_chaos_{variation}', \ - valid_test_case(lambda: container_case_fn(rng, mode, typ)) - - -def mod_offset(b: bytes, offset_index: int, change: Callable[[int], int]): - return b[:offset_index] + \ - (change(int.from_bytes(b[offset_index:offset_index + 4], byteorder='little')) & 0xffffffff) \ - .to_bytes(length=4, byteorder='little') + \ - b[offset_index + 4:] - - -def invalid_cases(): - rng = Random(1234) - for (name, (typ, offsets)) in PRESET_CONTAINERS.items(): - # using mode_max_count, so that the extra byte cannot be picked up as normal list content - yield f'{name}_extra_byte', \ - invalid_test_case(lambda: serialize( - container_case_fn(rng, RandomizationMode.mode_max_count, typ)) + b'\xff') - - if len(offsets) != 0: - # Note: there are many more ways to have invalid offsets, - # these are just example to get clients started looking into hardening ssz. - for mode in [RandomizationMode.mode_random, - RandomizationMode.mode_nil_count, - RandomizationMode.mode_one_count, - RandomizationMode.mode_max_count]: - if len(offsets) != 0: - for offset_index in offsets: - yield f'{name}_offset_{offset_index}_plus_one', \ - invalid_test_case(lambda: mod_offset( - b=serialize(container_case_fn(rng, mode, typ)), - offset_index=offset_index, - change=lambda x: x + 1 - )) - yield f'{name}_offset_{offset_index}_zeroed', \ - invalid_test_case(lambda: mod_offset( - b=serialize(container_case_fn(rng, mode, typ)), - offset_index=offset_index, - change=lambda x: 0 - )) diff --git a/tests/generators/ssz_generic/ssz_uints.py b/tests/generators/ssz_generic/ssz_uints.py deleted file mode 100644 index 896443f4cc..0000000000 --- a/tests/generators/ssz_generic/ssz_uints.py +++ /dev/null @@ -1,42 +0,0 @@ -from ssz_test_case import invalid_test_case, valid_test_case -from eth2spec.utils.ssz.ssz_typing import BasicView, uint8, uint16, uint32, uint64, uint128, uint256 -from random import Random -from typing import Type -from eth2spec.debug.random_value import RandomizationMode, get_random_ssz_object - - -def uint_case_fn(rng: Random, mode: RandomizationMode, typ: Type[BasicView]): - return get_random_ssz_object(rng, typ, - max_bytes_length=typ.type_byte_length(), - max_list_length=1, - mode=mode, chaos=False) - - -UINT_TYPES = [uint8, uint16, uint32, uint64, uint128, uint256] - - -def valid_cases(): - rng = Random(1234) - for uint_type in UINT_TYPES: - byte_len = uint_type.type_byte_length() - yield f'uint_{byte_len * 8}_last_byte_empty', \ - valid_test_case(lambda: uint_type((2 ** ((byte_len - 1) * 8)) - 1)) - for variation in range(5): - for mode in [RandomizationMode.mode_random, RandomizationMode.mode_zero, RandomizationMode.mode_max]: - yield f'uint_{byte_len * 8}_{mode.to_name()}_{variation}', \ - valid_test_case(lambda: uint_case_fn(rng, mode, uint_type)) - - -def invalid_cases(): - for uint_type in UINT_TYPES: - byte_len = uint_type.type_byte_length() - yield f'uint_{byte_len * 8}_one_too_high', \ - invalid_test_case(lambda: (2 ** (byte_len * 8)).to_bytes(byte_len + 1, 'little')) - for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]: - byte_len = uint_type.type_byte_length() - yield f'uint_{byte_len * 8}_one_byte_longer', \ - invalid_test_case(lambda: (2 ** (byte_len * 8) - 1).to_bytes(byte_len + 1, 'little')) - for uint_type in [uint8, uint16, uint32, uint64, uint128, uint256]: - byte_len = uint_type.type_byte_length() - yield f'uint_{byte_len * 8}_one_byte_shorter', \ - invalid_test_case(lambda: (2 ** ((byte_len - 1) * 8) - 1).to_bytes(byte_len - 1, 'little')) diff --git a/tests/generators/ssz_static/README.md b/tests/generators/ssz_static/README.md deleted file mode 100644 index 3434fe174b..0000000000 --- a/tests/generators/ssz_static/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# SSZ-static - -The purpose of this test-generator is to provide test-vectors for the most important applications of SSZ: - the serialization and hashing of Ethereum data type. - -Test-format documentation can be found [here](../../formats/ssz_static/README.md). diff --git a/tests/generators/ssz_static/main.py b/tests/generators/ssz_static/main.py deleted file mode 100644 index 3f894ea796..0000000000 --- a/tests/generators/ssz_static/main.py +++ /dev/null @@ -1,88 +0,0 @@ -from random import Random -from typing import Iterable -from inspect import getmembers, isclass - -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing - -from eth2spec.debug import random_value, encode -from eth2spec.test.helpers.constants import TESTGEN_FORKS, MINIMAL, MAINNET -from eth2spec.test.context import spec_targets -from eth2spec.utils.ssz.ssz_typing import Container -from eth2spec.utils.ssz.ssz_impl import ( - hash_tree_root, - serialize, -) - - -MAX_BYTES_LENGTH = 1000 -MAX_LIST_LENGTH = 10 - - -def create_test_case(rng: Random, typ, - mode: random_value.RandomizationMode, chaos: bool) -> Iterable[gen_typing.TestCasePart]: - value = random_value.get_random_ssz_object(rng, typ, MAX_BYTES_LENGTH, MAX_LIST_LENGTH, mode, chaos) - yield "value", "data", encode.encode(value) - yield "serialized", "ssz", serialize(value) - roots_data = { - "root": '0x' + hash_tree_root(value).hex() - } - yield "roots", "data", roots_data - - -def get_spec_ssz_types(spec): - return [ - (name, value) for (name, value) in getmembers(spec, isclass) - if issubclass(value, Container) and value != Container # only the subclasses, not the imported base class - ] - - -def ssz_static_cases(fork_name: str, preset_name: str, seed: int, name, ssz_type, - mode: random_value.RandomizationMode, chaos: bool, count: int): - random_mode_name = mode.to_name() - - # Reproducible RNG - rng = Random(seed) - - for i in range(count): - yield gen_typing.TestCase( - fork_name=fork_name, - preset_name=preset_name, - runner_name='ssz_static', - handler_name=name, - suite_name=f"ssz_{random_mode_name}{'_chaos' if chaos else ''}", - case_name=f"case_{i}", - case_fn=lambda: create_test_case(rng, ssz_type, mode, chaos) - ) - - -def create_provider(fork_name, preset_name: str, seed: int, mode: random_value.RandomizationMode, chaos: bool, - cases_if_random: int) -> gen_typing.TestProvider: - def prepare_fn() -> None: - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - count = cases_if_random if chaos or mode.is_changing() else 1 - spec = spec_targets[preset_name][fork_name] - - for (i, (name, ssz_type)) in enumerate(get_spec_ssz_types(spec)): - yield from ssz_static_cases(fork_name, preset_name, seed * 1000 + i, name, ssz_type, mode, chaos, count) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -if __name__ == "__main__": - # [(seed, config name, randomization mode, chaos on/off, cases_if_random)] - settings = [] - seed = 1 - for mode in random_value.RandomizationMode: - settings.append((seed, MINIMAL, mode, False, 30)) - seed += 1 - settings.append((seed, MINIMAL, random_value.RandomizationMode.mode_random, True, 30)) - seed += 1 - settings.append((seed, MAINNET, random_value.RandomizationMode.mode_random, False, 5)) - seed += 1 - for fork in TESTGEN_FORKS: - gen_runner.run_generator("ssz_static", [ - create_provider(fork, preset_name, seed, mode, chaos, cases_if_random) - for (seed, preset_name, mode, chaos, cases_if_random) in settings - ]) diff --git a/tests/generators/ssz_static/requirements.txt b/tests/generators/ssz_static/requirements.txt deleted file mode 100644 index 1822486863..0000000000 --- a/tests/generators/ssz_static/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] diff --git a/tests/generators/transition/main.py b/tests/generators/transition/main.py deleted file mode 100644 index 2ded56a13b..0000000000 --- a/tests/generators/transition/main.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Iterable - -from eth2spec.test.helpers.constants import ALTAIR, MINIMAL, MAINNET, PHASE0 -from eth2spec.test.altair.transition import test_transition as test_altair_transition - -from eth2spec.gen_helpers.gen_base import gen_runner, gen_typing -from eth2spec.gen_helpers.gen_from_tests.gen import generate_from_tests - - -def create_provider(tests_src, preset_name: str, pre_fork_name: str, post_fork_name: str) -> gen_typing.TestProvider: - - def prepare_fn() -> None: - return - - def cases_fn() -> Iterable[gen_typing.TestCase]: - return generate_from_tests( - runner_name='transition', - handler_name='core', - src=tests_src, - fork_name=post_fork_name, - phase=pre_fork_name, - preset_name=preset_name, - ) - - return gen_typing.TestProvider(prepare=prepare_fn, make_cases=cases_fn) - - -TRANSITION_TESTS = ((PHASE0, ALTAIR, test_altair_transition),) - - -if __name__ == "__main__": - for pre_fork, post_fork, transition_test_module in TRANSITION_TESTS: - gen_runner.run_generator("transition", [ - create_provider(transition_test_module, MINIMAL, pre_fork, post_fork), - create_provider(transition_test_module, MAINNET, pre_fork, post_fork), - ]) diff --git a/tests/generators/transition/requirements.txt b/tests/generators/transition/requirements.txt deleted file mode 100644 index 735f863faa..0000000000 --- a/tests/generators/transition/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -pytest>=4.4 -../../../[generator] \ No newline at end of file diff --git a/tests/infra/test_md_to_spec.py b/tests/infra/test_md_to_spec.py new file mode 100644 index 0000000000..4f31d000ce --- /dev/null +++ b/tests/infra/test_md_to_spec.py @@ -0,0 +1,335 @@ +from pathlib import Path + +import pytest + +from pysetup.md_to_spec import MarkdownToSpec +from pysetup.typing import SpecObject + + +@pytest.fixture +def dummy_preset(): + return {"EXAMPLE": "1"} + + +@pytest.fixture +def dummy_config(): + return {"CONFIG": "2"} + + +@pytest.fixture +def dummy_file(tmp_path): + file = tmp_path / "dummy.md" + file.write_text("# Dummy\n") + return file + + +def test_constructor_initializes_fields(dummy_file, dummy_preset, dummy_config): + preset_name = "mainnet" + m2s = MarkdownToSpec( + file_name=Path(dummy_file), + preset=dummy_preset, + config=dummy_config, + preset_name=preset_name, + ) + assert m2s.preset == dummy_preset + assert m2s.config == dummy_config + assert m2s.preset_name == preset_name + assert isinstance(m2s.spec, dict) + assert isinstance(m2s.all_custom_types, dict) + assert hasattr(m2s, "document_iterator") + assert m2s.current_heading_name is None + + +def test_run_returns_spec_object(dummy_file, dummy_preset, dummy_config): + preset_name = "mainnet" + m2s = MarkdownToSpec( + file_name=Path(dummy_file), + preset=dummy_preset, + config=dummy_config, + preset_name=preset_name, + ) + spec_obj = m2s.run() + # Check that the result is of the expected type + + assert isinstance(spec_obj, SpecObject) + + +def test_run_includes_table_in_specobject(tmp_path, dummy_preset, dummy_config): + # Create a markdown file with a simple markdown table + md_content = """ +# Example + +| Name | Value | Description | +|---------|--------------|------------------| +| CONST_A | uint64(42) | Example constant | +| CONST_B | Bytes32(0x01)| Another constant | +""" + file = tmp_path / "table.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The constant should be present in the SpecObject's constant_vars + assert "CONST_A" in spec_obj.constant_vars + assert spec_obj.constant_vars["CONST_A"].type_name == "uint64" + assert spec_obj.constant_vars["CONST_A"].value == "42" + assert "CONST_B" in spec_obj.constant_vars + assert spec_obj.constant_vars["CONST_B"].type_name == "Bytes32" + assert spec_obj.constant_vars["CONST_B"].value == "0x01" + + +def test_run_includes_list_of_records_table(tmp_path, dummy_preset, dummy_config): + md_content = """ + + +| Epoch | Max Blobs Per Block | Description | +| --------------------------- | ------------------- | -------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | +""" + file = tmp_path / "list_of_records.md" + file.write_text(md_content) + # The config must have a 'BLOB_SCHEDULE' key with the expected structure for mainnet + config = dummy_config.copy() + config["BLOB_SCHEDULE"] = [ + {"EPOCH": "269568", "MAX_BLOBS_PER_BLOCK": "6"}, + {"EPOCH": "364032", "MAX_BLOBS_PER_BLOCK": "9"}, + ] + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The result should have 'BLOB_SCHEDULE' in config_vars + assert "BLOB_SCHEDULE" in spec_obj.config_vars + # The value should be a list of dicts with type constructors applied + assert isinstance(spec_obj.config_vars["BLOB_SCHEDULE"], list) + assert spec_obj.config_vars["BLOB_SCHEDULE"][0]["EPOCH"] == "Epoch(269568)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][0]["MAX_BLOBS_PER_BLOCK"] == "uint64(6)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][1]["EPOCH"] == "Epoch(364032)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][1]["MAX_BLOBS_PER_BLOCK"] == "uint64(9)" + + +def test_run_includes_list_of_records_table_minimal(tmp_path, dummy_preset, dummy_config): + md_content = """ + + +| Epoch | Max Blobs Per Block | Description | +| --------------------------- | ------------------- | -------------------------------- | +| `Epoch(269568)` **Deneb** | `uint64(6)` | The limit is set to `6` blobs | +| `Epoch(364032)` **Electra** | `uint64(9)` | The limit is raised to `9` blobs | +""" + file = tmp_path / "list_of_records_minimal.md" + file.write_text(md_content) + config = dummy_config.copy() + # Use different values than the table for minimal preset + config["BLOB_SCHEDULE"] = [ + {"EPOCH": "2", "MAX_BLOBS_PER_BLOCK": "3"}, + {"EPOCH": "4", "MAX_BLOBS_PER_BLOCK": "5"}, + ] + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=config, + preset_name="minimal", + ) + spec_obj = m2s.run() + assert "BLOB_SCHEDULE" in spec_obj.config_vars + assert isinstance(spec_obj.config_vars["BLOB_SCHEDULE"], list) + # The result should follow the config, not the table + assert spec_obj.config_vars["BLOB_SCHEDULE"][0]["EPOCH"] == "Epoch(2)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][0]["MAX_BLOBS_PER_BLOCK"] == "uint64(3)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][1]["EPOCH"] == "Epoch(4)" + assert spec_obj.config_vars["BLOB_SCHEDULE"][1]["MAX_BLOBS_PER_BLOCK"] == "uint64(5)" + + +def test_run_includes_python_function(tmp_path, dummy_preset, dummy_config): + md_content = """ +#### `compute_epoch_at_slot` + +```python +def compute_epoch_at_slot(slot: Slot) -> Epoch: + \"\"\" + Return the epoch number at slot. + \"\"\" + return Epoch(slot // SLOTS_PER_EPOCH) +``` +""" + file = tmp_path / "function.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The function should be present in the SpecObject's functions + assert "compute_epoch_at_slot" in spec_obj.functions + func_src = spec_obj.functions["compute_epoch_at_slot"] + assert "def compute_epoch_at_slot(slot: Slot) -> Epoch" in func_src + assert "return Epoch(slot // SLOTS_PER_EPOCH)" in func_src + + +def test_run_includes_python_class_container(tmp_path, dummy_preset, dummy_config): + md_content = """ +#### `Checkpoint` + +```python +class Checkpoint(Container): + epoch: Epoch + root: Root +``` +""" + file = tmp_path / "class_container.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The class should be present in the SpecObject's ssz_objects + assert "Checkpoint" in spec_obj.ssz_objects + class_src = spec_obj.ssz_objects["Checkpoint"] + assert "class Checkpoint(Container):" in class_src + assert "epoch: Epoch" in class_src + assert "root: Root" in class_src + + +def test_run_includes_python_dataclass(tmp_path, dummy_preset, dummy_config): + md_content = """ +## Helpers + +### `PayloadAttributes` + +Used to signal to initiate the payload build process via `notify_forkchoice_updated`. + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress +``` +""" + file = tmp_path / "dataclass.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The dataclass should be present in the SpecObject's dataclasses + assert "PayloadAttributes" in spec_obj.dataclasses + class_src = spec_obj.dataclasses["PayloadAttributes"] + assert "@dataclass" in class_src + assert "class PayloadAttributes(object):" in class_src + assert "timestamp: uint64" in class_src + assert "prev_randao: Bytes32" in class_src + assert "suggested_fee_recipient: ExecutionAddress" in class_src + + +def test_run_skips_predefined_type_rows(tmp_path, dummy_preset, dummy_config): + md_content = """ +## Cryptographic types + +| Name | SSZ equivalent | Description | +| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | ------------------------------------------------------------ | +| [`PolynomialCoeff`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L20-L24) | `List[BLSFieldElement, FIELD_ELEMENTS_PER_EXT_BLOB]` | A polynomial in coefficient form | +| [`Coset`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L27-L33) | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_CELL]` | The evaluation domain of a cell | +| [`CosetEvals`](https://github.com/ethereum/consensus-specs/blob/36a5719b78523c057065515c8f8fcaeba75d065b/pysetup/spec_builders/eip7594.py#L36-L42) | `Vector[BLSFieldElement, FIELD_ELEMENTS_PER_CELL]` | A cell's evaluations over its coset | +""" + file = tmp_path / "predefined_types.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # These should not be in custom_types or constant_vars due to + assert "PolynomialCoeff" not in spec_obj.custom_types + assert "Coset" not in spec_obj.custom_types + assert "CosetEvals" not in spec_obj.custom_types + assert "PolynomialCoeff" not in spec_obj.constant_vars + assert "Coset" not in spec_obj.constant_vars + assert "CosetEvals" not in spec_obj.constant_vars + + +def test_run_skips_eth2spec_skip_code_block(tmp_path, dummy_preset, dummy_config): + md_content = """ +## Helpers + +### `PayloadAttributes` + +Used to signal to initiate the payload build process via `notify_forkchoice_updated`. + + +```python +@dataclass +class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress +``` +""" + file = tmp_path / "dataclass_skip.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + spec_obj = m2s.run() + # The dataclass should NOT be present in the SpecObject's dataclasses + assert "PayloadAttributes" not in spec_obj.dataclasses + + +def test_finalize_types_called_and_updates_custom_types( + tmp_path, dummy_preset, dummy_config, monkeypatch +): + # Minimal markdown with a type definition + md_content = """ +# Types + +| Name | SSZ equivalent | Description | +| ---------------- | -------------- | --------------------------------- | +| `Slot` | `uint64` | a slot number | +| `Epoch` | `uint64` | an epoch number | +""" + file = tmp_path / "types.md" + file.write_text(md_content) + m2s = MarkdownToSpec( + file_name=Path(file), + preset=dummy_preset, + config=dummy_config, + preset_name="mainnet", + ) + + # Spy on _finalize_types + called = {} + orig_finalize_types = m2s._finalize_types + + def spy_finalize_types(): + called["ran"] = True + return orig_finalize_types() + + monkeypatch.setattr(m2s, "_finalize_types", spy_finalize_types) + + spec_obj = m2s.run() + assert called.get("ran") is True + # After _finalize_types, custom_types should include 'Slot' and 'Epoch' + assert spec_obj.custom_types["Slot"] == "uint64" + assert spec_obj.custom_types["Epoch"] == "uint64"